entryPoints:
websecure:
address: ":8443" (1)
api:
insecure: true (2)
This guide describes how to configure Traefik as a TLS passthrough load balancer in front of Keycloak.
In TLS passthrough mode, Traefik forwards the raw TLS connection directly to Keycloak without terminating it. Traefik 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.
Traefik separates its configuration into two files:
traefik.yaml — static configuration loaded at startup (entry points, dashboard, and providers).
keycloak.yaml — dynamic configuration for runtime routing rules, services, and health checks.
traefik.yaml)The following traefik.yaml configures the entry point for TLS traffic and enables the dashboard.
entryPoints:
websecure:
address: ":8443" (1)
api:
insecure: true (2)
| 1 | Defines the entry point on port 8443 where Traefik accepts incoming TCP/TLS connections.
Clients connect to this port, and Traefik forwards the raw TCP stream to the backend. |
| 2 | Enables the Traefik dashboard on port 8080 without authentication.
This is intended for local development and debugging only and must never be used in production. |
keycloak.yaml)The following keycloak.yaml configures the TCP router, load balancer, and health checks.
tcp:
routers:
keycloak-router:
rule: "HostSNI(`*`)" (1)
service: "keycloak-service"
priority: 100 (2)
entryPoints:
- "websecure"
tls:
passthrough: true (3)
serversTransports:
kc-passthrough:
proxyProtocol:
version: 2 (4)
services:
keycloak-service:
loadBalancer:
serversTransport: kc-passthrough (5)
# The inline YAML syntax is required so that \r\n is interpreted literally.
healthCheck: { (6)
interval: "5s",
timeout: "3s",
port: 9000,
send: "HEAD /health/ready HTTP/1.0\r\n\r\n",
expect: "HTTP/1.0 200 OK\r\ncontent-type: application/json; charset=UTF-8\r\ncache-control: no-store\r\n\r\n"
}
servers:
- address: "keycloak1:8443" (7)
- address: "keycloak2:8443"
| 1 | Matches all TLS connections regardless of the SNI hostname.
This is suitable when Traefik is dedicated to Keycloak only.
If Traefik handles multiple backend services, use a specific hostname instead of a wildcard (for example, HostSNI() to avoid routing unintended traffic to Keycloak. |
||||
| 2 | Ensures this router takes precedence over any other TCP routers that may be defined. | ||||
| 3 | Instructs Traefik to forward the raw TLS stream without terminating it. Keycloak handles TLS termination directly. | ||||
| 4 | Enables PROXY protocol v2 on all backend connections.
This allows Keycloak to read the real client IP address from the PROXY protocol header added by Traefik.
This requires Keycloak to be configured with --proxy-protocol-enabled=true (see Keycloak configuration). |
||||
| 5 | Links the load balancer to the named transport above, activating PROXY protocol v2 for all connections to the backend servers. | ||||
| 6 | Configures a TCP-level health check against Keycloak’s management port (9000).
The health check parameters are explained below:
|
||||
| 7 | Defines the two Keycloak backend servers. Traffic is distributed across both instances using round-robin load balancing. |
With TLS passthrough, Keycloak requires the following configuration:
bin/kc.[sh|bat] start --proxy-protocol-enabled true --health-enabled true --metrics-enabled true --http-management-scheme=http
--proxy-protocol-enabled trueEnables the PROXY protocol so that Keycloak can read the real client IP address from the PROXY protocol header added by Traefik.
Do not set --proxy-headers when using TLS passthrough because Traefik cannot inject HTTP headers into the encrypted traffic.
The --proxy-protocol-enabled option and the --proxy-headers option are mutually exclusive.
--health-enabled trueEnables the health check endpoints on the management port (9000).
Without this, the health endpoint that Traefik polls will not be available, and Traefik will mark all Keycloak instances as down.
--metrics-enabled trueEnables 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.
--http-management-scheme httpConfigures the management port (9000) to use plain HTTP instead of HTTPS.
Traefik does not support native HTTP health checks for TCP services; it can only send and match raw bytes over a plain TCP connection.
Disabling TLS on the management port allows the raw send/expect health check (see the health check configuration above) to communicate with Keycloak in plain HTTP.
This is safe because the management port is only reachable on the internal backend network and is not exposed to clients.
With TLS passthrough, Traefik cannot signal a connection close at the HTTP level. The health check timing directly determines how long it takes Traefik 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 (interval: 5s, timeout: 3s), it takes up to 8 seconds (interval + timeout) for Traefik 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 --http-management-scheme=http --shutdown-delay=30s
The 30-second delay accounts for both the detection time (~8 seconds) and draining existing open TCP connections, which Traefik cannot close at the HTTP level in passthrough mode.
A --shutdown-delay=16s (2x the maximum detection time) is sufficient if TCP connection draining is not a concern.
|
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.