Broken Keycloak redirects behind a TLS-terminating reverse proxy usually mean that three URL owners disagree: Keycloak’s public hostname, the proxy’s forwarded request identity, and the application’s callback URI. Capture the first wrong scheme, host, port, or path; then repair the layer that created it. Changing every proxy and client setting at once replaces useful evidence with a new configuration.
This guide assumes a current Keycloak production deployment where one trusted reverse proxy receives public HTTPS and forwards HTTP or re-encrypted HTTPS to Keycloak. TLS passthrough is a different topology: the proxy cannot inject HTTP headers into encrypted traffic, so Keycloak’s current documentation says not to enable proxy-headers for passthrough.
A browser may finish on Invalid parameter: redirect_uri, a 403 response, mixed-content warning, blank Admin Console, or login loop. That last symptom does not identify the owner. Start a fresh private-browser session, open the Network panel, preserve the log, and record four artifacts:
client_id, redirect_uri, and realm;Location header until the failure;issuer and authorization endpoint;/auth prefix.Command-line capture is useful because it preserves headers without browser extensions or cached cookies:
curl -fsS -D /tmp/keycloak-discovery.headers -o /tmp/keycloak-discovery.json https://sso.example.com/realms/example/.well-known/openid-configuration
python3 -m json.tool /tmp/keycloak-discovery.json | sed -n '/"issuer"/p;/"authorization_endpoint"/p;/"token_endpoint"/p'
Those files contain public endpoint metadata, not tokens. Keep authorization codes, cookies, client secrets, password-reset links, and bearer tokens out of incident tickets. Stop at the first malformed URL: later redirects often repeat damage introduced earlier.
Keycloak login is not one redirect generated by one component. Its URLs are assembled and validated at different boundaries.
Keycloak publishes its issuer, authorization endpoint, token endpoint, password-reset links, static resource URLs, and administrative URLs. The current Keycloak hostname guide explains why production deployments should pin that identity: dynamically trusting an attacker-controlled Host header could place a fraudulent domain inside security-sensitive links.
For a single public identity, the expected value is normally a full URL such as https://sso.example.com. A hostname without a scheme can leave scheme, port, and path dependent on request headers. That flexibility is useful in some designs, but it creates more moving parts during diagnosis.
Edge termination changes what Keycloak receives. The browser connects to https://sso.example.com:443, while Keycloak may receive plain HTTP on 127.0.0.1:8080. Forwarded headers carry the original scheme, host, port, prefix, and client address across that boundary.
Keycloak parses either standardized Forwarded or X-Forwarded-* headers when proxy-headers selects that family. Its reverse-proxy documentation warns that the proxy must overwrite client-supplied values, not blindly trust or append them. Otherwise a remote client can spoof the apparent scheme, host, or source address used for origin checks, audit records, and IP policy.
OIDC clients send redirect_uri in the authorization request. Keycloak validates that callback against the client’s configured Valid Redirect URIs; Keycloak does not invent a broken application callback after receiving it. If the request already contains http://app.internal:3000/callback, repair the application’s external-URL awareness or its own proxy trust rather than widening Keycloak’s allowlist.
This distinction prevents a common security regression: adding broad wildcards until the error disappears. A successful redirect to an untrusted origin is worse than a rejected login.

Write the expected external contract in one line before editing configuration:
Keycloak frontend: https://sso.example.com
Application callback: https://app.example.com/oidc/callback
Proxy-to-Keycloak hop: http://127.0.0.1:8080
Then express the stable Keycloak side explicitly. The following is environment-file syntax for a container environment file or a systemd EnvironmentFile; it is not a standalone Bash launcher. If you enter these values in an interactive shell before starting Keycloak, prefix each assignment with export so the child process inherits it:
KC_HOSTNAME=https://sso.example.com
KC_HTTP_ENABLED=true
KC_PROXY_HEADERS=xforwarded
KC_PROXY_TRUSTED_ADDRESSES=127.0.0.1/32
KC_HTTP_ENABLED=true belongs to edge termination where the private hop is HTTP. Do not enable it merely because a tutorial did. Re-encrypted TLS keeps HTTPS between proxy and Keycloak; passthrough keeps the client TLS session intact and does not use forwarded HTTP headers.
Current Keycloak releases use hostname v2 and proxy-headers. The Keycloak 26 release notes document removal of the deprecated proxy option, so copying KC_PROXY=edge from an older guide is not a durable repair. Do not combine old and current proxy modes and assume the last option wins predictably.
Dynamic host resolution with KC_HOSTNAME_STRICT=false can serve a legitimate multi-host design, but only when the proxy overwrites Host/forwarded headers and Keycloak accepts traffic exclusively from trusted proxy addresses. A fixed full URL is easier to audit for a normal single-host login service.
Choose one header family and make every layer agree. For a single NGINX proxy using xforwarded, this narrow example preserves the public request identity without accepting a client-provided X-Forwarded-For chain:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
Multi-proxy chains need an explicit trust design at every hop; the single-proxy overwrite above should not be copied into a CDN/load-balancer chain without deciding which address represents the client. Regardless of topology, Keycloak’s chosen family must match what the final trusted proxy sets. xforwarded does not parse a lone RFC Forwarded header, and forwarded does not make an incomplete X-Forwarded-* set authoritative.
Validate the proxy configuration before release and use a reversible reload. Operators using Caddy can follow Voxfor’s validated Caddy reload workflow while preserving the same hostname and trust principles. NGINX, HAProxy, Traefik, IIS, and cloud load balancers express the syntax differently; the URL contract is the portable part.
Random changes are unnecessary once the trace shows which artifact first diverged.
When Keycloak receives proxied HTTP or re-encrypted traffic without the selected proxy-headers mode, current documentation says origin-checked requests can return 403. Confirm the topology, set exactly one header family, and verify that the proxy overwrites it. Do not disable origin protections to make the response green.
If the OIDC issuer or Keycloak-generated redirect is wrong, compare KC_HOSTNAME with the forwarded scheme, host, port, and prefix. A full https://sso.example.com hostname removes ambiguity for the public identity, while forwarded headers still establish request origin. Wrong X-Forwarded-Port is especially easy to miss because Keycloak gives that header precedence over a port embedded in X-Forwarded-Host when xforwarded is selected.
redirect_uri is already wrongRepair the application or the proxy in front of the application. Many frameworks build callback URLs from their own external base URL; others require an explicit issuer/callback setting. Compare the decoded request value character for character with the intended callback before changing Keycloak client configuration.
Keep production redirect URIs narrow. Scheme, hostname, port, path, and trailing-slash behavior can all matter. A wildcard may conceal a broken deployment while expanding where authorization responses can be sent.
Keycloak can live behind /auth, but the proxy and server need one coherent model. The reverse-proxy guide’s context-path section describes three valid approaches: include the path in the full hostname URL, forward X-Forwarded-Prefix with xforwarded, or make Keycloak’s http-relative-path match the proxy. Choose one deliberate ownership model. Stacking all three without mapping the resulting URLs can produce /auth/auth or resource 404s.
A Keycloak health endpoint can return 200 while login redirects remain unusable. Process readiness and identity-flow acceptance answer different questions. After one scoped change, verify from outside the proxy and from a fresh browser session:
curl -fsS https://sso.example.com/realms/example/.well-known/openid-configuration | python3 -m json.tool | sed -n '/"issuer"/p;/"authorization_endpoint"/p'
curl -fsS -o /dev/null -D - 'https://sso.example.com/realms/example/account/' | sed -n '1p;/^location:/Ip'
The discovery issuer must use the intended public scheme, host, port, and path. Next, perform a real authorization-code flow with a dedicated test client or non-privileged account. Confirm that login, callback, token exchange, refresh, logout, and a password-reset link all retain the same issuer and external route where those features are in scope.
Treat each proof as a different boundary:
Infrastructure selection still matters after correctness. Compare VPS hosting options sized for identity services using measured login rate, database latency, memory pressure, backup needs, and recovery objectives. More CPU does not repair a malformed issuer, but undersized identity infrastructure can turn a correct route into unreliable authentication.
Keycloak exposes health and metrics on management port 9000 by default when those features are enabled. The current health-check guidance recommends probing from outside the minimal container because the production image intentionally omits tools such as curl. The reverse-proxy guide also recommends keeping /health, /metrics, administrative paths, and the master realm’s protocol endpoints off the public internet unless a documented need says otherwise.
Temporarily enabled hostname debugging can show how Keycloak resolved scheme, host, port, and path, but /realms/master/hostname-debug is diagnostic exposure. Restrict it to the maintenance path, capture only non-secret evidence, then disable hostname-debug after the incident.
Public availability monitoring should test the safe external protocol surface, such as discovery plus a controlled authorization redirect. Private readiness monitoring should test the management interface from the trusted network. Neither check replaces the other.
Keycloak can return 403 on origin-checked proxied requests when the proxy forwards HTTP or re-encrypted traffic but Keycloak is not configured to parse the matching Forwarded or X-Forwarded-* family. Confirm the topology, set proxy-headers to the family the trusted proxy overwrites, and keep origin checks enabled.
KC_PROXY=edge?Current Keycloak deployments should not use KC_PROXY=edge. Keycloak 26 removed the deprecated proxy option, so deployments should combine the appropriate proxy-headers value with explicit hostname and HTTP/TLS settings for their topology. An edge-termination deployment commonly needs KC_HTTP_ENABLED=true; TLS passthrough does not.
redirect_uri?The OIDC client application supplies redirect_uri in its authorization request; Keycloak validates it against that client’s allowed redirects. If the request already contains an internal hostname or HTTP callback, repair the application’s external URL or its own proxy handling.
KC_HOSTNAME_STRICT=false required behind every proxy?KC_HOSTNAME_STRICT=false is not required behind every proxy. A fixed full KC_HOSTNAME=https://sso.example.com is usually clearer for one public identity. Dynamic host resolution is appropriate only when the design requires it and every accepted request arrives through a proxy that overwrites host-related headers from trusted addresses.
/auth behind a reverse proxy?Yes. Put /auth in the full hostname URL, forward a trusted X-Forwarded-Prefix, or align Keycloak’s http-relative-path with the proxy. Use one coherent mapping and verify discovery, resources, admin access, login, logout, and callback URLs after the change.
Normally no. Keep management port 9000 and /health or /metrics on a trusted network, then use a separate safe external probe for discovery and the authorization route. Process readiness does not prove that public login redirects work.
Sticky sessions can reduce remote cache lookups in a Keycloak cluster, but they do not repair a wrong issuer, scheme, host, port, path, or callback URI. Diagnose the first malformed URL before changing load-balancer affinity; a single-node deployment does not need stickiness for correctness.
End the incident only when the browser, discovery document, proxy trace, and Keycloak configuration agree on the public Keycloak origin, and the application sends the separately recorded exact public callback. Record the fixed frontend URL, chosen header family, trusted proxy range, TLS mode, context path, client callback, and rollback file. That compact contract gives the next operator something stronger than “login worked once.”
Security teams can browse Voxfor security operations library for adjacent infrastructure controls. Keep this incident focused: one public identity, one trusted header contract, one exact callback, and evidence across the complete OIDC round trip.