This line in the nginx error log means nginx forwarded a request to a backend (PHP-FPM, Node, Gunicorn, another web server) and the backend did not answer within the configured timeout, so nginx gave up. The visitor receives a 504 Gateway Timeout. The log line is more useful than the 504 because it tells you which stage timed out.
You find it in /var/log/nginx/error.log:
2026/10/05 10:12:01 [error] 1234#1234: *5678 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.9, server: example.com, request: "POST /checkout HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock", host: "example.com"
For the general 504 picture, see Fix 504 Gateway Timeout. This page focuses on reading the log line and setting the right directive.
Read the phrase after "while"
The text after "while" says which phase timed out, and each phase has its own directive:
| Log phrase | Meaning | Directive (default) |
|---|---|---|
while connecting to upstream | Could not establish the connection in time | proxy_connect_timeout / fastcgi_connect_timeout (60s) |
while reading response header from upstream | Connected, but no response headers arrived | proxy_read_timeout / fastcgi_read_timeout (60s) |
while reading upstream | Headers arrived, then the body stalled | same read timeout |
while sending request to upstream | Backend stopped accepting the request | proxy_send_timeout / fastcgi_send_timeout (60s) |
Read timeouts are measured between two successive reads, not over the whole response. A backend that sends a byte every 30 seconds will not trip a 60 second read timeout.
Causes, most likely first
- A slow request in the backend. A heavy report, an unindexed database query, a slow external API call or a large export takes longer than 60 seconds.
- Backend overloaded or out of workers. All PHP-FPM children are busy and requests queue until nginx times out.
- Backend hung or deadlocked, often waiting on a database lock or an external service.
- Timeout too low for a legitimate long job such as an import.
- Connect-phase problems: a firewall dropping packets to a remote upstream, or a wrong address.
Fix 1: find out why the backend is slow first
Raising timeouts hides slowness. Check the backend:
# PHP-FPM: slow log and status
sudo grep -E "pm.max_children|request_slowlog_timeout|slowlog" /etc/php/8.2/fpm/pool.d/www.conf
sudo tail -n 50 /var/log/php8.2-fpm.log # look for "server reached pm.max_children"
Enable the slow log in the pool config to get a stack trace of requests running longer than a threshold:
request_slowlog_timeout = 10s
slowlog = /var/log/php8.2-fpm.slow.log
If the log says the server reached pm.max_children, raise it only as far as your RAM allows (average process memory times max children must fit), and fix the slow code that holds workers.
Fix 2: raise the right timeout for the right location
Apply long timeouts only to the endpoint that needs them, not globally.
PHP-FPM (fastcgi):
location = /admin/export.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_read_timeout 300s;
}
Reverse proxy to Node, Gunicorn, another server:
location /api/reports/ {
proxy_pass http://127.0.0.1:3000;
proxy_read_timeout 300s;
proxy_connect_timeout 5s;
}
uWSGI: uwsgi_read_timeout 300s;
Then test and reload:
sudo nginx -t && sudo systemctl reload nginx
Check those too:
php -i | grep max_execution_time
grep -n request_terminate_timeout /etc/php/8.2/fpm/pool.d/www.conf
Fix 3: move long work out of the request
For anything that routinely takes over a minute, return quickly and process in the background: put the job on a queue, return 202 Accepted with a status URL, and let the browser poll. Exports and imports are the classic cases.
Fix 4: for connect-phase timeouts
If the log says "while connecting to upstream", test the path to the backend:
curl -v --max-time 5 http://10.0.0.8:3000/health
Check firewall rules between hosts and security groups, and use the Port Checker to confirm a public port is reachable. If the log says "connection refused (111)" instead, that is a different problem, see nginx connect() failed (111). Keep proxy_connect_timeout short, a few seconds, so failures surface quickly.
How to verify
Repeat the slow request with timing:
curl -o /dev/null -s -w "status=%{http_code} time=%{time_total}s\n" https://example.com/admin/export.php
Then check the error log has no new upstream timed out lines (sudo tail -f /var/log/nginx/error.log). Use the HTTP Status Checker and Website Speed Test from outside your network, and see nginx 502 and 504 timeout tuning for the full tuning picture.
Next steps
- Measure response time with the Website Speed Test.
- Confirm the status code with the HTTP Status Checker.
- Read Fix 504 Gateway Timeout.
- Track recurrence with an Uptime Monitor.
FAQ
Why does the client see 504 and not the log message?
nginx converts upstream timeouts into a 504 response for the visitor, while the precise cause goes only to the error log.
Is the default timeout 60 seconds everywhere?
For the nginx proxy, FastCGI, uWSGI and SCGI read, send and connect directives the documented default is 60 seconds. The backend, a CDN or a load balancer in front may have different limits, so check each hop.
Will a bigger timeout fix it permanently?
Only if the work is legitimately long. If a request normally takes 2 seconds and now takes 70, a longer timeout hides a real performance problem.
Does Cloudflare time out earlier?
Cloudflare has its own proxy read timeout, 100 seconds by default, after which it shows a 524. See Cloudflare 520 to 526 errors.
Get notified when this fix updates
Plus occasional practical tips on uptime and monitoring, nothing else. Unsubscribe anytime.
This page is general information written by our editorial team and updated when documentation changes. Servers and providers differ, so test changes safely first. See our editorial policy and disclaimer.