You're staring at a 5xx in your logs at 2am. Is it a 503 or a 504, and does the difference actually change what you do next? Yes — a lot. 503 means something upstream is deliberately refusing the request right now. 504 means something upstream accepted the request and then never answered in time. That's an availability problem versus a latency problem, and they call for opposite first moves. The annoying part: the exact trigger condition for each is different in nginx, AWS ALB, and Cloudflare, and none of them share the same defaults.
nginx: proxy_read_timeout doesn't measure what you think
nginx fires a 504 when the upstream connects fine but goes quiet for longer than proxy_read_timeout allows. That setting is not a total-response deadline — it's the max gap between two successive reads. A slow-but-steadily-streaming response can run past a minute without tripping it, while a fast backend that just pauses for a few seconds mid-response can trigger it even though the whole request would've finished quickly.
That's why "just raise the timeout" is a symptom fix, not a cure. Per nginx's own docs, the directive is purely gap-between-reads — nothing about it fixes a genuinely slow backend. It just gives that backend more rope before the same failure shows up again, hiding a slow query, exhausted connection pool, or an un-timed downstream call behind a longer wait.
nginx doesn't emit a 503 for a slow upstream at all — only 504. If you're seeing 503s behind nginx, that's almost always the application layer (framework load-shedding) or nginx's own limit_req rate limiting configured to respond 503 instead of dropping the connection.
AWS ALB: 503 is a target-group problem, 504 is a target-speed problem
ALB returns 503 when it has no healthy targets for the target group — every instance failed its health check, a deploy briefly pulled everything out of rotation, or nothing was ever registered. Pure availability problem.
504 means the opposite: a target was reachable and accepted the connection, but didn't respond before ALB's idle timeout (60s default, configurable to 4000s). This is a performance problem on a target ALB still considers healthy — a slow query, an un-timed downstream call, or a big file upload can all pass a fast, separate health-check endpoint while still 504ing on the actual slow request.
Cloudflare: 524 is not a 504
Cloudflare has its own code for "origin never answered": 524. TCP handshake succeeded, Cloudflare sent the request, no HTTP response arrived inside Cloudflare's own window (~100s on most plans, longer on Enterprise). A plain 504 from Cloudflare is rarer and usually means an intermediate hop — a proxy or LB sitting between Cloudflare and your real origin — timed out first.
Cloudflare's 503 is different from the ALB case: it means Cloudflare itself is refusing the request — a WAF rule, a rate limit, occasionally an edge outage. Your origin might not even be involved.
Quick reference
| System | 503 trigger | 504 / equivalent trigger |
|---|---|---|
| nginx |
limit_req rate limiting or app-layer response — never for a slow upstream |
proxy_read_timeout exceeded mid-response |
| AWS ALB | No healthy targets in the target group | Healthy target didn't respond before idle timeout (60s default) |
| Cloudflare | Cloudflare itself blocked/rate-limited the request | 524: origin TCP connected, no HTTP response in time. Plain 504: a hop before the origin timed out |
The fast triage
- Seeing 503s? Check target-group health and recent deploys before touching code.
- Seeing 504s? Time the actual slow operation first — a longer timeout only delays the same failure.
- Seeing Cloudflare 524? Confirm the origin itself is slow, not a proxy sitting in front of it.
- About to raise a timeout as "the fix"? Treat it as a stopgap and go find why the upstream is actually slow.
We went a layer deeper on Cloudflare's edge-specific codes (521–526) in a related breakdown on the WebPixie blog, if that's the layer where your failure's happening.
Top comments (0)