Debug guide · you're seeing
504 Gateway Time-outupstream timed out (110: Connection timed out) while reading response header from upstreamupstream timed out (110: Connection timed out) while connecting to upstreamupstream timed out (110: Connection timed out) while sending request to upstream
nginx 504 Gateway Timeout: Fix Upstream Timed Out (110)
Fix nginx 504 Gateway Time-out and 'upstream timed out (110)': proxy_read_timeout, fastcgi_read_timeout, ALB idle timeout, and Cloudflare 524 compared.
On this page
TL;DR: nginx forwarded the request to the upstream and gave up waiting (all three
proxy_*_timeoutdirectives default to 60s). Theupstream timed out (110: Connection timed out)error-log line names the phase: connecting, sending or reading. Fix the slow upstream first; raiseproxy_read_timeout(orfastcgi_read_timeout) only for bounded slow work, and raise every timeout in front of nginx too.
What it means
A 504 is the gateway telling the client that it did not get a timely response from the server behind it. In nginx the browser sees:
HTTP/1.1 504 Gateway Time-out
Server: nginx/1.27.0
Content-Type: text/html
2026/10/04 10:03:17 [error] 31#31: *482 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.10, server: example.com, request: "GET /report HTTP/1.1", upstream: "http://127.0.0.1:3000/report", host: "example.com"
The tail of the upstream timed out line identifies the phase, and each phase has its own directive. All three default to 60 seconds:
| Log says | Directive | What it measures |
|---|---|---|
while connecting to upstream | proxy_connect_timeout | Time to establish the TCP connection. Raising it rarely helps; an upstream that does not accept a connection in a few seconds is down or unreachable. |
while sending request to upstream | proxy_send_timeout | Maximum gap between two writes of the request to the upstream. Matters for large uploads to a slow upstream. |
while reading response header from upstream | proxy_read_timeout | Maximum gap between two reads from the upstream. Usually the culprit: the app is still working and has sent nothing. |
For PHP-FPM, uWSGI, and gRPC upstreams the equivalents are fastcgi_*_timeout, uwsgi_*_timeout and grpc_*_timeout.
Who sent it?
Not every 504 comes from nginx. Look at the failing response:
Server: nginxand the page title504 Gateway Time-out: nginx on your host or the ingress controller. Confirm with theupstream timed outlog line with the matching timestamp.Server: awselb/2.0: an AWS Application Load Balancer. It returns 504 when a target does not respond before the idle timeout (60 seconds by default) or when it cannot establish a connection. It logs the reason in access logs undererror_reason.X-Cache: Error from cloudfrontandVia: ... (CloudFront): CloudFront gave up on the origin (origin response timeout, 30 seconds by default).Server: cloudflarewith a Cloudflare-branded page: look at the number. “Error 504” is Cloudflare relaying a gateway timeout; “Error 524: A timeout occurred” means the origin accepted the connection but did not answer in time (125 seconds by default). “Error 522” means the connection to the origin timed out.- JSON or framework-styled body: an application server or API gateway is generating the 504 itself.
Compare timings. A 504 that always arrives after about 60 seconds points at a default nginx or ALB timeout. One that arrives after 30 seconds points at CloudFront, many API gateways or a Gunicorn worker timeout. One that arrives at about 125 seconds points at Cloudflare (older guides say 100; Cloudflare’s current docs say 125).
Fix it, in order of likelihood
- Find out why the upstream is slow. Check the upstream’s own logs for the same request, and time it directly:
curl -w '%{time_starttransfer}\n' -o /dev/null -s http://127.0.0.1:3000/report. If the upstream is healthy but nginx times out, you have a configuration problem; if the upstream is slow, no nginx setting is the real fix. - Look at the phase in the error log, then raise the matching timeout in the narrowest
location. - Raise the limits of every proxy in front: ALB idle timeout, CloudFront origin response timeout, Cloudflare plan limits.
- Raise the limits of the application server so it does not kill the worker first: Gunicorn
--timeout(30 seconds by default), PHP-FPMrequest_terminate_timeout, PHPmax_execution_time. - For work that takes more than a minute or so, switch to an asynchronous API.
nginx
location /report {
proxy_pass http://app;
proxy_connect_timeout 5s; # fail fast if the upstream is down
proxy_send_timeout 60s;
proxy_read_timeout 180s; # only for this slow endpoint, not the whole server
# Do not retry a slow request on another upstream; it doubles the load and the wait
proxy_next_upstream off;
}
proxy_next_upstream defaults to error timeout. With several upstreams, a timeout is retried on the next one, so a single slow request can occupy every backend and the client waits for the sum of the timeouts. For non-idempotent methods nginx does not retry by default unless you add non_idempotent.
PHP-FPM
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_read_timeout 180s; # default 60s
}
; php-fpm pool config
request_terminate_timeout = 180s
; php.ini
max_execution_time = 180
If PHP is terminated before nginx times out, the symptom flips to a 502 (upstream prematurely closed connection). Keep nginx’s timeout slightly higher than PHP’s.
Kubernetes ingress-nginx
Annotations take plain seconds as strings. The ConfigMap defaults are 5 for connect and 60 for send and read:
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: "5"
nginx.ingress.kubernetes.io/proxy-send-timeout: "180"
nginx.ingress.kubernetes.io/proxy-read-timeout: "180"
AWS Application Load Balancer
Set the idle timeout on the load balancer attributes (default 60 seconds). It limits the time a connection may stay idle in either direction, so a slow request with no bytes flowing hits it exactly like nginx’s read timeout does:
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn "$ALB_ARN" \
--attributes Key=idle_timeout.timeout_seconds,Value=180
If nginx sits behind the ALB, make nginx’s proxy_read_timeout at least as long, otherwise nginx is the first to time out and you will see nginx-style 504s instead.
Cloudflare
Cloudflare’s Proxy Read Timeout is 125 seconds by default (error 524). Only Enterprise customers can raise it, up to 6,000 seconds. Beyond the limit the visitor gets 524 regardless of your origin settings. If a request genuinely needs to be longer, take that route out of the proxied path (a grey-clouded DNS-only hostname for the API), or restructure the work into a job: respond 202 Accepted with a Location of a status resource and let the client poll.
The 202 pattern
POST /reports HTTP/1.1
HTTP/1.1 202 Accepted
Location: /reports/8f3a
Retry-After: 5
GET /reports/8f3a HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"running"}
Reproduce and verify
Create a deliberately slow endpoint or use a sleep in a test upstream, then time the request through each layer. Compare the elapsed times to see which one cuts the request:
# Through the public URL
curl -sS -o /dev/null -w 'status=%{http_code} total=%{time_total}s\n' https://example.com/report
# Straight to the origin, skipping the CDN
curl -sS -o /dev/null -w 'status=%{http_code} total=%{time_total}s\n' \
-H 'Host: example.com' http://ORIGIN_IP/report
# Straight to the app, skipping nginx
curl -sS -o /dev/null -w 'status=%{http_code} total=%{time_total}s\n' http://127.0.0.1:3000/report
A 504 at total=60.0xx from the first two and a success from the third means the nginx or ALB 60 second default is cutting you off. Watch the error log live while you retry with tail -f /var/log/nginx/error.log.
Related
- 504 Gateway Timeout describes the status and how it differs from a client-side timeout.
- 524 A Timeout Occurred is Cloudflare’s version, with a fixed 125 second default budget.
- 502 Bad Gateway is the other half of nginx upstream failures: the upstream answered badly or closed the connection.
- 503 Service Unavailable is what an overloaded or draining upstream should answer instead of hanging.
- ERR_INCOMPLETE_CHUNKED_ENCODING: the timeout hits after the response has started, so the browser sees a cut-off body instead of a 504.
Frequently asked questions
What is the default proxy_read_timeout in nginx?
It is 60 seconds, and so are proxy_connect_timeout and proxy_send_timeout. proxy_read_timeout is not a total request budget: it is the longest nginx will wait between two successive reads from the upstream, so a response that trickles data every few seconds can run far longer than 60 seconds.
Which timeout do I change for PHP-FPM?
With fastcgi_pass use fastcgi_read_timeout (default 60s), not proxy_read_timeout. Also raise PHP-FPM request_terminate_timeout and PHP max_execution_time, otherwise the worker is killed at its own limit and you get a 502 instead of a longer wait.
Why does increasing proxy_read_timeout not stop my 504?
Something in front of nginx has a shorter limit. An AWS Application Load Balancer defaults to a 60 second idle timeout, CloudFront to a 30 second origin response timeout, and Cloudflare gives up after 125 seconds unless an Enterprise zone raised it. The 504 you see may not be generated by nginx at all; check the Server header of the failing response.
What is the difference between a 504 and Cloudflare 524?
A 504 means the gateway gave up waiting for the upstream. Cloudflare 524 means Cloudflare connected to your origin but the origin did not return an HTTP response within the Proxy Read Timeout, 125 seconds by default (up to 6,000 on Enterprise). Cloudflare returns 522 when it cannot even complete the TCP connection.
Is raising the timeout the right fix?
Only for work that is legitimately slow but bounded. If a request regularly needs minutes, return 202 Accepted with a status URL and do the work in a background job, or stream progress. Longer timeouts hold connections and worker slots open, which lets a slow dependency take the whole service down.
Sources
- nginx: ngx_http_proxy_module proxy_read_timeoutnginx.org
- nginx: ngx_http_fastcgi_module fastcgi_read_timeoutnginx.org
- MDN: 504 Gateway Timeoutdeveloper.mozilla.org
- RFC 9110 Section 15.6.5: 504 Gateway Timeoutrfc-editor.org
- AWS Docs: Idle timeout for your Application Load Balancerdocs.aws.amazon.com
- Cloudflare Docs: Error 524developers.cloudflare.com
Related
nginx 502 Bad Gateway: Causes and Fixes by Error Log
Fix nginx 502 Bad Gateway by matching the error log: connection refused, prematurely closed connection, php-fpm socket permissions, too big header, keepalive.
HTTP 502 Bad Gateway: nginx, ALB and Cloudflare Fixes
Fix 502 Bad Gateway: decode nginx error-log lines, php-fpm sockets, ALB keep-alive mismatches and Cloudflare 502 vs 52x, with curl checks.
524 A Timeout Occurred
Cloudflare-specific error when the origin server takes too long to respond. Learn how to diagnose and fix 524 timeout errors.
504 Gateway Timeout: nginx, ALB and Cloudflare Fixes
Fix 504 Gateway Timeout: nginx proxy_read_timeout (60s default), ALB 60s idle, API Gateway 29s, Cloudflare 524 at 125s, with error-log strings and curl timing.