The app behind nginx didn't answer in time. First decide whether the app is legitimately slow (raise proxy_read_timeout) or hanging (fix the app). Blindly raising timeouts just moves the failure later.
Long reports, batch endpoints, AI calls — jobs exceeding the default 60s proxy_read_timeout. The app finishes (logs prove it), nginx already gave up at exactly the timeout boundary.
App never responds at all: DB lock, thread-pool exhaustion, or a handler waiting forever. Raising the timeout changes nothing — the app's own logs show where it stalls.
# app logs: response completed AFTER nginx 504'd -> slow-but-working; nothing ever completes -> hang
# location /reports/ { proxy_read_timeout 300s; } # scoped, not the whole server block
# thread dump / py-spy / slow-query log — the 504 is a symptom; the app owns the bug
# return 202 + job id + polling endpoint — timeouts disappear as a class for work >60s
proxy_read_timeout vs proxy_connect_timeout vs proxy_send_timeout are three different clocks: 110 while reading response header is read-timeout; connect-timeout failures log a different errno. Exact 60-second boundaries in failure timing = default timeout, always. It's the fastest fingerprint in this whole class.
No — nginx is the messenger. The upstream failed to answer in the window. nginx fixes (timeouts) apply only to the slow-but-working variant; hangs need app-side debugging.
Match it to the real endpoint: normal APIs 60s is generous; long-job endpoints get their own scoped value (2-5min). Global 300s values just keep piles of hung workers alive — scope tight.
Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.
Browse the template store →One-time. Yours to modify. Instant download from the NinjaOps template store.