Traefik with TLS re-encrypt

Configure Traefik as a TLS re-encrypt load balancer for Keycloak.

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 configuration

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.

Static configuration (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.

Dynamic configuration (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.

Keycloak configuration

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 xforwarded

Instructs 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 true

Enables 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 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.

--https-certificate-file=/path/to/certificate and --https-certificate-key-file=/path/to/key

Configure the certificate and the private key Keycloak will use for HTTPS. See the Configuring TLS guide for additional details.

--https-client-auth=required

Configures Keycloak to require client authentication, for mutual TLS.

--https-trust-store-file=/path/to/https-truststore

Configures 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=none

Disables 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.

Optional: Admin UI and Admin API on a dedicated hostname and port

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.

Keycloak configuration

--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 hostname-admin only changes the URLs Keycloak generates for the Admin Console and Admin API. It does not stop the Admin API from also answering on the public frontend URL — restricting administration access to the dedicated port must be enforced at the reverse proxy, which is what the rules below do. See the Configuring the hostname (v2) guide.

Traefik configuration

TLS certificate

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.yaml

Add 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.yaml
  1. Remove 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.

  2. 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 keycloak-public router, which only matches /realms/, /resources/, and /.well-known/. Admin paths on this port will return a 404.

Graceful shutdown considerations

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.

Relevant options

Type or Values Default

https-certificate-file

The file path to a server certificate or certificate chain in PEM format.

CLI: --https-certificate-file
Env: KC_HTTPS_CERTIFICATE_FILE

File

https-certificate-key-file

The file path to a private key in PEM format.

CLI: --https-certificate-key-file
Env: KC_HTTPS_CERTIFICATE_KEY_FILE

File

https-client-auth

Configures the server to require/request client authentication.

CLI: --https-client-auth
Env: KC_HTTPS_CLIENT_AUTH

none, request, required

none

https-trust-store-file

The trust store which holds the certificate information of the certificates to trust.

CLI: --https-trust-store-file
Env: KC_HTTPS_TRUST_STORE_FILE

File

shutdown-delay

Length of the pre-shutdown phase during which the server prepares for shutdown.

May be an ISO 8601 duration value, an integer number of seconds, or an integer followed by one of [ms, h, m, s, d]. This period allows for loadbalancer reconfiguration and draining of TLS/HTTP keepalive connections.

CLI: --shutdown-delay
Env: KC_SHUTDOWN_DELAY

String

1s

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

https-management-client-auth

Configures the management interface to require/request client authentication.

If not given, the value is inherited from HTTP options. Relevant only when something is exposed on the management interface - see the guide for details.

CLI: --https-management-client-auth
Env: KC_HTTPS_MANAGEMENT_CLIENT_AUTH

none, request, required

none

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-headers

The proxy headers that should be accepted by the server.

Misconfiguration might leave the server exposed to security vulnerabilities. Takes precedence over the deprecated proxy option.

CLI: --proxy-headers
Env: KC_PROXY_HEADERS

forwarded, xforwarded

On this page