HAProxy with TLS passthrough

Configure HAProxy as a TLS passthrough load balancer for Keycloak.

This guide describes how to configure HAProxy as a TLS passthrough load balancer in front of Keycloak.

In TLS passthrough mode, HAProxy forwards the raw TLS connection directly to Keycloak without terminating it. HAProxy operates at the TCP layer (Layer 4) and has no visibility into the HTTP content. The TLS handshake occurs between the client and Keycloak, preserving end-to-end encryption.

For a general overview of TLS passthrough and how it compares to re-encrypt, see the Configuring a reverse proxy guide.

A ready-to-run example with Docker Compose is available in the quickstart repository.

HAProxy configuration

The following haproxy.cfg shows a configuration for TLS passthrough with two Keycloak backend servers.

global
    log stdout format raw local0

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    timeout connect 5s
    timeout client  50s
    timeout server  50s
    retries 3

frontend https_front
    bind *:8443 (1)
    mode tcp (2)
    option tcplog
    default_backend keycloak_back

backend keycloak_back
    mode tcp (3)
    balance roundrobin (4)
    option httpchk GET /health/ready (5)
    http-check expect status 200
    server keycloak1 keycloak1:8443 send-proxy-v2 check port 9000 check-ssl verify none inter 5s fall 3 rise 2 (6)
    server keycloak2 keycloak2:8443 send-proxy-v2 check port 9000 check-ssl verify none inter 5s fall 3 rise 2
1 The port HAProxy listens on for incoming TLS connections. Clients connect to this port, and HAProxy forwards the raw TCP stream to the backend.
2 Enables TCP mode on the frontend. This tells HAProxy to operate at Layer 4, forwarding raw bytes without decrypting TLS. HAProxy never sees the plaintext HTTP traffic in this mode.
3 The backend must also use TCP mode to match the frontend.
4 Distributes connections across backend servers using round-robin load balancing.
5 Configures HTTP health checks against Keycloak’s readiness endpoint. Even though the data traffic uses TCP mode, HAProxy can perform HTTP health checks on a separate port. The http-check expect status 200 directive tells HAProxy to consider the server healthy only when it receives an HTTP 200 response. For details on the Keycloak health check endpoint, see the Configuring the Management Interface guide.
6 Defines a backend Keycloak server. The parameters on this line control PROXY protocol, health checks, and failure detection:

The server directive parameters are explained below:

send-proxy-v2

Enables the PROXY protocol v2. HAProxy prepends the original client IP address to the TCP connection so that Keycloak sees the real source IP instead of HAProxy’s address. This requires Keycloak to be configured with --proxy-protocol-enabled=true (see Keycloak configuration). Version 1 (send-proxy) is also supported.

check port 9000 check-ssl verify none

Directs health checks to the management port (9000) over HTTPS. The verify none option skips TLS certificate verification for the health check connection, which is acceptable because the health check is an internal communication between HAProxy and Keycloak. The management port is separate from the main application port (8443), as described in the Configuring the Management Interface guide.

inter 5s fall 3 rise 2

Configures 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. These values affect how quickly HAProxy detects that a Keycloak instance is shutting down (see Graceful shutdown considerations).

Keycloak configuration

With TLS passthrough, Keycloak requires the following configuration:

bin/kc.[sh|bat] start --proxy-protocol-enabled true --health-enabled true --metrics-enabled true
--proxy-protocol-enabled true

Enables the PROXY protocol so that Keycloak can read the real client IP address from the PROXY protocol header added by HAProxy. Do not set --proxy-headers when using TLS passthrough because HAProxy cannot inject HTTP headers into the encrypted traffic. The --proxy-protocol-enabled option and the --proxy-headers option are mutually exclusive.

--health-enabled true

Enables the health check endpoints on the management port (9000). Without this, the /health/ready endpoint that HAProxy polls will not be available, and HAProxy will mark all Keycloak instances as down.

--metrics-enabled true

Enables the metrics endpoints on the management port. Enabling metrics also enables an additional status check for the database, so it is recommended to enable metrics.

Graceful shutdown considerations

With TLS passthrough, HAProxy cannot signal a connection close at the HTTP level. The health check timing directly determines how long it takes HAProxy to detect that a Keycloak instance is shutting down and stop routing new connections to it.

With the health check settings from the configuration above (inter 5s fall 3), it takes up to 15 seconds (3 failures x 5-second interval) for HAProxy to mark a Keycloak instance as down. During this period, Keycloak must remain running to serve in-flight requests. Therefore, configure the --shutdown-delay to be at least as long as the detection time:

bin/kc.[sh|bat] start --proxy-protocol-enabled true --health-enabled true --metrics-enabled true --shutdown-delay=30s

For a detailed explanation of shutdown phases and how to tune the delay and timeout values, see the Graceful HTTP shutdown section in the Configuring a reverse proxy guide.

Relevant options

Type or Values Default

health-enabled

If the server should expose health check endpoints.

If enabled, health checks are available at the /health, /health/ready and /health/live endpoints.

CLI: --health-enabled
Env: KC_HEALTH_ENABLED

true, false

false

metrics-enabled

If the server should expose metrics.

If enabled, metrics are available at the /metrics endpoint.

CLI: --metrics-enabled
Env: KC_METRICS_ENABLED

true, false

false

proxy-protocol-enabled

Whether the server should use the HA PROXY protocol when serving requests from behind a proxy.

When set to true, the remote address returned will be the one from the actual connecting client. Cannot be enabled when the proxy-headers is used.

CLI: --proxy-protocol-enabled
Env: KC_PROXY_PROTOCOL_ENABLED

true, false

false

On this page