Matt Scott

504 vs 503: what actually triggers each in nginx, Cloudflare, and ALB

503 means a server is deliberately not serving right now. 504 means something upstream never answered in time. The trigger conditions for each are different in nginx, AWS ALB, and Cloudflare, and none of them share the same defaults.

503 means a server is up and reachable but deliberately refusing to serve a request right now, an overload, a maintenance window, a health check that failed. 504 means something upstream never responded in time. The distinction sounds simple until you ask which specific condition in nginx, AWS ALB, or Cloudflare triggers each one, and the answer is different for all three.

nginx: proxy_read_timeout does not measure what people assume

nginx returns a 504 when the upstream connects successfully but stops sending data for longer than proxy_read_timeout allows. The setting is not a total-response deadline, it is the maximum gap between two successive reads from the upstream. A slow-but-steadily-streaming response can run past a minute without tripping it, while a fast backend that pauses for a few seconds mid-response can trigger it even though the total request would have finished quickly.

This distinction matters because the common fix, raising the timeout, treats the symptom. nginx's own documentation defines the directive purely as the gap-between-reads, nothing in the spec says a longer value fixes a genuinely slow backend, it only gives that backend more room to eventually respond while hiding a real problem (a slow query, connection pool exhaustion, a downstream call with no timeout of its own) behind a longer wait before the same failure shows up. nginx does not return a 503 for a slow upstream at all, only 504. A 503 from an nginx-fronted stack almost always comes from the application layer behind it (a framework returning 503 under its own load-shedding logic) or from nginx itself refusing a connection under limit_req rate limiting configured to respond with 503 rather than drop the connection.

AWS ALB: 503 is a target-group problem, 504 is a target-speed problem

An Application Load Balancer returns a 503 when it has no healthy targets registered for the request's target group, every instance failed its health check, a deployment briefly took every target out of rotation, or nothing was ever registered. This is an availability problem: the infrastructure is telling you it has nowhere to send the request.

A 504 from an ALB means the opposite: a target was reachable and accepted the connection, but did not send a response before the load balancer's idle timeout expired, 60 seconds by default, configurable up to 4000. This is a performance problem on a target that is otherwise considered healthy. A backend doing a slow database query, waiting on a downstream API with no timeout, or processing a large file upload can pass its health check (a fast, separate endpoint) while still hitting a 504 on the request that actually does the slow work. Raising the idle timeout buys time the same way nginx's proxy_read_timeout does, without addressing why the target is slow in the first place.

Cloudflare: 524 is not a 504

Cloudflare has its own dedicated code for the specific case of an origin that never answered: 524. The TCP handshake to the origin succeeded and Cloudflare sent the request, but no HTTP response arrived within Cloudflare's own timeout window, around 100 seconds on most plans, longer on Enterprise. A standard 504 from Cloudflare is rarer and usually means the timeout happened at an intermediate hop, a proxy or load balancer sitting between Cloudflare and the true origin, giving up before Cloudflare's own window expired.

Cloudflare returns a 503 when Cloudflare itself, not the origin, is the one refusing the request: an active WAF rule block, a rate limit, or (rarely) a Cloudflare-side outage. The origin server is not necessarily involved in a Cloudflare 503 at all, which is the opposite of the ALB case above, where a 503 is entirely about the origin's health.

Quick reference

System 503 trigger 504 / equivalent trigger
nginx Rate limiting (limit_req) or an app-layer response; not returned for a slow upstream proxy_read_timeout exceeded, upstream stopped sending data mid-response
AWS ALB No healthy targets in the target group A healthy target did not respond before the idle timeout (default 60s)
Cloudflare Cloudflare itself blocked or rate-limited the request (WAF, rate limit, edge outage) 524: origin TCP connected but sent no HTTP response in time. Standard 504: a hop between Cloudflare and the true origin timed out first

Diagnosing which one you actually have

A 503 points at availability: something upstream (a target group, a rate limiter, a WAF rule) is deliberately not passing the request through. Check health-check status and recent deployments before looking at code.

A 504 points at latency: something upstream accepted the request and then took too long. Check the slow endpoint's own timing, not the proxy config first, a longer timeout only delays the same failure if the underlying operation genuinely needs to finish faster.

On WebPixie, an uptime monitor treats an unexpected status code as a confirmed failure once it matches the response rules configured for that check, so a 503 and a 504 both open an incident, but distinguishing which one occurred still comes down to reading the code itself. The related breakdown of Cloudflare's edge-specific codes, What Cloudflare's 5xx error codes actually mean, covers 521 through 526 in more depth if the failure is happening at Cloudflare's edge rather than at nginx or an ALB.

Fix-first checklist

  • 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 failure
  • Seeing Cloudflare 524: confirm the origin itself is slow, not a proxy sitting between Cloudflare and the origin
  • Raising a timeout as a fix: treat it as a stopgap, not a resolution, and go find why the upstream is slow