This quickstart is for educational purposes only and should not be used in production. It demonstrates how to configure HAProxy as a TLS passthrough load balancer in front of a clustered Keycloak deployment.
In TLS passthrough mode, the load balancer forwards encrypted TLS traffic directly to the backend servers without decrypting it. HAProxy operates at the TCP layer (Layer 4) and has no visibility into the HTTP content. The TLS connection is terminated by Keycloak itself, which means:
- Keycloak holds the TLS certificate and private key, not the proxy.
- HAProxy cannot inspect, modify, or cache HTTP headers or the request body.
- End-to-end encryption is preserved between the client and Keycloak.
- HAProxy listens on port 8443 and forwards raw TCP traffic to both Keycloak instances using round-robin.
It uses the PROXY protocol v2 to pass the original client IP address to Keycloak.
It is the only container attached to the
frontendnetwork, making it the single entry point. - Keycloak 1 & 2 are clustered via embedded Infinispan.
They terminate TLS and share the same PostgreSQL database.
They live exclusively on the
backendnetwork, which is marked asinternaland unreachable from the host. - PostgreSQL provides the shared database for Keycloak on the
backendnetwork.
- Docker and Docker Compose
openssl(for certificate generation)
./generate-certs.sh <hostname>This example uses nip.io, a DNS service that maps 127.0.0.1.nip.io to 127.0.0.1, avoiding the need
to edit /etc/hosts:
./generate-certs.sh 127.0.0.1.nip.ioKC_HOST=<hostname> docker compose up -dFor example:
KC_HOST=127.0.0.1.nip.io docker compose up -dOnce the services are up, Keycloak is available at https://<hostname>:8443.
Log in to the admin console using credentials admin / admin.
The browser will show a certificate warning because the certificate is self-signed. This is expected and can be safely accepted for local testing.
Open http://127.0.0.1.nip.io:8404/stats in a browser to verify that both Keycloak backends are healthy.
This is a walkthrough through a graceful shutdown of one of the Keycloak instances:
- Open http://127.0.0.1.nip.io:8404/stats in a browser to verify that both Keycloak backends are healthy.
- Send a
TERMsignal to one of the Keycloak containers for a graceful shutdown (takes 30 seconds). Container exits with code 143.docker compose stop keycloak1 -t 60
- Observe that after 3x5=15 seconds the
keycloak1backend turns UP/green to UP/yellow and eventually to DOWN/red. Requests are still served by the node until it shuts down gracefully after 30 seconds. - Start the Keycloak container again:
docker compose start keycloak1
- Observe that after 2x5=10 seconds the
keycloak1backend turns DOWN/yellow and eventually UP/green.
docker compose downThe key parts of haproxy.cfg are explained below.
TCP mode for TLS passthrough:
mode tcp
HAProxy operates in TCP mode (Layer 4), forwarding raw bytes without decrypting TLS. It never sees the plaintext HTTP traffic.
HTTP health check on the management port:
option httpchk GET /health/ready
http-check expect status 200
HAProxy performs health checks against Keycloak's management endpoint /health/ready, expecting an HTTP 200 response.
This endpoint is only available when Keycloak is configured with KC_HEALTH_ENABLED=true and KC_METRICS_ENABLED=true.
Server lines:
server keycloak1 keycloak1:8443 send-proxy-v2 check port 9000 check-ssl verify none inter 5s fall 3 rise 2
-
send-proxy-v2enables the PROXY protocol v2, which prepends the original client IP address to the TCP connection so Keycloak sees the real source IP instead of HAProxy's. Version 1 (send-proxy) is also supported. This requires Keycloak to be configured withKC_PROXY_PROTOCOL_ENABLED=true. -
check port 9000 check-ssl verify nonedirects health checks to the management port (9000) over HTTPS, skipping certificate verification for the health check connection. -
inter 5s fall 3 rise 2configures the health check frequency: poll every 5 seconds, mark a server as down after 3 consecutive failures, and mark it as up again after 2 consecutive successes.
Graceful shutdown timing:
With the values above, it may take up to 15 seconds (3 failures x 5s interval) for HAProxy to detect that a Keycloak instance is down.
For this reason, Keycloak is configured with KC_SHUTDOWN_DELAY=30s and
KC_SHUTDOWN_TIMEOUT=30s, giving HAProxy enough time to detect the shutdown and allowing existing client connections to drain gracefully.