Intermittent 502/504 OAuth2 Token Errors Behind HAProxy in Kubernetes
Problem Description
In a production GKE data‑pipeline, several microservices acquire an OAuth2 access token from an internal /token endpoint. The endpoint is exposed through a HAProxy Ingress controller. Under normal load the token exchange succeeds, but during rolling updates, high‑load spikes, or after pod eviction the following symptoms appear:
- HTTP 502 Bad Gateway or HTTP 504 Gateway Timeout returned to the client.
- Client logs show errors such as
context deadline exceededornet/http: request canceled. - HAProxy logs contain entries like:
<...> 0/0/30/120/150 504 0 -- 0/0/0/0/0 0/0indicating a server timeout after the connect phase.
- Pipeline jobs abort sporadically, causing data‑loss windows.
These failures are intermittent, affecting only token requests while other API calls continue to work.
Root Cause Analysis
1. HAProxy timeout defaults vs. OAuth2 provider latency
HAProxy’s default timeout server is 50 s (see HAProxy Configuration Manual, “timeout and HTTP keep‑alive settings”). In a multi‑region Kafka connector cluster the OAuth2 provider occasionally needs >50 s to compute a token under load. When the response exceeds timeout server, HAProxy aborts the connection and logs a 504 error.
2. Connection reuse (keep‑alive) and stale backends
HAProxy reuses TCP connections to backend pods by default. If a pod is evicted or not yet ready after a rolling deployment, HAProxy may keep a keep‑alive socket open to the old pod. Subsequent token requests are sent over this stale connection, resulting in “server returned no data or closed connection” (502) as the pod has already terminated. This behavior is described in the community thread “Intermittent 502 on /token endpoint after rolling update”.
3. Health‑check interval too aggressive
The HAProxy Ingress controller’s health checks run every 2 s by default. During a rollout the pod may report UP before the readiness probe passes, so HAProxy continues to route traffic to a pod that is still initializing. The incident “Production data‑pipeline on GKE … health‑checks still reported ‘UP’ while the pod was not ready” illustrates this timing mismatch.
4. Maxconn saturation
A mis‑configured maxconn on the frontend limited concurrent token requests. When the limit was reached, HAProxy returned 502 without contacting the backend, as observed in the CI/CD pipeline case where a specific node hit the limit.
Investigation and Debugging Steps
Log Inspection
# HAProxy log snippet (syslog)
Oct 12 14:23:45 haproxy[1234]: 10.1.2.3:56789 [12/Oct/2026:14:23:45.123] http-in token_backend/token~ token_server/10.244.1.7:8080 0/0/30/120/150 504 0 -- 0/0/0/0/0 0/0
Fields of interest:
- 0/0/30/120/150 – Tq/Tw/Tc/Tr/Tt (queue, wait, connect, response, total). The 150 ms total is far below the observed latency, indicating HAProxy timed out before the backend responded.
- 504 – Server timeout.
Tracing the request path
# Capture the token request from a pod
kubectl exec -it pod/data-consumer-abc -- curl -v -X POST http://haproxy-ingress/token \
-d 'grant_type=client_credentials&client_id=svc&client_secret=***'
Typical output when the failure occurs:
* Trying 10.96.0.10:80...
* Connected to haproxy-ingress (10.96.0.10) port 80 (#0)
> POST /token HTTP/1.1
> Host: haproxy-ingress
> Content-Length: 58
> Content-Type: application/x-www-form-urlencoded
>
* Empty reply from server
* Connection #0 to host haproxy-ingress left intact
curl: (52) Empty reply from server
Backend readiness verification
# Check pod readiness
kubectl get pod -l app=oauth2-token -o wide
kubectl describe pod oauth2-token-5d9f6c7b8f-xyz
# Look for Ready condition and recent restarts
Health‑check behavior
# HAProxy health‑check logs (if enabled)
Oct 12 14:22:30 haproxy[1234]: 10.1.2.3:56789 [12/Oct/2026:14:22:30.001] http-in token_backend/token~ token_server/10.244.1.7:8080 0/0/0/0/0 200 0 -- 0/0/0/0/0 0/0
If the health‑check reports 200 while the pod is still initializing, the check is too permissive.
Solution
1. Align HAProxy timeouts with OAuth2 service latency
Increase timeout server and timeout http-request to accommodate worst‑case token generation time.
# Before (haproxy.cfg)
defaults
timeout connect 5s
timeout client 30s
timeout server 50s
# After
defaults
timeout connect 5s
timeout client 60s
timeout server 120s # double the previous value
timeout http-request 30s
2. Disable keep‑alive for the token endpoint
Force HAProxy to close the connection after each token request, preventing reuse of stale sockets.
# Before
frontend http-in
bind *:80
default_backend token_backend
backend token_backend
server token_server token-service:8080 check
# After (add http-request set-header and option httpclose)
backend token_backend
option httpclose
http-request set-header Connection close
server token_server token-service:8080 check
The option httpclose directive is recommended in the HAProxy “option httpchk” documentation for OAuth2 token endpoints.
3. Harden health‑check timing
Configure a more conservative health‑check interval and use the http-check expect status 200 pattern that also validates the token endpoint’s readiness.
# After (HAProxy Ingress annotations)
haproxy.org/check-interval: "10s"
haproxy.org/check-http: "/token"
haproxy.org/check-http-method: "POST"
haproxy.org/check-http-send: "grant_type=client_credentials&client_id=health&client_secret=health"
haproxy.org/check-http-expect: "status 200"
4. Increase maxconn for the token backend
Set a higher per‑server maxconn to avoid 502 due to connection limits.
# After
backend token_backend
server token_server token-service:8080 check maxconn 2000
5. Deploy rolling update safety net
Use a pre‑stop hook that drains connections from HAProxy before the pod terminates:
# pod spec snippet
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "curl -X POST http://haproxy-ingress/drain?backend=token_backend"]
This ensures HAProxy stops sending new requests to a pod that is about to disappear.
Verification
Functional test
# Simulate token request after changes
for i in {1..20}; do
curl -s -o /dev/null -w "%{http_code}\n" http://haproxy-ingress/token \
-d 'grant_type=client_credentials&client_id=svc&client_secret=***' &
done
wait
Expected output: a stream of 200 status codes with no 502/504.
Metrics validation
- HAProxy
frontend.http_in.responses.2xxshould increase. - Metrics
haproxy_backend_token_backend_server_errors_totalfor 502/504 should drop to zero. - Latency histogram for
/tokenshould stay below the newtimeout server(120 s).
Log sanity check
# Sample log after fix (no timeout entries)
Oct 12 15:01:10 haproxy[1234]: 10.1.2.3:56789 [12/Oct/2026:15:01:10.321] http-in token_backend/token~ token_server/10.244.1.9:8080 0/0/5/30/35 200 0 -- 0/0/0/0/0 0/0
Prevention and Best Practices
- Timeout alignment: Match HAProxy
timeout serverto the 95th‑percentile response time of the OAuth2 service. - Connection hygiene: Disable keep‑alive for short‑lived, state‑changing endpoints (e.g., token acquisition).
- Health‑check fidelity: Use POST health checks that exercise the same code path as production token requests.
- Graceful draining: Integrate pre‑stop hooks or HAProxy
drainAPI calls during rolling updates. - Capacity planning: Monitor
maxconnusage and set headroom for bursty token refresh traffic. - Observability: Export HAProxy error counters to Prometheus and alert on any rise in 502/504 for the token backend.
Related Topic Hub: Distributed Systems Troubleshooting Hub
FAQ
- Why does the token endpoint return 502 only from certain nodes?
Because those nodes kept a keep‑alive connection to a pod that had been evicted. Disabling keep‑alive forces a new TCP handshake per request, eliminating the stale‑socket scenario. - Can increasing
timeout serveralone solve the 504 errors?
It resolves timeouts caused by long token generation, but does not address stale connections or health‑check timing mismatches. All three mitigations are required for a robust fix. - Is it safe to set
option httpcloseglobally?
For high‑throughput, stateless APIs it may add overhead, but for short‑lived token exchanges the performance impact is negligible and the reliability gain outweighs the cost. - How do I verify that HAProxy is no longer reusing connections?
Enableoption httplogand inspect theConnectionheader in the logs. Each request should showConnection: closeand a newServer:pair.: - What alert thresholds should I set for token‑related 502/504 errors?
Alert if the rate of 502/504 for the/tokenpath exceeds 1 % of total token requests over a 5‑minute window, or if the latency exceeds 80 % of the configuredtimeout server.