502 Bad Gateway means Nginx is working, but the upstream it forwards requests to — PHP-FPM, a Node.js or Python app, a Docker container — failed to give a valid response. Nginx itself is rarely the problem; the error log tells you which upstream failed and why.
Step 1: read the Nginx error log
sudo tail -n 30 /var/log/nginx/error.log
(Or the site-specific error log configured in your server block.) The message identifies the cause:
| Error log message | Meaning |
|---|---|
connect() failed (111: Connection refused) while connecting to upstream |
Nothing listens on that address/port — app down or wrong port |
connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory) |
Wrong socket path, or PHP-FPM not running |
connect() to unix:… failed (13: Permission denied) |
Nginx can’t access the socket (permissions or SELinux) |
upstream prematurely closed connection while reading response header |
The app crashed or killed the request mid-way |
upstream sent too big header while reading response header |
Response headers exceed Nginx’s buffers |
no live upstreams while connecting to upstream |
All servers in an upstream block marked failed |
Cause 1: the upstream isn’t running
For PHP:
systemctl status php8.3-fpm
sudo systemctl restart php8.3-fpm
For an app managed by systemd, PM2 or Docker:
systemctl status myapp
pm2 ls
docker ps -a
If it keeps crashing, read its logs (journalctl -u myapp, pm2 logs, docker logs). A frequent root cause on small servers is the out-of-memory killer:
sudo journalctl -k | grep -i "killed process"
Cause 2: wrong address, port or socket
Compare what Nginx expects with what’s actually listening:
grep -rn "proxy_pass\|fastcgi_pass" /etc/nginx/sites-enabled/
sudo ss -tlnp # TCP listeners
ls -l /run/php/ # PHP-FPM sockets
Common mismatches:
- After upgrading PHP, the socket name changes (
php8.1-fpm.sock→php8.3-fpm.sock) but the Nginx config still points to the old one. - The app listens on
127.0.0.1:3000but Nginx proxies tolocalhost:3000, which may resolve to IPv6::1first — use127.0.0.1explicitly. - The app binds to a different port than configured (check its
PORTenvironment variable).
Cause 3: socket permissions
The PHP-FPM pool config (/etc/php/8.3/fpm/pool.d/www.conf) defines who may use the socket:
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
Nginx must run as a user in that group (user www-data; in nginx.conf on Debian/Ubuntu; nginx on RHEL-family systems — adjust listen.owner/listen.group there). Restart PHP-FPM after changes.
Cause 4: SELinux (AlmaLinux, Rocky)
SELinux blocks Nginx from connecting to network ports by default. If the audit log shows denials:
sudo ausearch -m avc -ts recent | grep nginx
sudo setsebool -P httpd_can_network_connect 1
Cause 5: Docker upstreams
- Nginx on the host proxying to a container: the container must publish the port (
-p 127.0.0.1:8080:80) and Nginx must use the published port. - Nginx in a container:
127.0.0.1means the Nginx container itself. Put both on the same Docker network and use the service name:proxy_pass http://app:3000;. - After a container is recreated, its IP changes. Nginx resolves hostnames at startup; if you proxy to a container name, reload Nginx after recreating, or use a resolver with variables.
See Docker networking.
Cause 6: the app is too slow or overloaded
If PHP-FPM has used all its workers, new requests queue and may fail:
sudo grep -i "max_children" /var/log/php8.3-fpm.log
WARNING: [pool www] server reached pm.max_children setting means you need more workers (if RAM allows), faster code, or caching. See PHP-FPM tuning.
Slow responses usually produce 504 Gateway Timeout rather than 502 — see Nginx 504 — but a PHP max_execution_time or request_terminate_timeout that kills a request produces 502.
Cause 7: header too big
Apps that set large cookies or many headers trigger upstream sent too big header. Increase the buffers:
# for proxy_pass
proxy_buffer_size 16k;
proxy_buffers 8 16k;
# for fastcgi_pass (PHP)
fastcgi_buffer_size 32k;
fastcgi_buffers 16 16k;
Test the upstream directly
Bypass Nginx to see whether the upstream answers:
curl -i http://127.0.0.1:3000/
For PHP-FPM over a socket, use cgi-fcgi (package libfcgi-bin) or simply create a test PHP file and request it through Nginx while watching the PHP-FPM log.
Checklist
- Read the Nginx error log.
- Is the upstream running? Check its own logs.
- Do address/port/socket in Nginx match what’s listening?
- Socket permissions and SELinux OK?
- Docker: right network, right port?
- Worker exhaustion or memory kills?
- Header buffer sizes?
Related: Nginx 504 Gateway Timeout, Nginx 403 Forbidden, and the Nginx reverse proxy guide.