The page hangs. The spinner spins. And after an agonising wait, up comes 504 Gateway Timeout. Where a 502 is abrupt, a 504 is the error of waiting — and that's the biggest clue to what's wrong.
A 504 isn't "your app is dead." It's "your app is too slow." Something upstream took longer than the proxy was willing to wait, so the proxy gave up. Let's find the slowness.
What a 504 actually means
Like a 502, a 504 comes from a proxy or load balancer sitting in front of your app:
Browser ──► Proxy ──► Your app (took too long) ──► ⏱ timeout
The proxy forwarded the request, waited… and waited… and eventually hit its timeout limit before your app responded. So it returns 504 Gateway Timeout.
502 vs 504, the one-liner: 502 = the upstream is dead (no valid response); 504 = the upstream is slow (no response in time). Both come from the proxy, but they point at very different problems.
The usual causes
A 504 almost always traces to something taking too long:
| Cause | What's slow |
|---|---|
| Slow database query | An unindexed or heavy query blocking the request |
| Slow third-party call | An external API your app waits on |
| Overloaded app | Requests queue up faster than they're served |
| Heavy computation | A request doing too much synchronous work |
| Timeout set too low | The proxy's patience is shorter than a legitimately long task |
The most common culprit by far: a slow database query or a slow downstream dependency that your app sits and waits on.
How to diagnose it
- Check your app's response times. A 504 means something crossed the timeout — your latency metrics (especially the p99) will show it.
- Look at the database. Slow-query logs are the number-one source of 504s. Find the query that's crawling.
- Check external calls. Is your app waiting on a third-party API that got slow? Their problem becomes your 504.
- Look for queuing. Under load, requests pile up and each waits longer — even fast work can time out if it's stuck in a queue.
How to fix it
- Speed up the slow thing. Add the missing database index, optimise the query, cache the expensive result. This fixes the root cause.
- Add timeouts to your app. Don't let your app wait forever on a dependency — fail fast with a clear error instead of hanging until the proxy times out.
- Make long work asynchronous. If a task genuinely takes a while (a report, a video encode), don't do it in the request — queue it and return immediately.
- Scale to clear queues. If it's load-driven, more capacity stops requests from backing up.
- Raise the proxy timeout — carefully. Only if the work is legitimately long; otherwise you're just making users wait longer for the same failure.
502 vs 503 vs 504
| Code | Meaning | Shortcut |
|---|---|---|
| 502 Bad Gateway | Invalid/no response from upstream | App is dead |
| 503 Service Unavailable | Up but can't serve now | Overloaded / maintenance |
| 504 Gateway Timeout | Upstream too slow to answer | App is slow |
The bottom line
| In one line | |
|---|---|
| What | The proxy waited for your app and timed out. |
| Meaning | Something upstream is slow, not dead. |
| Where to look | Database queries, third-party calls, p99 latency. |
| Fix | Speed up the slow thing; add app-side timeouts; go async. |
A 504 is your infrastructure telling you "this is taking too long." Don't just bump the timeout and hope — follow it to the slow query or sluggish dependency behind it, and fix that. Your users (and your p99) will thank you.
Related: 502 Bad Gateway, latency percentiles, and response-time monitoring.