Fixing Keycloak 502 Bad Gateway Errors
Last updated: June 2026
A 502 Bad Gateway means your reverse proxy — nginx, Traefik, HAProxy, or an AWS ALB — successfully reached the proxy layer itself but could not get a valid response from the Keycloak upstream. The error lives in the proxy-to-upstream leg, not in the client-to-proxy leg. The most common causes are Keycloak not yet fully started, the proxy pointing at the wrong host or port, a timeout that fires before long admin operations complete, or missing KC_PROXY_HEADERS and KC_HOSTNAME settings that cause Keycloak to build malformed redirect URLs and reject the request.
What a 502 actually means
When a browser or API client hits a 502, the instinct is to blame Keycloak. Often the real fault is in how the proxy is configured to talk to it.
The HTTP stack for a proxied Keycloak deployment has two distinct legs:
- Client to proxy — the TLS handshake, the HTTP/2 negotiation, and the initial request land here. If this leg breaks you get a connection error, a 525 (SSL handshake), or a 503, not a 502.
- Proxy to upstream (Keycloak) — the proxy opens a new TCP connection to Keycloak’s backend port, forwards the request, and waits for a response. A 502 means this second leg failed: the upstream either refused the connection, closed it mid-response, or responded with something the proxy cannot interpret.
Understanding which leg is failing narrows the search space immediately. Check the proxy’s error log, not just the access log. nginx logs like connect() failed (111: Connection refused) while connecting to upstream or upstream timed out (110: Connection timed out) pinpoint the leg and the root cause in a single line.
Cause 1 — Keycloak has not finished starting
Keycloak on Quarkus takes 20–60 seconds to boot depending on the number of realms, providers, and available CPU. Reverse proxies register the container as healthy the moment the port binds, which can happen before Keycloak’s HTTP stack is fully accepting requests. During that window, every proxied request produces a 502.
Symptoms: 502s disappear after a minute or two post-restart. The Keycloak container log shows Keycloak 26.x.x on JVM started in ... after the 502s stop.
Fix — use the readiness endpoint, not a TCP probe:
Keycloak 26.x exposes three health endpoints under /health:
| Endpoint | Returns 200 when |
|---|---|
/health/live |
JVM is alive (always true if process is running) |
/health/ready |
Keycloak is fully started and can serve requests |
/health/started |
Startup phase is complete |
Use /health/ready for both Kubernetes readiness probes and load-balancer health checks. /health/live passes too early and defeats the purpose.
Kubernetes startup probe example:
startupProbe:
httpGet:
path: /health/ready
port: 9000
failureThreshold: 30
periodSeconds: 5
readinessProbe:
httpGet:
path: /health/ready
port: 9000
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 3
Note: in Keycloak 26.x the management interface (health, metrics) listens on port 9000 by default, separate from the main HTTP port (8080). Do not health-check port 8080 if you have KC_HTTP_MANAGEMENT_PORT set.
For more on Kubernetes-specific deployment patterns, see the Keycloak Kubernetes production deployment guide.
Cause 2 — Wrong upstream host or port
Keycloak Quarkus listens on:
8080for HTTP8443for HTTPS (when TLS is configured directly on Keycloak)9000for the management interface (health, metrics)
A common mistake after migrating from the legacy WildFly-based Keycloak is pointing the proxy at port 8080 but having configured KC_HTTPS_PORT or KC_HTTP_PORT to a non-default value, or pointing at 8443 when Keycloak is running HTTP-only behind the proxy (which is the recommended setup for proxy-terminated TLS).
Symptoms: nginx logs connect() failed (111: Connection refused). The Keycloak container is running but the proxy cannot reach it.
Fix: Confirm the port Keycloak is actually listening on:
# Inside the container or on the host network
ss -tlnp | grep java
# or
curl -v http://localhost:8080/health/ready
Then align your proxy upstream block:
upstream keycloak {
server keycloak:8080; # HTTP, proxy-terminated TLS
keepalive 32;
}
If you are running Keycloak in Docker Compose, make sure the service name in the upstream matches the Docker Compose service name, not localhost.
Cause 3 — Proxy timeouts are too short
Several Keycloak operations take longer than the default proxy timeout:
- First-time realm export/import via the Admin API
- LDAP full synchronization
- Large token introspection under load
- Admin console operations that trigger cache invalidation across cluster nodes
nginx’s default proxy_read_timeout is 60 seconds. HAProxy’s timeout server default is often set to 30 seconds in starter configs. If a Keycloak operation exceeds these limits, the proxy closes the upstream connection and returns a 502 while Keycloak is still happily processing the request.
Symptoms: 502s on specific long-running Admin API calls or LDAP sync endpoints. The Keycloak log shows the operation completed successfully moments after the 502 hit the client.
Fix for nginx:
location / {
proxy_pass http://keycloak;
proxy_read_timeout 300s; # 5 minutes — covers long admin ops
proxy_send_timeout 300s;
proxy_connect_timeout 10s; # Keep this short — fast fail on connection refused
}
Fix for Traefik (static config):
# traefik.yml
serversTransport:
forwardingTimeouts:
responseHeaderTimeout: 300s
idleConnTimeout: 90s
Or per-router via a middleware:
# Dynamic config
http:
middlewares:
keycloak-timeout:
forwardAuth:
address: "http://keycloak:8080"
routers:
keycloak:
rule: "Host(`auth.example.com`)"
service: keycloak
middlewares:
- keycloak-timeout
services:
keycloak:
loadBalancer:
servers:
- url: "http://keycloak:8080"
responseForwarding:
flushInterval: "1ms"
See the Keycloak Traefik integration guide for a complete Traefik setup including labels for Docker Compose.
Cause 4 — Missing proxy headers and hostname configuration
This is the most misunderstood cause of 502s and related redirect loops. When a reverse proxy terminates TLS and forwards requests over plain HTTP to Keycloak, Keycloak needs to know two things:
- What was the original scheme, host, and port the client used? (So it builds correct redirect URIs in tokens and OpenID Connect metadata)
- What hostname should it claim as its own?
Without this, Keycloak builds redirect URLs using http:// instead of https://, which causes OAuth flows to break, and may also cause Keycloak to reject requests it thinks are cross-origin.
Keycloak 26.x configuration (environment variables):
# Tell Keycloak to trust and read forwarded headers
KC_PROXY_HEADERS=xforwarded # or "forwarded" if your proxy sends RFC 7239 Forwarded header
# Set the public-facing hostname
KC_HOSTNAME=auth.example.com
# If running HTTP-only on the backend (proxy terminates TLS)
KC_HTTP_ENABLED=true
KC_HTTPS_ENABLED=false # or simply omit KC_HTTPS_* config
# Disable strict hostname checking if you need to access Keycloak via internal hostnames too
KC_HOSTNAME_STRICT=false # development only — leave true in production
What KC_PROXY_HEADERS does: It tells Keycloak to read the X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers (for xforwarded) or the RFC 7239 Forwarded header (for forwarded) when building URLs and determining the request scheme. Without this, Keycloak ignores those headers entirely and uses its own perception of the connection, which is HTTP on the backend.
nginx — ensure you are sending the right headers:
location / {
proxy_pass http://keycloak:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_http_version 1.1;
proxy_set_header Connection ""; # Enable keepalives
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
The proxy_buffer_size settings matter: Keycloak responses — especially OpenID Connect discovery documents and token responses with large JWTs — can exceed nginx’s default buffer sizes, causing partial responses that the proxy logs as upstream errors.
For a complete walkthrough of proxy header configuration, see how to run Keycloak behind a reverse proxy.
Cause 5 — TLS mismatch between proxy and Keycloak
If your proxy is configured to connect to the Keycloak upstream over HTTPS (e.g., proxy_pass https://keycloak:8443) but Keycloak is running in HTTP mode, the TLS handshake fails and the proxy returns 502. The reverse is also true: if you set KC_HTTPS_ENABLED=true and point the proxy at port 8080, the proxy gets a TLS ClientHello instead of an HTTP response.
Symptoms: nginx logs SSL_do_handshake() failed or peer closed connection in SSL handshake. The Keycloak log may show nothing because the connection was dropped at the TLS layer.
Recommended approach: Run Keycloak in HTTP mode on the backend and let the proxy terminate TLS. This is simpler to certificate-manage and is the pattern Keycloak’s own documentation recommends for production proxy deployments.
# Keycloak runs HTTP-only on port 8080
KC_HTTP_ENABLED=true
# Proxy handles TLS, Keycloak trusts forwarded proto header
KC_PROXY_HEADERS=xforwarded
KC_HOSTNAME=auth.example.com
If you need end-to-end TLS (proxy to Keycloak), set proxy_ssl_verify off in nginx during initial setup to rule out certificate issues, then add the correct CA cert once connectivity is confirmed.
Cause 6 — Container networking and DNS resolution
In Docker Compose and Kubernetes, a 502 can come from DNS not yet resolving the upstream service name at the time the proxy starts. nginx resolves upstream hostnames at startup by default. If the Keycloak container is not yet up when nginx starts, the DNS lookup fails, nginx marks the upstream as permanently down, and every request gets a 502 until nginx is reloaded.
Fix for nginx in Docker Compose:
Add a resolver directive and use a variable for the upstream address to force per-request DNS resolution:
http {
resolver 127.0.0.11 valid=10s; # Docker's embedded DNS
server {
listen 443 ssl;
server_name auth.example.com;
set $upstream http://keycloak:8080;
location / {
proxy_pass $upstream;
# ... other proxy headers
}
}
}
Fix for Kubernetes: Use a Service name as the upstream, not the Pod IP. Service DNS is stable even when pods restart. If you are using an Ingress controller, ensure the backend Service is in the same namespace or use a fully qualified name (keycloak.auth-namespace.svc.cluster.local).
If you are seeing connection-refused errors rather than 502s, the Keycloak connection refused troubleshooting guide covers port binding and network policy issues in detail.
Symptom-to-cause-to-fix reference
| Symptom | Likely cause | First fix |
|---|---|---|
| 502 for ~60s after pod restart, then clears | Keycloak still starting | Add /health/ready readiness probe |
nginx: connect() failed (111: Connection refused) |
Wrong port or Keycloak not bound | Verify port with ss -tlnp; check KC_HTTP_PORT |
| 502 only on long admin API calls or LDAP sync | Proxy timeout too short | Set proxy_read_timeout 300s in nginx |
502 + redirect loops, http:// in OAuth redirect URIs |
Missing KC_PROXY_HEADERS |
Add KC_PROXY_HEADERS=xforwarded and KC_HOSTNAME |
nginx: SSL_do_handshake() failed |
TLS mismatch proxy↔upstream | Run Keycloak HTTP-only on backend; proxy terminates TLS |
| 502 immediately after deploy, clears on nginx reload | DNS not resolved at startup | Add resolver + variable upstream in nginx |
| 502 only on first request, then fine | Keepalive misconfiguration | Add proxy_http_version 1.1 and Connection "" header |
| 502 with large token responses | Buffer size too small | Increase proxy_buffer_size and proxy_buffers |
Realm-related 502 patterns
If 502s appear only on requests to a specific realm path (e.g., /realms/myrealm/protocol/openid-connect/token) but not others, the cause may not be a proxy issue at all — it may be that the realm does not exist or the hostname mismatch causes Keycloak to return a non-2xx that the proxy treats as an upstream error.
Use curl directly against Keycloak’s backend port to isolate proxy from Keycloak:
curl -v http://keycloak-host:8080/realms/myrealm/.well-known/openid-configuration
If this returns a 404 or 500, the problem is in Keycloak itself, not the proxy. See the Keycloak realm not found troubleshooting guide for realm configuration issues.
Frequently asked questions
Why does Keycloak return 502 behind nginx?
The most common reason is a configuration mismatch between what nginx expects from the upstream and what Keycloak delivers. This includes pointing nginx at the wrong port, setting proxy_read_timeout too low for long operations, or omitting proxy_http_version 1.1, which causes nginx to use HTTP/1.0 by default — a protocol that does not support keepalive connections and can cause upstream connection drops that manifest as 502s.
What proxy headers does Keycloak need?
Keycloak 26.x requires KC_PROXY_HEADERS=xforwarded (or forwarded for RFC 7239) to read forwarded headers from the proxy. Your proxy must send X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host. Without these, Keycloak builds redirect URIs using http:// regardless of the client’s actual scheme, which breaks HTTPS-only OAuth flows and can cause the proxy to see redirect responses it cannot handle.
How do I health-check Keycloak?
Use GET /health/ready on port 9000 (the Keycloak 26.x management interface). This endpoint returns HTTP 200 only when Keycloak has fully started and is ready to handle authentication requests. Avoid /health/live, which returns 200 as soon as the JVM is running — before Keycloak’s HTTP stack is ready. Configure this as your load-balancer health check and Kubernetes readiness probe to prevent traffic from reaching Keycloak before it can serve it.
Can I run Keycloak without a reverse proxy?
For development, yes. For production, a reverse proxy is strongly recommended. Keycloak’s Quarkus distribution does not support zero-downtime restarts on its own — the proxy layer provides connection draining during rolling deployments. The proxy also handles TLS termination, HTTP/2, rate limiting, and Web Application Firewall integration. Running Keycloak directly on port 443 requires root privileges or port forwarding, both of which introduce unnecessary risk.
Why do 502 errors only happen under load?
Under high concurrency, proxy connection pools can be exhausted. If nginx’s worker_connections or upstream keepalive pool is too small, new requests queue until a connection is available. If the queue fills, nginx returns 502. Additionally, Keycloak’s own thread pool (configured via KC_HTTP_POOL_MAX_THREADS) can saturate under load, causing it to queue or reject connections, which the proxy reports as 502. Monitor keycloak_http_server_active_requests via the metrics endpoint to detect Keycloak-side saturation.
Managed Keycloak removes the proxy complexity
Proxy configuration is one of the most operationally demanding parts of a self-hosted Keycloak deployment. Getting KC_PROXY_HEADERS, KC_HOSTNAME, buffer sizes, timeout values, health checks, and TLS settings right — and keeping them right across Keycloak version upgrades — requires sustained attention.
Skycloak’s managed Keycloak hosting handles the full proxy layer, TLS termination, health check configuration, and hostname setup out of the box. Your team connects to a production-ready Keycloak endpoint without managing a single nginx config or Traefik label. If you would rather not debug 502 errors at 2 a.m., see the pricing page for plans that fit teams of any size.
Ready to simplify your authentication?
Deploy production-ready Keycloak in minutes. Unlimited users, flat pricing, no SSO tax.