When nginx returns 502 or 504, it is telling you that the application behind it didn't answer correctly or in time. Raising timeouts blindly makes some 504s go away and hides the actual problem. Tuning works best when you read the error log first, pick the directive that matches the log line, and change one thing at a time. This guide maps log messages to the correct nginx directives and shows configuration that works for reverse-proxied apps (Node, Gunicorn, Puma) and PHP-FPM.
For the overall diagnosis of 502 across layers, see fix 502 bad gateway and fix 504 gateway timeout. This page is the nginx-specific tuning companion.
502 vs 504 in nginx terms
- 502 Bad Gateway: nginx could not get a valid response. The upstream refused the connection, reset it, returned malformed data, or the socket was missing.
- 504 Gateway Timeout: nginx waited and gave up. The upstream accepted the connection but did not respond within the time limit.
The error log distinguishes them. Find it:
sudo tail -n 100 /var/log/nginx/error.log
sudo nginx -T 2>/dev/null | grep -E "error_log|access_log" # where logs are configured
Match the log line to the fix
| Error log message | Meaning | Where to look |
|---|---|---|
connect() failed (111: Connection refused) while connecting to upstream | Nothing listening at that address | App crashed or not started, wrong port |
connect() to unix:/run/php/php8.2-fpm.sock failed (2: No such file or directory) | Socket path wrong or service down | PHP-FPM status, fastcgi_pass path |
connect() to unix:... failed (13: Permission denied) | nginx user cannot access socket | Socket owner and mode |
upstream timed out (110: Connection timed out) while reading response header from upstream | App too slow | proxy_read_timeout or fastcgi_read_timeout, and app performance |
upstream timed out (110: Connection timed out) while connecting to upstream | Cannot establish connection in time | proxy_connect_timeout, backlog, firewall |
recv() failed (104: Connection reset by peer) while reading response header from upstream | Upstream closed the connection abruptly | App crash, worker killed, keepalive mismatch |
upstream prematurely closed connection while reading response header from upstream | Upstream died mid-response | App logs, memory kills, request timeouts at app |
upstream sent too big header while reading response header from upstream | Response headers exceed buffer | proxy_buffer_size or fastcgi_buffer_size |
no live upstreams while connecting to upstream | All upstream servers marked failed | max_fails, fail_timeout, backend health |
Check which one you have before changing anything. The Port Checker can test that the upstream port is open if it is reachable from outside, and ss -ltnp tells you locally.
sudo ss -ltnp | grep -E ':(3000|8000|8080|9000)\b'
ls -l /run/php/
systemctl status php8.2-fpm --no-pager
Timeouts for proxied applications
For proxy_pass upstreams, three timeouts matter. All three default to 60 seconds in nginx.
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 5s; # time to establish the TCP connection
proxy_send_timeout 60s; # max gap between two writes of the request to upstream
proxy_read_timeout 60s; # max gap between two reads of the response from upstream
}
Key facts that people get wrong:
proxy_read_timeoutis the time between two successive reads, not the total response time. A streaming response that sends data regularly never trips it. A response that stays silent longer than the limit does.- Setting
proxy_connect_timeoutlow (a few seconds) is a good idea. If the upstream is down, you want to fail fast and try another server, rather than hold the visitor for a minute. nginx documents that this timeout usually can't exceed 75 seconds. - For a slow endpoint like an export or report, raise the timeout for that location only:
location /reports/ {
proxy_pass http://127.0.0.1:3000;
proxy_read_timeout 300s;
}
A better answer for work that takes minutes is a background job and a polling or webhook pattern, not a longer HTTP wait.
Timeouts for PHP-FPM (FastCGI)
For PHP, the equivalents use the fastcgi_ prefix.
location ~ \.php$ {
include snippets/fastcgi-php.conf; # Debian/Ubuntu; on other distros include fastcgi_params
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_connect_timeout 5s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s; # default 60s; raise only where needed
}
Raising fastcgi_read_timeout is only half the story. PHP has its own limits:
max_execution_timeinphp.ini(default 30 seconds for web requests) stops scripts first.- In the PHP-FPM pool file (for example
/etc/php/8.2/fpm/pool.d/www.conf),request_terminate_timeoutcan kill a request after a set time.
If these are lower than nginx's timeout, you get a PHP error or a 502/500 before nginx times out. Align them: for a 5-minute endpoint, raise all three, for that path only if you can.
A very common 502/504 cause on PHP sites is worker exhaustion. The PHP-FPM log says something like server reached pm.max_children setting (5), consider raising it. All workers are busy, new requests queue, and nginx times out. Check:
sudo grep -i "max_children" /var/log/php8.2-fpm.log
sudo systemctl status php8.2-fpm --no-pager | head
Raising pm.max_children helps only if the server has the memory for more workers. Estimate with ps --no-headers -o rss -C php-fpm8.2 | awk '{s+=$1} END {print s/NR/1024 " MB avg"}' and divide spare RAM by that average. Setting it higher than RAM allows causes the kernel to kill processes, which causes more 502s. See also WordPress memory limit exhausted fix for related limits.
Buffer tuning
nginx buffers the response from the upstream. Two situations call for tuning.
"upstream sent too big header"
The first part of the response, the status line and headers, must fit in one buffer. Large cookies, long Set-Cookie headers, or many Link headers can overflow it. This produces a 502.
# proxy_pass upstreams
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
# FastCGI upstreams
fastcgi_buffer_size 16k;
fastcgi_buffers 16 16k;
fastcgi_busy_buffers_size 32k;
The default buffer size is one memory page (4k or 8k depending on platform). Increase *_buffer_size first, and if you raise *_busy_buffers_size keep it at least as large as the buffer size and smaller than the total of the buffers minus one. Fixing the cause, usually an oversized cookie, is better than only enlarging buffers.
For server-sent events or streaming, turn buffering off for that location with proxy_buffering off;.
Upstream keepalive
By default nginx opens a new connection to the upstream for each request. For high request rates, that costs time and can exhaust local ports. Keepalive reuses connections, and it requires three settings together:
upstream app {
server 127.0.0.1:3000;
keepalive 32; # idle connections kept per worker
keepalive_timeout 60s; # idle time before closing (nginx 1.15.3 and later)
keepalive_requests 1000; # requests per connection (1.15.3 and later)
}
server {
location / {
proxy_pass http://app;
proxy_http_version 1.1; # required for keepalive
proxy_set_header Connection ""; # clear the default "close"
}
}
The proxy_http_version 1.1 and the empty Connection header are the parts people forget, and without them keepalive silently does nothing.
A known source of intermittent 502s is a keepalive mismatch: the application closes idle connections sooner than nginx expects. nginx sends a request on a connection the upstream has just closed and logs recv() failed (104: Connection reset by peer) or upstream prematurely closed connection. The fix is to make the upstream's idle timeout longer than nginx's keepalive_timeout. For Node's HTTP server, that means raising server.keepAliveTimeout above the nginx value. Check your application server's documentation for its equivalent setting.
For FastCGI, use keepalive in the upstream plus fastcgi_keep_conn on;.
Retries and failover
If you have more than one upstream server, tell nginx when to try the next one:
proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 10s;
By default nginx does not retry non-idempotent requests (such as POST) after they've been sent, which protects against duplicate orders. Keep it that way. See load balancer health checks for max_fails and fail_timeout.
Test and apply safely
Always test the config, then reload instead of restarting, because reload keeps serving existing connections.
sudo nginx -t
sudo systemctl reload nginx
curl -sI https://example.com/ | head -n 5
Confirm which settings are really active with sudo nginx -T | grep -n proxy_read_timeout, since an include file may override your edit. After the change, measure rather than assume: replay the slow request and watch the access log. If you log upstream timing, you can see where time goes:
log_format timing '$remote_addr "$request" $status '
'rt=$request_time urt=$upstream_response_time uct=$upstream_connect_time';
access_log /var/log/nginx/access.log timing;
urt (upstream response time) near your timeout value shows the app is the bottleneck, not nginx.
Don't forget the layer in front of nginx
If a CDN or another proxy sits in front, its timeouts apply too. Cloudflare, for example, returns a 524 when the origin takes longer than its limit (100 seconds by default, per their documentation), regardless of what nginx allows. See Cloudflare 520-526 errors explained. Check the full chain with the HTTP Header Checker, and monitor with the Uptime Monitor, which can alert you when response times climb before errors begin.
Next steps
- Identify the status your site returns with the HTTP Status Checker.
- Confirm the upstream port with the Port Checker.
- Compare web servers in Apache vs nginx troubleshooting.
- Read fix 504 gateway timeout for the full 504 workflow.
- Get alerted on slow responses with the Uptime Monitor.
FAQ
What is the default proxy_read_timeout in nginx?
60 seconds. It is the maximum time between two successive reads from the upstream, not the total time of the response.
Should I just set all timeouts to a very large value?
No. Long timeouts tie up workers and make a failing backend slow down everything. Raise timeouts only for the specific slow paths and fix the underlying slowness.
Why do I get 502 only occasionally under load?
Common causes are worker exhaustion (for example PHP-FPM pm.max_children), upstream keepalive timeouts shorter than nginx's, or the application being killed for memory. The error log line tells you which.
Do I need to restart nginx after changing these directives?
No. Run nginx -t to validate, then systemctl reload nginx. A reload applies the new configuration without dropping active connections.
Get notified when this guide 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.