Nginx 502 Bad Gateway and 504 Timeout: Causes and Fixes
Practice5 minInnovialabs editorial team
In short
A 502 means Nginx got no valid response from the backend (it is down, not listening or the socket is inaccessible), while a 504 means the backend was too slow; error.log almost always names the cause.
How 502 and 504 differ
As a reverse proxy, Nginx does not build the page itself. It forwards the request to an upstream application (PHP-FPM, Node.js, Python, Go) and relays the response.
- 502 Bad Gateway: Nginx could not get a proper response. The connection was refused, the socket is missing, the process died mid-response, or the response headers did not fit into the buffer.
- 504 Gateway Timeout: the connection worked, but the backend did not answer in time.
In short: 502 is "the backend is gone or answered garbage", 504 is "the backend is there but too slow".
Step 1. Read error.log
Do not tweak the config blindly. Open the error log first:
sudo tail -n 50 /var/log/nginx/error.log
The path may differ, so check the error_log directive. Typical lines and their meaning:
| Log message | Cause |
|---|---|
connect() failed (111: Connection refused) | app not running or listening on another port |
connect() to unix:... failed (2: No such file or directory) | wrong socket path or PHP-FPM is down |
connect() to unix:... failed (13: Permission denied) | the Nginx user cannot access the socket |
upstream prematurely closed connection | the process crashed or was killed mid-response |
upstream sent too big header | response headers do not fit into the buffer |
upstream timed out (110: Connection timed out) | backend is slower than the timeout, a 504 |
Step 2. Check the upstream process and socket
Make sure the app is alive and listening exactly where proxy_pass or fastcgi_pass points:
systemctl status php8.2-fpm # or your service name
ss -ltnp | grep 3000 # Node.js TCP port
ls -l /run/php/ # PHP-FPM unix sockets
curl -I http://127.0.0.1:3000 # request bypassing Nginx
If a direct curl fails, Nginx is not the problem. Check the app's own logs with journalctl -u service-name.
A common mistake is an address mismatch: Nginx calls 127.0.0.1:3000 while the app listens on IPv6 localhost only, or on a different port. Another one: after a PHP upgrade the socket name changed, but the config still points to the old one.
Step 3. Socket permissions
On Permission denied, compare the Nginx user (user in nginx.conf) with the socket owner. In the PHP-FPM pool config set:
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
Restart PHP-FPM. If SELinux is enabled, the denial may come from it, so check the audit log.
Step 4. Timeouts (fixing 504)
First find out why the request is slow: a heavy SQL query, an external API, a big report. Raise timeouts only for genuinely long operations, and preferably per location:
location /reports/ {
proxy_connect_timeout 10s;
proxy_read_timeout 120s;
proxy_send_timeout 120s;
proxy_pass http://127.0.0.1:3000;
}
For PHP-FPM the equivalent is fastcgi_read_timeout. Remember that PHP has its own max_execution_time and the pool has request_terminate_timeout. If these are shorter than the Nginx timeout, the process is killed first and you get a 502 instead of a 504.
Long jobs such as imports or file generation belong in a background queue, with the user receiving a status instead of waiting.
Step 5. Buffers
upstream sent too big header usually comes from large cookies or many headers. Increase the buffers:
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
# for PHP-FPM
fastcgi_buffer_size 32k;
fastcgi_buffers 16 16k;
Also check whether the app inflates cookies for no reason.
Step 6. PHP-FPM and Node.js crashes
If 502s appear under load and then disappear, the backend cannot keep up:
- PHP-FPM: look for
server reached pm.max_children settingin the pool log. All workers are busy. Raisepm.max_childrenwith memory in mind, since each worker uses its own. - Node.js: an unhandled exception kills the whole process. Run the app under a process manager (systemd, PM2, Docker with
restart) so it comes back automatically, and find the cause in the logs. - OOM killer: run
dmesg | grep -i kill. If the kernel kills the process for lack of memory, you need optimization or more RAM.
After any change, test the config and reload without stopping the server:
sudo nginx -t && sudo systemctl reload nginx
Checklist
- Read
error.logand find the exact error. - Confirm the upstream is running and answers directly.
- Match the address, port or socket path.
- Check unix socket permissions.
- For 504, find the slow part, then raise the timeout locally.
- For "too big header", increase buffers.
- Check worker limits and the OOM killer.