Nginx 504 Gateway Timeout: Diagnosing proxy_read_timeout
A 504 from Nginx often means the upstream backend did not respond within proxy_read_timeout. Confirm the timing mismatch before changing the limit.
15 May 2026, 16:59 UTC

Problem and takeaway
A client receives 504 Gateway Timeout from Nginx, and the Nginx error log contains upstream timed out. The useful takeaway is that Nginx stopped waiting for the backend because proxy_read_timeout expired; this does not prove the backend is down. The backend may simply be slower than the configured limit. Confirm the timing mismatch before changing the timeout.
proxy_read_timeout sets how long Nginx waits between successive reads from an upstream server. An upstream is a backend server or group of servers Nginx proxies to. A 504 means Nginx, acting as a gateway, did not get a timely response from that upstream.
Cause and diagnostic table
| Client sees | Nginx error log | Most likely cause | First check |
|---|---|---|---|
| 504 Gateway Timeout | upstream timed out while reading response header from upstream | proxy_read_timeout is shorter than the time the backend needs to respond | Compare proxy_read_timeout to measured backend response time |
| 504 Gateway Timeout | connect() to upstream timed out | Backend is not accepting connections or proxy_connect_timeout is too short | Test backend reachability directly |
| 502 Bad Gateway | upstream sent invalid response | Backend crashed or returned malformed data | Inspect backend application logs |
Ordered checks
- Reproduce and read the error log. Run on the Nginx host as a user with read access to the log directory, typically with
sudo:
Look forsudo tail -n 80 /var/log/nginx/error.logupstream timed outpaired with the request that returned 504. If the log is rotated, checkerror.log.1or the path set byerror_loginnginx.conf. Risk: log entries may belong to an older request if traffic is high. - Find the effective
proxy_read_timeout. Nginx merges directives fromhttp,server, andlocationblocks; the most specific block wins. Dump the running configuration:sudo nginx -T 2>/dev/null | grep -n "proxy_read_timeout"nginx -Tprints the parsed configuration, including files brought in byinclude. If nothing matches, the default is 60 seconds. Permissions: read access to /etc/nginx; usesudoif directories are restricted. Risk: a matching line in an included file can override the one you expect. - Measure the backend directly. Bypass Nginx and time the upstream itself:
Replacecurl -o /dev/null -s -w "HTTP %{http_code} in %{time_total}s\n" http://127.0.0.1:8080/endpoint127.0.0.1:8080and/endpointwith the upstream address and path fromproxy_pass. Run from the Nginx host or another host that can reach the backend. No special permissions are required. Risk: this measures raw backend speed, not Nginx overhead or network hops between Nginx and the backend. - Compare timeout to backend timing. If
time_totalexceeds the effectiveproxy_read_timeout, the timeout is the likely cause. If the backend responds quickly but Nginx still logsupstream timed out, look for network stalls, slow client readback, or keepalive connection reuse issues. - Check for worker or connection saturation.
or usesudo ss -ant | grep ':80' | wc -ltopto inspect Nginx worker CPU. High connection counts can make timeouts look like backend slowness when workers are actually exhausted. Risk: connection count alone does not prove the cause.
Fixes tied to findings
Finding: legitimate backend is slower than the current timeout
Raise proxy_read_timeout in the narrowest block that covers the slow endpoint.
http {
proxy_read_timeout 60s;
server {
listen 80;
server_name example.com;
location /api/report/ {
proxy_pass http://backend_pool;
proxy_read_timeout 180s;
}
}
}
The location block overrides the http default for that path only. Edit the active configuration file, then validate and reload:
sudo nginx -t
sudo systemctl reload nginx
Permissions: write access to the Nginx config directory and root or sudo to reload the service. Risk: a syntax error in nginx -t prevents reload; fix before reloading. Longer timeouts keep worker connections open longer, which can exhaust worker_connections under traffic spikes.
Finding: backend is hung or intermittently failing
Do not only raise the timeout. Fix the backend, and if you have multiple upstream servers, tell Nginx to retry on timeout:
upstream backend_pool {
server 10.0.0.10:8080;
server 10.0.0.11:8080;
}
server {
location /api/ {
proxy_pass http://backend_pool;
proxy_read_timeout 60s;
proxy_next_upstream timeout;
}
}
proxy_next_upstream timeout allows Nginx to forward the request to the next upstream when the first one times out, instead of immediately returning 504. Open-source Nginx applies this passively based on failed connections; active health checks require Nginx Plus.
Finding: the client gives up before Nginx does
A load balancer, CDN, or browser may close the connection and show its own 504 before Nginx reaches proxy_read_timeout. Check the outermost gateway's timeout setting as well.
Rollback
Because editing Nginx configuration changes runtime state, keep a backup before editing:
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
If the new timeout causes problems, restore the backup, run sudo nginx -t, and reload:
sudo cp /etc/nginx/nginx.conf.bak /etc/nginx/nginx.conf
sudo nginx -t
sudo systemctl reload nginx
Escalation criteria
- Backend response time keeps growing even after the timeout is raised, indicating an application or database problem rather than a configuration issue.
- The log shows
connect() timed out, pointing to network, firewall, or backend process failure. - Nginx worker CPU or memory approaches saturation after the timeout increase.
- You need active health checks or automatic removal of slow backends, which requires Nginx Plus or an external load balancer.
Limitations and verification
proxy_read_timeout measures the interval between two successive reads from the upstream, not the total request duration. A backend that streams data slowly may never trigger this timeout while still taking a long time overall.
HTTP/2, gRPC, and client-side keepalive can change timeout behavior. Cloud load balancers in front of Nginx often have independent timeouts that return 504 before Nginx logs anything.
Practical check after the change: reproduce the slow request, confirm the access log shows 200 instead of 504, confirm no new upstream timed out entries appear in the error log, and watch connection counts with ss during a traffic spike.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.