HTTP 504 is Gateway Timeout: a server acting as a gateway or proxy didn’t get a timely response from the server behind it (RFC 9110 section 15.6.5). Something in the chain gave up waiting. The upstream may still be working on the request, and may finish it.
Which timeout fired
Every layer has a default, and the 504 comes from the first one to run out:
nginx proxy_read_timeout 60s between two reads from the upstream
AWS ALB idle timeout 60s
CloudFront origin response timeout 30s
API Gateway REST integration 29s "Endpoint request timed out"
ingress-nginx proxy-read-timeout 60s
nginx logs upstream timed out (110: Connection timed out) while reading response header from upstream. Raising one timeout helps only if every layer in front of it allows at least as long: nginx at 120 seconds behind an ALB at 60 still gets cut off at 60, and the ALB sends the 504.
504, 502 or 408
the upstream didn't answer in time 504 Gateway Timeout
the upstream answered badly or hung up 502 Bad Gateway
the client was too slow sending 408 Request Timeout
408 (RFC 9110 section 15.5.9) is a server giving up on a client that didn’t finish sending its request; 504 is a proxy giving up on a server. Go’s httputil.ReverseProxy blurs the line: its default error handler answers 502 for timeouts too. Search 5xx timeout in the tool below: it lists 504 and Cloudflare’s unofficial 524, and leaves 408 out because it’s a 4xx.
What the client should do
Don’t assume nothing happened. A POST that timed out at the gateway may have charged the card anyway. Retry idempotent requests with backoff; for the rest, send an idempotency key or check the result before trying again. If the work honestly takes minutes, stop raising timeouts: accept it with 202, return a status URL, and let the client poll.
Caching and search
504 isn’t heuristically cacheable, though CloudFront caches it for 10 seconds by default. Googlebot slows down when it sees 5xx errors, and pages that keep timing out drop out of the index.
In your stack
location /reports/ {
proxy_pass http://reports:8080;
proxy_connect_timeout 5s;
proxy_read_timeout 120s; # raise the load balancer's idle timeout above this too
}
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
w.WriteHeader(http.StatusGatewayTimeout) // ReverseProxy would send 502
return
}
w.WriteHeader(http.StatusBadGateway)
}