Introduction
Keycloak is an open source identity and access management (IAM) platform developed by Red Hat. Provides centralized authentication and authorization using standard protocols such as OpenID Connect, OAuth 2.0 and SAML 2.0, allowing you to implement Single Sign-On (SSO) throughout the organization without having to modify the applications.
In production environments, identity is a critical service: if Keycloak goes down, users cannot authenticate to any application. Therefore, deploy Keycloak in high availability (HA) It is essential to guarantee business continuity.
In this guide we will deploy a complete HA architecture with the following components:
- 2 Keycloak nodes in active-active cluster with Infinispan for session replication.
- 3 PostgreSQL nodes managed by Patroni and Etcd for high availability of the database with automatic failover.
- 2 Nginx nodes as load balancers with Keepalived for a VIP (Virtual IP) with automatic failover.
The architecture diagram is as follows:
Clientes
|
[VIP: 192.168.1.220]
/ \
[lb1: .212] [lb2: .213] <-- Nginx + Keepalived
\ /
(upstream HTTP/8080)
/ \
[kc1: .210] [kc2: .211] <-- Keycloak (clúster Infinispan)
\ /
(JDBC connection)
/ | \
[db1:.214] [db2:.215] [db3:.216] <-- PostgreSQL + Patroni + Etcd
Prerequisites
we will need 7 servers with Debian 12 (Bookworm) or Ubuntu 22.04. The role of each one is described below:
Hostname IP Rol
-------- --------------- ---------------------------------
kc1 192.168.1.210 Nodo Keycloak 1
kc2 192.168.1.211 Nodo Keycloak 2
lb1 192.168.1.212 Balanceador Nginx + Keepalived (MASTER)
lb2 192.168.1.213 Balanceador Nginx + Keepalived (BACKUP)
db1 192.168.1.214 PostgreSQL + Patroni + Etcd (nodo 1)
db2 192.168.1.215 PostgreSQL + Patroni + Etcd (nodo 2)
db3 192.168.1.216 PostgreSQL + Patroni + Etcd (nodo 3)
VIP 192.168.1.220 IP virtual gestionada por Keepalived
Additional requirements:
- Java 17+ at nodes kc1 and kc2.
- DNS resolution or entries in
/etc/hostsfor all hostnames. - Root or sudo access on all nodes.
- The following ports must be open on the internal firewall:
Puerto Protocolo Componente Entre
-------- --------- ----------------- ---------------------------
2379 TCP Etcd client API db1, db2, db3 <-> db1, db2, db3
2380 TCP Etcd peer db1, db2, db3 <-> db1, db2, db3
5432 TCP PostgreSQL kc1, kc2 -> db1, db2, db3
8008 TCP Patroni REST API db1, db2, db3 internamente
8080 TCP Keycloak HTTP lb1, lb2 -> kc1, kc2
7800 TCP JGroups (Infinispan) kc1 <-> kc2
80/443 TCP Nginx Clientes -> lb1, lb2
112 VRRP Keepalived lb1 <-> lb2
Add the entries in /etc/hosts on all nodes:
cat >> /etc/hosts <<EOF
192.168.1.210 kc1
192.168.1.211 kc2
192.168.1.212 lb1
192.168.1.213 lb2
192.168.1.214 db1
192.168.1.215 db2
192.168.1.216 db3
EOF
Installing PostgreSQL and Patroni
Perform the following steps in the three database nodes (db1, db2 and db3).
We install PostgreSQL 16, Patroni and its dependencies:
# Actualizar repositorios
apt update && apt upgrade -y
# Instalar dependencias de Python y PostgreSQL
apt install -y python3 python3-pip python3-dev libpq-dev gcc
# Instalar el repositorio oficial de PostgreSQL
apt install -y curl ca-certificates gnupg
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/postgresql-keyring.gpg] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list
apt update
# Instalar PostgreSQL 16
apt install -y postgresql-16 postgresql-client-16
# Instalar Patroni con soporte para Etcd y PostgreSQL
pip3 install patroni[etcd] psycopg2-binary
# Instalar Etcd
apt install -y etcd
Roles of each component:
- PostgreSQL- The relational database engine where Keycloak stores its data.
- Patroni- Agent that manages the PostgreSQL lifecycle, performs leader election, and executes automatic failover.
- Etc.– Distributed key-value store that Patroni uses as a consensus store (DCS – Distributed Configuration Store) to coordinate which node is the leader.
Stop PostgreSQL so Patroni can manage it:
systemctl stop postgresql
systemctl disable postgresql
Etcd cluster configuration
Configure the Etcd service on each of the three database nodes. The configuration file is located in /etc/default/etcd.
db1 (192.168.1.214):
ETCD_NAME="db1"
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.1.214:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.214:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.1.214:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.214:2379"
ETCD_INITIAL_CLUSTER="db1=http://192.168.1.214:2380,db2=http://192.168.1.215:2380,db3=http://192.168.1.216:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="keycloak-etcd-cluster"
db2 (192.168.1.215):
ETCD_NAME="db2"
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.1.215:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.215:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.1.215:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.215:2379"
ETCD_INITIAL_CLUSTER="db1=http://192.168.1.214:2380,db2=http://192.168.1.215:2380,db3=http://192.168.1.216:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="keycloak-etcd-cluster"
db3 (192.168.1.216):
ETCD_NAME="db3"
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.1.216:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.216:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.1.216:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.216:2379"
ETCD_INITIAL_CLUSTER="db1=http://192.168.1.214:2380,db2=http://192.168.1.215:2380,db3=http://192.168.1.216:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="keycloak-etcd-cluster"
Start and enable the service on all three nodes:
systemctl enable etcd
systemctl start etcd
Check the status of the Etcd cluster from any of the nodes:
etcdctl --endpoints=http://192.168.1.214:2379,http://192.168.1.215:2379,http://192.168.1.216:2379 endpoint health
The expected output is:
http://192.168.1.214:2379 is healthy: successfully committed proposal: took = 2.3ms
http://192.168.1.215:2379 is healthy: successfully committed proposal: took = 2.5ms
http://192.168.1.216:2379 is healthy: successfully committed proposal: took = 2.7ms
Patroni Settings
Create the configuration directory and the data file on the three nodes:
mkdir -p /etc/patroni
mkdir -p /data/patroni
chown postgres:postgres /data/patroni
chmod 700 /data/patroni
Patroni configuration in db1 — /etc/patroni/config.yml:
scope: keycloak-postgres
namespace: /db/
name: db1
restapi:
listen: 192.168.1.214:8008
connect_address: 192.168.1.214:8008
etcd:
hosts: 192.168.1.214:2379,192.168.1.215:2379,192.168.1.216:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
max_wal_senders: 5
max_replication_slots: 5
wal_log_hints: "on"
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication replicator 192.168.1.0/24 md5
- host all all 0.0.0.0/0 md5
users:
admin:
password: "AdminPassword123!" # CAMBIAR EN PRODUCCIÓN
options:
- createrole
- createdb
postgresql:
listen: 192.168.1.214:5432
connect_address: 192.168.1.214:5432
data_dir: /data/patroni
bin_dir: /usr/lib/postgresql/16/bin
pgpass: /tmp/pgpass0
authentication:
replication:
username: replicator
password: "ReplicaPassword123!" # CAMBIAR EN PRODUCCIÓN
superuser:
username: postgres
password: "PostgresPassword123!" # CAMBIAR EN PRODUCCIÓN
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
Patroni configuration in db2 — /etc/patroni/config.yml (they only change name, listen and connect_address):
scope: keycloak-postgres
namespace: /db/
name: db2
restapi:
listen: 192.168.1.215:8008
connect_address: 192.168.1.215:8008
etcd:
hosts: 192.168.1.214:2379,192.168.1.215:2379,192.168.1.216:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
max_wal_senders: 5
max_replication_slots: 5
wal_log_hints: "on"
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication replicator 192.168.1.0/24 md5
- host all all 0.0.0.0/0 md5
users:
admin:
password: "AdminPassword123!" # CAMBIAR EN PRODUCCIÓN
options:
- createrole
- createdb
postgresql:
listen: 192.168.1.215:5432
connect_address: 192.168.1.215:5432
data_dir: /data/patroni
bin_dir: /usr/lib/postgresql/16/bin
pgpass: /tmp/pgpass0
authentication:
replication:
username: replicator
password: "ReplicaPassword123!" # CAMBIAR EN PRODUCCIÓN
superuser:
username: postgres
password: "PostgresPassword123!" # CAMBIAR EN PRODUCCIÓN
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
Patroni configuration in db3 — /etc/patroni/config.yml:
scope: keycloak-postgres
namespace: /db/
name: db3
restapi:
listen: 192.168.1.216:8008
connect_address: 192.168.1.216:8008
etcd:
hosts: 192.168.1.214:2379,192.168.1.215:2379,192.168.1.216:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
max_wal_senders: 5
max_replication_slots: 5
wal_log_hints: "on"
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication replicator 192.168.1.0/24 md5
- host all all 0.0.0.0/0 md5
users:
admin:
password: "AdminPassword123!" # CAMBIAR EN PRODUCCIÓN
options:
- createrole
- createdb
postgresql:
listen: 192.168.1.216:5432
connect_address: 192.168.1.216:5432
data_dir: /data/patroni
bin_dir: /usr/lib/postgresql/16/bin
pgpass: /tmp/pgpass0
authentication:
replication:
username: replicator
password: "ReplicaPassword123!" # CAMBIAR EN PRODUCCIÓN
superuser:
username: postgres
password: "PostgresPassword123!" # CAMBIAR EN PRODUCCIÓN
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
Create the systemd service for Patroni on all three nodes (/etc/systemd/system/patroni.service):
[Unit]
Description=Patroni - High Availability PostgreSQL
After=syslog.target network.target etcd.service
Wants=etcd.service
[Service]
Type=simple
User=postgres
Group=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni/config.yml
KillMode=process
TimeoutSec=30
Restart=no
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable patroni
systemctl start patroni
Check the status of the Patroni cluster:
patronictl -c /etc/patroni/config.yml list
You should see output similar to this, where one of the nodes is the Leader and the others are Replicas:
+ Cluster: keycloak-postgres (7890123456789012345) +----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+-----------------+---------+---------+----+-----------+
| db1 | 192.168.1.214:5432 | Leader | running | 1 | |
| db2 | 192.168.1.215:5432 | Replica | running | 1 | 0 |
| db3 | 192.168.1.216:5432 | Replica | running | 1 | 0 |
+--------+-----------------+---------+---------+----+-----------+
Creating the Keycloak database
Connect to the Patroni leader node (in the example, db1) and create the user and database for Keycloak:
# Conectar como superusuario de PostgreSQL
sudo -u postgres psql -h 192.168.1.214 -p 5432
Once inside the psql session, run:
-- Crear usuario de Keycloak (cambia la contraseña en producción)
CREATE USER keycloak WITH PASSWORD 'KeycloakDBPassword123!';
-- Crear base de datos
CREATE DATABASE keycloak OWNER keycloak ENCODING 'UTF8' LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0;
-- Otorgar todos los privilegios
GRANT ALL PRIVILEGES ON DATABASE keycloak TO keycloak;
-- Verificar
\l keycloak
\q
Keycloak installation
Perform the following steps in the two Keycloak nodes (kc1 and kc2).
Install Java 17, which is the minimum requirement for Keycloak 24.x:
apt update
apt install -y openjdk-17-jdk curl
# Verificar versión de Java
java -version
Download and install Keycloak 24.0.5 (the latest stable version available):
# Descargar Keycloak
cd /opt
curl -LO https://github.com/keycloak/keycloak/releases/download/24.0.5/keycloak-24.0.5.tar.gz
# Extraer
tar -xzf keycloak-24.0.5.tar.gz
mv keycloak-24.0.5 keycloak
rm keycloak-24.0.5.tar.gz
# Crear usuario del sistema
groupadd -r keycloak
useradd -r -g keycloak -d /opt/keycloak -s /sbin/nologin keycloak
# Asignar permisos
chown -R keycloak:keycloak /opt/keycloak
chmod -R 750 /opt/keycloak
Configuring Keycloak in cluster mode
Edit the main configuration file /opt/keycloak/conf/keycloak.conf in both nodes. The only difference between kc1 and kc2 is the IP in the parameter hostname and the Infinispan parameter.
Configuration on kc1 (192.168.1.210):
# Base de datos
db=postgres
db-url=jdbc:postgresql://192.168.1.214:5432,192.168.1.215:5432,192.168.1.216:5432/keycloak?targetServerType=primary&loadBalanceHosts=false
db-username=keycloak
db-password=KeycloakDBPassword123!
db-pool-min-size=5
db-pool-initial-size=5
db-pool-max-size=20
# HTTP
http-enabled=true
http-host=0.0.0.0
http-port=8080
https-port=8443
# Hostname (usa la URL pública expuesta por el balanceador)
hostname=https://keycloak.example.com
hostname-admin=https://keycloak.example.com
hostname-strict=false
hostname-strict-backchannel=false
# Proxy
proxy=edge
# Clúster Infinispan
cache=ispn
cache-stack=tcp
# Logging
log=console
log-level=INFO
Configuration in kc2 (192.168.1.211): identical to kc1.
For communication between cluster nodes, Keycloak uses JGroups with the protocol TCPPING. Create the file /opt/keycloak/conf/cache-ispn-tcp.xml on both nodes:
# Copiar la plantilla incluida con Keycloak
cp /opt/keycloak/conf/cache-ispn.xml /opt/keycloak/conf/cache-ispn-tcp.xml
Edit the section JGroups in cache-ispn-tcp.xml to define the cluster nodes:
# En kc1, añade la variable de entorno para JGroups TCPPING
# En /opt/keycloak/conf/keycloak.conf añade al final:
cache-ispn-config=conf/cache-ispn-tcp.xml
Add the JGroups environment variables to the file /opt/keycloak/conf/keycloak.conf of each node so that Infinispan discovers the peers via TCPPING:
# En kc1
export JAVA_OPTS_APPEND="-Djgroups.tcpping.initial_hosts=192.168.1.210[7800],192.168.1.211[7800] -Djgroups.bind.address=192.168.1.210"
# En kc2
export JAVA_OPTS_APPEND="-Djgroups.tcpping.initial_hosts=192.168.1.210[7800],192.168.1.211[7800] -Djgroups.bind.address=192.168.1.211"
Compile and start Keycloak on both nodes:
sudo -u keycloak /opt/keycloak/bin/kc.sh build
sudo -u keycloak /opt/keycloak/bin/kc.sh start
Create the systemd service on both nodes (/etc/systemd/system/keycloak.service):
[Unit]
Description=Keycloak Identity Provider
After=network.target
[Service]
User=keycloak
Group=keycloak
WorkingDirectory=/opt/keycloak
Environment=KEYCLOAK_ADMIN=admin
Environment=KEYCLOAK_ADMIN_PASSWORD=AdminPassword123!
# En kc1:
Environment=JAVA_OPTS_APPEND=-Djgroups.tcpping.initial_hosts=192.168.1.210[7800],192.168.1.211[7800] -Djgroups.bind.address=192.168.1.210
ExecStart=/opt/keycloak/bin/kc.sh start
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=keycloak
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable keycloak
systemctl start keycloak
systemctl status keycloak
Nginx Load Balancer Configuration
Perform the following steps in the two balancing nodes (lb1 and lb2).
Install Nginx:
apt update
apt install -y nginx
systemctl enable nginx
Create the virtual host configuration in /etc/nginx/sites-available/keycloak:
upstream keycloak_nodes {
# Distribución round-robin por defecto
# Para sticky sessions basadas en IP:
ip_hash;
server 192.168.1.210:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.211:8080 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name keycloak.example.com;
# Redirigir HTTP a HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name keycloak.example.com;
# Certificados SSL (Let's Encrypt o corporativos)
ssl_certificate /etc/nginx/ssl/keycloak.crt;
ssl_certificate_key /etc/nginx/ssl/keycloak.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Cabeceras de seguridad
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
# Health check endpoint
location /health {
access_log off;
return 200 "OK\n";
}
location / {
proxy_pass http://keycloak_nodes;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# Soporte para WebSocket (usado por la consola de administración)
proxy_cache_bypass $http_upgrade;
}
}
Activate the configuration and reload Nginx:
ln -s /etc/nginx/sites-available/keycloak /etc/nginx/sites-enabled/
rm /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx
Balancer High Availability with Keepalived
Keepalived implements the protocol VRRP (Virtual Router Redundancy Protocol) to manage a virtual IP (VIP) between the two Nginx nodes. If lb1 goes down, the VIP automatically migrates to lb2 within seconds.
apt install -y keepalived
lb1 setting (MASTER) — /etc/keepalived/keepalived.conf:
global_defs {
router_id LB1
}
vrrp_script check_nginx {
script "pgrep nginx"
interval 2
weight -10
}
vrrp_instance VI_KEYCLOAK {
state MASTER
interface eth0 # ajusta a tu interfaz de red
virtual_router_id 51
priority 110
advert_int 1
authentication {
auth_type PASS
auth_pass keycloakVRRP # CAMBIAR EN PRODUCCIÓN
}
virtual_ipaddress {
192.168.1.220/24
}
track_script {
check_nginx
}
}
lb2 configuration (BACKUP) — /etc/keepalived/keepalived.conf:
global_defs {
router_id LB2
}
vrrp_script check_nginx {
script "pgrep nginx"
interval 2
weight -10
}
vrrp_instance VI_KEYCLOAK {
state BACKUP
interface eth0 # ajusta a tu interfaz de red
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass keycloakVRRP # CAMBIAR EN PRODUCCIÓN
}
virtual_ipaddress {
192.168.1.220/24
}
track_script {
check_nginx
}
}
Enable and start Keepalived on both nodes:
systemctl enable keepalived
systemctl start keepalived
# Verificar que la VIP está asignada en lb1
ip addr show eth0 | grep 192.168.1.220
Verification and testing
With all the infrastructure in place, perform the following checks.
1. Access to the administration console:
Open a browser and access https://keycloak.example.com/admin. You should see the Keycloak login screen. Log in with the user admin and the configured password.
2. Check the status of the Keycloak cluster:
# Comprobar logs en kc1 y kc2 para ver que se han unido al clúster
journalctl -u keycloak -f | grep -i "cluster\|ispn\|jgroups"
# Debe aparecer algo como:
# ISPN000094: Received new cluster view for channel ISPN: [kc1|1] (2) [kc1, kc2]
3. Keycloak failover test:
# En kc1, para el servicio
systemctl stop keycloak
# Verifica que el servicio sigue respondiendo a través del LB (kc2 debe responder)
curl -s -o /dev/null -w "%{http_code}" https://keycloak.example.com/health/ready
# Respuesta esperada: 200
# Vuelve a arrancar kc1
systemctl start keycloak
4. PostgreSQL Failover Test (Patroni):
# En db1 (líder actual), para Patroni
systemctl stop patroni
# Observa cómo Patroni promueve automáticamente db2 o db3 como nuevo líder
patronictl -c /etc/patroni/config.yml list
# Verifica que Keycloak sigue funcionando (la URL de JDBC apunta a todos los nodos)
curl -s -o /dev/null -w "%{http_code}" https://keycloak.example.com/health/ready
5. Balancer failover test (Keepalived):
# En lb1, para Nginx para simular un fallo
systemctl stop nginx
# La VIP debería migrar a lb2 en pocos segundos
# Verificar desde lb2:
ip addr show eth0 | grep 192.168.1.220
# Comprobar conectividad
curl -s -o /dev/null -w "%{http_code}" https://keycloak.example.com/health/ready
Useful monitoring commands:
# Estado del clúster Patroni
patronictl -c /etc/patroni/config.yml list
# Estado detallado de Patroni vía API REST
curl http://192.168.1.214:8008/cluster | python3 -m json.tool
# Estado de Etcd
etcdctl --endpoints=http://192.168.1.214:2379,http://192.168.1.215:2379,http://192.168.1.216:2379 endpoint status --write-out=table
# Logs de Keycloak
journalctl -u keycloak -n 100 --no-pager
# Conexiones activas en Nginx
nginx -T | grep upstream
Security considerations
Once the infrastructure is up and running, it is essential to apply the following security measures before moving to production:
1. Change all default passwords:
All passwords in this manual are examples. They should be replaced with randomly generated strong passwords:
# Generar contraseñas seguras de 32 caracteres
openssl rand -base64 32
2. Enable TLS on all components:
- Etc.: configure
ETCD_PEER_CLIENT_CERT_AUTH,ETCD_PEER_CERT_FILEandETCD_PEER_KEY_FILEto encrypt communication between peers. - Patroni ↔ Etcd: use options
certfileandkeyfilein the sectionetcdfrom the Patroni config. - PostgreSQL- Enable SSL on
postgresql.confwithssl = onand configure the certificates. - Keycloak- Although SSL encryption is terminated in Nginx, consider enabling internal HTTPS between Nginx and Keycloak for high security environments.
3. Firewall rules between components:
# En los nodos de base de datos: permitir acceso PostgreSQL solo desde kc1 y kc2
ufw allow from 192.168.1.210 to any port 5432
ufw allow from 192.168.1.211 to any port 5432
# Comunicación Etcd solo entre nodos de DB
ufw allow from 192.168.1.214 to any port 2379:2380
ufw allow from 192.168.1.215 to any port 2379:2380
ufw allow from 192.168.1.216 to any port 2379:2380
# En los nodos Keycloak: permitir acceso al puerto 8080 solo desde LBs
ufw allow from 192.168.1.212 to any port 8080
ufw allow from 192.168.1.213 to any port 8080
# Comunicación JGroups solo entre nodos KC
ufw allow from 192.168.1.210 to any port 7800
ufw allow from 192.168.1.211 to any port 7800
4. Restrict access to the Keycloak management console:
It is advisable to limit access to /admin only from trusted networks (for example, the administration network). In Nginx configuration:
location /admin {
allow 192.168.10.0/24; # red de administración
deny all;
proxy_pass http://keycloak_nodes;
# ... resto de directivas proxy ...
}
5. Rotate secrets regularly and store them in a secrets manager such as HashiCorp Vault or your cloud provider's secrets manager.
:wq!
Comments