504 Gateway Timeout: what it means and how to fix it
A proxy in front of the real application gave up waiting for it to respond — not a problem with your own network connection.
A 504 comes from a reverse proxy or load balancer sitting in front of the actual application — not from the application itself. It means the proxy sent your request along to its upstream (the real backend) and never got a response back within the time it was willing to wait. Per RFC 9110 §15.6.5: "the server, while acting as a gateway or proxy, did not receive a timely response from an upstream server it needed to access in attempting to complete the request."
This is almost always the site's problem, not yours — the backend was too slow, not your connection. Reloading occasionally clears a transient spike, but if it persists, there's nothing on your end to fix.
Where exactly it breaks
Client
sends the request
Proxy / load balancer
forwards it, starts its own timer
Backend
still working when the proxy's timer runs out
The proxy's timeout and the backend's own capacity are two separate settings that have to agree with each other. A backend that's simply slow (a heavy query, a downstream API call, GC pauses under load) doesn't have to be down to produce a 504 — it just has to be slower than whatever number the proxy was configured with, even once.
Diagnosing a 504
-
1
Check the proxy's timeout against real backend response times. Is the backend actually finishing just a little too slowly, or not finishing at all? A backend that's 10% over the timeout needs a different fix than one that's stuck.
-
2
Look for backend saturation, not just slowness. A connection pool that's exhausted, a thread pool with every worker busy, or a database with a long lock queue all produce the same symptom from the proxy's point of view: no response in time.
-
3
Check whether it's a tail-latency spike or a hard stall. If p50 is fine but p99 occasionally exceeds the timeout, that's a different problem (and a different fix) than every request stalling — look at your actual latency distribution, not just an average.
-
4
Rule out a rolling-deploy gap. If 504s cluster tightly around deploy times, the proxy may briefly be routing to an instance that's draining connections or not yet ready, not a real capacity problem.
location / {
proxy_pass http://backend;
# default is 60s - if your backend genuinely needs longer for one
# specific route, raise it there, not globally
proxy_read_timeout 60s;
proxy_connect_timeout 5s;
}
Raising this number buys time for a genuinely slow-but-working endpoint. It does nothing for a backend that's actually stuck — that needs the saturation/deadlock investigation above, not a bigger timeout.
A 504 is a real HTTP status code, so an endpoint check with an HTTP status code assertion (expecting 200, or a 2xx range) fails the moment your proxy returns one — no special handling needed, it's evaluated the same as any unexpected status. More usefully: a latency assertion on the same endpoint can flag the backend degrading well before it's slow enough to actually time out, so you find out it's trending toward a 504 instead of finding out once it's already there. And because a single slow response shouldn't page anyone, pairing it with automatic incident detection means a one-off blip doesn't fire an alert — a sustained pattern does.
Frequently asked questions
Is a 504 the same thing as a 502?
No — they're different failure points in the same proxy-to-backend chain. A 502 means the proxy got an invalid or missing response from the backend (often because the backend isn't reachable at all). A 504 means the proxy did reach the backend, but gave up waiting because it took longer than the proxy's own timeout window. One is "couldn't talk to it," the other is "talked to it, but it was too slow."
Why does the error come from the proxy and not my application?
Because the proxy is the thing that actually gave up. Your application might still be working — just slowly, or stuck on something (a slow query, a downstream call, a saturated thread pool) — but the proxy in front of it (nginx, an ALB, Cloudflare, a Caddy reverse_proxy block) has its own timeout, and it returns 504 to the client the moment that timeout is reached, whether or not the backend eventually would have responded.
Should I just raise the proxy's timeout?
Sometimes, but treat it as a last resort, not a fix. Raising the timeout just means users wait longer before seeing the same failure if the underlying slowness is real (a query that shouldn't take 40 seconds still shouldn't take 40 seconds). It's a reasonable stopgap for a known-slow endpoint (a large export, a report generation job) that genuinely needs more time — not a substitute for finding why the backend is slow.
Does HeimPulse have a dedicated 504 alert type?
No — a 504 is a normal HTTP response, so it's caught by whichever assertions you've already configured on the endpoint, most commonly an HTTP status code assertion expecting something like 200 or a 2xx range. There's no special-cased "504 handling"; it's just a status code that fails your expected-status assertion like any other unexpected code would.
Full uptime monitoring feature → · Alert on real patterns, not single blips → · All error explainers →
Catch a slow backend before it's a 504.
Free plan, no credit card, set up in a few minutes.