entryPoints:
keycloak: (1)
address: ":8443"
api:
dashboard: true
insecure: true
log:
level: INFO
providers:
file:
directory: "/mnt" (2)
This guide describes how to configure Traefik as a TLS re-encrypt load balancer in front of Keycloak.
In TLS re-encrypt mode, Traefik terminates the incoming TLS connection and establishes a new encrypted connection to Keycloak. Traefik operates at Layer 7 (HTTP). Unlike passthrough, end-to-end TLS encryption between client and Keycloak is not preserved, but the proxy gains the ability to inspect and modify HTTP traffic.
For a general overview of TLS re-encrypt and how it compares to passthrough, see the Configuring a reverse proxy guide.
A ready-to-run example with a Compose file is available in the quickstart repository.
Traefik separates its configuration into two files:
A static configuration file (traefik.yaml) that defines entrypoints and providers, loaded once at startup.
A dynamic configuration file (keycloak.yaml) that defines routing, TLS, and backend transport settings, hot-reloaded by Traefik at runtime.
traefik.yaml)entryPoints:
keycloak: (1)
address: ":8443"
api:
dashboard: true
insecure: true
log:
level: INFO
providers:
file:
directory: "/mnt" (2)
| 1 | Defines an entrypoint named keycloak that listens for TLS connections on port 8443.
The name keycloak is used in the dynamic configuration to bind routers to this entrypoint. |
| 2 | Instructs Traefik to watch the /mnt directory for dynamic configuration files.
The keycloak.yaml dynamic configuration file is mounted here. |
keycloak.yaml)tls:
certificates:
- certFile: /certs/traefik-external/cert.pem (1)
keyFile: /certs/traefik-external/key.pem
http:
serversTransports:
keycloak-transport:
certificates:
- certFile: /certs/traefik-internal/cert.pem (2)
keyFile: /certs/traefik-internal/key.pem
rootCAs:
- /certs/keycloak1-cert.pem (3)
- /certs/keycloak2-cert.pem
middlewares:
filter-headers:
headers:
customRequestHeaders:
# Prevent external spoofing of proxy identity headers (IP, protocol, host, path).
# Traefik re-adds its own verified values after stripping client-supplied ones.
Forwarded: "" (4)
X-Forwarded-For: ""
X-Forwarded-Proto: ""
X-Forwarded-Host: ""
X-Forwarded-Port: ""
X-Forwarded-Server: ""
X-Forwarded-Prefix: ""
X-Forwarded-Access-Token: ""
# Prevent external spoofing of reverse-proxy metadata headers
X-Original-Forwarded-For: ""
X-Original-URL: ""
X-Original-Method: ""
X-Real-IP: ""
X-Forwarded-Tls-Client-Cert: ""
X-Forwarded-Tls-Client-Cert-Info: ""
# Prevent external tracing context injection (W3C Trace Context / Baggage)
Traceparent: ""
Tracestate: ""
Baggage: ""
# Prevent external tracing context injection (Zipkin, Jaeger, OpenTracing)
B3: ""
X-B3-Traceid: ""
X-B3-Spanid: ""
X-B3-Parentspanid: ""
X-B3-Sampled: ""
X-B3-Flags: ""
Uber-Trace-Id: ""
X-Ot-Span-Context: ""
# Allowed source IP ranges. Replace with your internal IP address ranges. (5)
ip-allowlist:
ipAllowList:
sourceRange:
- 192.168.0.0/16
- 172.16.0.0/12
- 10.0.0.0/8
- 127.0.0.0/8
routers:
# Public router: accessible from any source IP address. (6)
keycloak-public:
entryPoints:
- keycloak
rule: "PathPrefix(`/realms/`) || PathPrefix(`/resources/`) || PathPrefix(`/.well-known/`)"
middlewares:
- filter-headers
tls: {}
service: keycloak
# Internal router: all other paths are restricted to the allowed source IP ranges. (7)
keycloak-internal:
entryPoints:
- keycloak
rule: "PathPrefix(`/`)"
priority: 1
middlewares:
- filter-headers
- ip-allowlist
tls: {}
service: keycloak
services:
keycloak:
loadBalancer:
serversTransport: keycloak-transport (8)
healthCheck:
path: /health/ready (9)
port: 9000
scheme: https
interval: "5s"
timeout: "3s"
servers:
- url: "https://keycloak1:8443" (10)
- url: "https://keycloak2:8443"
| 1 | The certificate Traefik presents to clients on the frontend TLS connection. This is the externally trusted certificate that browsers verify. |
| 2 | The certificate Traefik presents to Keycloak on the backend mTLS connection. Keycloak is configured to require client authentication and will verify this certificate against its truststore. |
| 3 | The public certificates of each Keycloak backend server. Traefik verifies each backend server’s TLS certificate against these entries, ensuring the connection goes to a trusted Keycloak instance and not an impersonator. |
| 4 | To remove an HTTP header from requests forwarded to Keycloak, define a key-value entry in customRequestHeaders where the key is the name of the HTTP header and the value is an empty string to indicate the header should be removed.
Removing specific HTTP headers prevents external clients from spoofing proxy identity headers (such as Forwarded, X-Forwarded-*, and X-Real-IP), injecting authentication-related headers (such as X-Forwarded-Access-Token), or injecting distributed tracing context (such as W3C Trace Context, Zipkin B3, or Jaeger headers).
Unlike HAProxy, Traefik does not support regex-based header matching, so each header variant must be listed explicitly.
For the full list of recommended headers to filter, see the Configuring a reverse proxy guide. |
| 5 | Restricts access so that only public Keycloak paths are reachable from external networks. Requests to non-public paths (such as the Admin API or Admin Console) are only allowed from the configured internal IP ranges. Replace the example ranges with your internal IP address ranges. For the full list of paths and recommendations, see the Configuring a reverse proxy guide. |
| 6 | The public router matches only the paths that must be reachable by external clients (/realms/, /resources/, and /.well-known/) and is therefore not subject to the IP allowlist.
The filter-headers middleware and tls settings apply as on the internal router.
With these settings, the redirect to the welcome screen or Admin UI will not work from external IP addresses, and this is expected. |
| 7 | The internal router matches all remaining paths and additionally applies the ip-allowlist middleware, so these paths are only reachable from the allowed source IP ranges.
The priority: 1 is set deliberately low so that Traefik always evaluates the more specific public router first; only requests that do not match a public path fall through to this router. |
| 8 | Links the load balancer to the named transport above, activating mTLS for all connections to the backend Keycloak servers. |
| 9 | Configures HTTP health checks against Keycloak’s readiness endpoint on the management port (9000) over HTTPS.
The management port does not require client authentication, so no mTLS client certificate is needed for health check connections. |
| 10 | The backend Keycloak servers. Traefik load-balances requests across both instances using round-robin. |
With TLS re-encrypt, Keycloak requires the following configuration:
bin/kc.[sh|bat] start --proxy-headers xforwarded --health-enabled true --metrics-enabled true --https-certificate-file=/path/to/certificate --https-certificate-key-file=/path/to/key --https-client-auth=required --https-trust-store-file=/path/to/https-truststore --https-management-client-auth=none
--proxy-headers xforwardedInstructs Keycloak to read the client IP address and protocol from the X-Forwarded- headers set by Traefik.
In re-encrypt mode, Traefik overwrites any incoming X-Forwarded- headers with the correct client values before forwarding requests to Keycloak, preventing clients from injecting misleading header values.
Do not set --proxy-protocol-enabled when using TLS re-encrypt.
The PROXY protocol is only relevant for TLS passthrough (TCP mode).
--health-enabled trueEnables the health check endpoints on the management port (9000).
Without this, the /health/ready 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.
--https-certificate-file=/path/to/certificate and --https-certificate-key-file=/path/to/keyConfigure the certificate and the private key Keycloak will use for HTTPS. See the Configuring TLS guide for additional details.
--https-client-auth=requiredConfigures Keycloak to require client authentication, for mutual TLS.
--https-trust-store-file=/path/to/https-truststoreConfigures a truststore for client authentication. In this case the truststore should contain the public certificate of the Traefik proxy. See the Configuring trusted certificates for mTLS guide for additional details.
--https-management-client-auth=noneDisables the client authentication requirement for the management endpoint.
This allows Traefik to perform HTTPS health checks against the management port (9000) without needing to present the mTLS client certificate.
By default, the Admin UI and Admin API are served on the same port as the public endpoints, protected only by the ip-allowlist middleware.
For stricter network-level isolation, you can move them to a dedicated hostname and port so they can be independently firewalled.
To enable this, add the configurations outlined in the following subsections.
--hostname-admin=https://...Add the full admin URL with its hostname and the port (if it is a non-standard port) to the Keycloak configuration.
|
Setting |
If you choose to have a different hostname for the admin API, update your certificate to include the admin hostname as an additional Subject Alternative Name in the certificate, or issue a separate certificate.
traefik.yamlAdd the keycloak-admin entrypoint to create a second Traefik listener on port 8444:
entryPoints:
keycloak-admin:
...
address: ":8444" (1)
| 1 | Replace with a port of your choice, matching the port configured as admin URL in Keycloak. |
keycloak.yamlRemove the keycloak-internal router as it is no longer needed. The admin paths are now handled exclusively by the dedicated keycloak-admin router.
Leaving both enabled would cause the keycloak-internal router to match admin paths on the original endpoint as well.
Add the keycloak-admin router, which routes all traffic on the keycloak-admin entrypoint to the keycloak service, restricted to the ip-allowlist.
routers:
...
keycloak-admin:
entryPoints:
- keycloak-admin
rule: "PathPrefix(`/`)"
middlewares:
- filter-headers
- pass-client-cert
- ip-allowlist
tls: { }
service: keycloak
|
With this setup, any request to an admin path on the public port (8443 in our example) will fall through to the |
The Traefik health check settings determine how long it takes for the proxy to detect that a Keycloak instance is shutting down and that connections should no longer be routed 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, you need to configure the --shutdown-delay to be at least as long as the detection time:
bin/kc.[sh|bat] start --proxy-headers xforwarded --health-enabled true --metrics-enabled true --https-certificate-file=/path/to/certificate --https-certificate-key-file=/path/to/key --https-client-auth=required --https-trust-store-file=/path/to/https-truststore --https-management-client-auth=none --shutdown-delay=8s
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.