Skip to content

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;
Step 2: Enable sticky sessions using IP hashing, for example,:

ip_hash; #<-- sticky sessions 
If the Keycloak interface loads but behaves unexpectedly—such as displaying "undefined" content or aborting the load—this is often due to misconfigured headers. Double-check that all required headers are correctly set in your reverse proxy configuration.

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'