Keycloak High Availability Setup: 2 Nodes, 1 Load Balancer (Nginx)
Overview
This document outlines the architecture and configuration steps for deploying a highly available Keycloak cluster consisting of two Keycloak nodes, a central Oracle database, and a multi-layered Nginx setup acting as a reverse proxy and load balancer.
Important
For High Availability, both Keycloak nodes must connect to the same database instance. This database is the single source of truth for our Keycloak deployment. For this reason, steps described in Keycloak Migration to Oracle are mandatory. This condition is critical for the infrastructure in HAJ.
The architecture involves three distinct server roles:
- Load Balancer (LB) Nginx: The public-facing entry point, distributing traffic to the intermediate Nginx nodes.
- Keycloak Node Nginx (Node 1 & Node 2): Intermediate Nginx servers on each Keycloak node, handling SSL termination and proxying requests to their respective local Keycloak container.
- Keycloak Application (Node 1 & Node 2): The actual Keycloak instances running in Docker containers, configured for clustering and connecting to a shared Oracle database.
- Central Database: A dedicated database server accessible by both Keycloak application nodes, crucial for state synchronization and persistence.
Prerequisites
Before proceeding, ensure you have the following in place:
- Three Servers/VMs:
- One for the Load Balancer (e.g., loadbalancer.quintessence.de)
- Two for Keycloak Nodes
- A central DB
- Docker & Docker Compose installed
- SSL Certificates: Valid TLS certificates (and their private keys) for:
- loadbalancer.quintessence.de
- auth-node1.quintessence.de
- auth-node2.quintessence.de
- On Each Keycloak Node Host, these ports need to be open to the Load Balancer Nginx and between the Keycloak Node Hosts themselves.
| Port | Protocol | Direction | Purpose |
|---|---|---|---|
| 80 | TCP | Inbound | HTTP traffic, typically for redirecting to HTTPS. (From LB Nginx or direct if allowed for testing) |
| 443 | TCP | Inbound | HTTPS traffic from the Load Balancer Nginx. |
| 1521 | TCP | Outbound | To the Central Oracle Database for database operations. |
| 7600 | UDP/TCP | Bi-direction | Keycloak JGroups Clustering: Used by Infinispan for inter-node communication and state replication. While jdbc-ping handles discovery, nodes still need direct ports for communication. 7600 is the common default for JGroups. If UDP is blocked, Keycloak might switch to TCP-based stacks, possibly on 7800 or a range. It's best to allow both, or confirm the exact JGroups stack being used by examining Keycloak's server logs during startup. |
Keycloak Node Setup
Info
This section is based on the example of auth-node1.quintessence.de and auth-node2.quintessence.de
Each Keycloak node will run its own Nginx instance and a Docker container for the Keycloak application.
External docker network
A Docker external network named quintessence for inter-container communication needs to be created once with this command line:
docker network create quintessence || true # '|| true' prevents error if it already exists
Keycloak Application Docker Compose (docker-compose.yml)
Docker compose adaptions:
Change the URL to use the load balancer
- KC_HOSTNAME_URL: https://loadbalancer.quintessence.de/auth
- KC_HOSTNAME_ADMIN_URL: https://loadbalancer.quintessence.de/auth
- KC_HTTP_RELATIVE_PATH: auth
- KC_HOSTNAME_INTERNAL_URL: https://loadbalancer.quintessence.de/auth
Clustering Configuration
- KC_NODE_NAME: keycloak-node1 # Unique node name for clustering
- KC_CACHE: ispn # Infinispan cache
- KC_CACHE_STACK: jdbc-ping # JGroups stack using JDBC_PING for discovery
JAVA_OPTS_APPEND: -Djgroups.external_addr={ IP of the current node | host name instead IP has yet to be tested }
Open the port for the Keycloak service
ports:
"8180:8080"
"8789:8789"
"7600:7600"`
Under service, add an external network which will also be used for nginx networks:
networks:
quintessence:
external: true
name: quintessence # Reference the existing external network`
Use that network in keycloak networks: * quintessence # Attach to the shared network
Take a reference from this git repo: nginx-custom-dtf/high-availability/dev/auth-node1.quintessence.de/keycloak/docker-compose.yml
Keycloak Node Nginx Setup (Intermediate Proxy)
This Nginx instance will run on each Keycloak node, acting as an SSL termination point and proxying requests to the local Keycloak Docker container over the shared quintessence network.
Docker compose adaptions
Our standard docker-compose for nginx can be used. Only the extern network must be added as used in all services.
networks:
quintessence:
name: quintessence
external: true
Take a reference from this git repo: nginx-custom-dtf/high-availability/dev/auth-node1.quintessence.de/nginx/docker-compose.yml
Nginx conf
Step 1: Adapt the Nginx conf that forwards to Keycloak. All locations must be configured as follows:
location /auth/realms {
proxy_set_header Host $http_x_forwarded_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 $http_x_forwarded_host;
proxy_pass http://keycloak-node1:8080/auth/realms;
}
A regular Keycloak configuration:
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
needs to be changed to:
proxy_set_header X-Forwarded-Host $http_x_forwarded_host;
Step 2: Note that the proxy_pass directive points to keycloak-node1:8080, where keycloak-node1 is the internal hostname of the Keycloak container as defined in the docker-compose.yml file. This allows NGINX to forward requests directly to the Keycloak service within the Docker network.
Step 3: Replace the Host header on the individual Keycloak instance as follows:
proxy_set_header Host $http_x_forwarded_host;
Load Balancer Nginx Setup
(loadbalancer.quintessence.de)
This is the public-facing Nginx instance that distributes traffic to your two Keycloak Node Nginx servers.
Keycloak admin URL
To ensure the Keycloak admin URL functions correctly, the following configurations are required:
Step 1: Forward the original Host header using the X-Forwarded-Host header:
$http_x_forwarded_host;
ip_hash; #<-- sticky sessions
Changelog
| Date | Author | Message |
|---|---|---|
| 2026-03-04 | aresnikowa | QC-47927: aligned with the template, mkDocs formatting alignment |
| 2026-02-26 | aresnikowa | qc-0: How to reuse content |
| 2026-02-25 | aresnikowa | QC-50171: in Keycloak folder, adjusted admonitions |
| 2026-02-25 | aresnikowa | Merge remote-tracking branch 'origin/master' |