502 Bad Gateway means Nginx itself is fine, but the thing behind it is not. Nginx passed the request to PHP-FPM or your app (the "upstream") and got no valid reply: the connection was refused, the socket did not exist, or the process crashed mid-request. The error log nearly always names the cause in one line. This guide shows how to read it and fix each common case.
Before you start
- A Linux VPS running Nginx in front of PHP-FPM or an app server such as Node.js, Gunicorn or Puma.
- SSH access with sudo. If you cannot reach SSH, use the browser console in your VPS provider's control panel.
Read the error log first
sudo tail -n 30 /var/log/nginx/error.logIf the site has its own log, check that instead (find it with sudo nginx -T | grep error_log). Reload the failing page while you watch:
sudo tail -f /var/log/nginx/error.logMatch the message to a section below:
connect to unix:/run/php/... failed (2: No such file or directory)→ socket pathconnect to unix:... failed (13: Permission denied)→ permissionsconnect failed (111: Connection refused) while connecting to upstream→ backend down or wrong portupstream prematurely closed connection→ crashed workerupstream sent too big header→ buffers
Backend not running or wrong port
Check the service. For PHP-FPM on Ubuntu or Debian (adjust the version):
systemctl status php8.3-fpmOn AlmaLinux and Rocky Linux:
systemctl status php-fpmIf it is stopped or failed, start it and read why it stopped:
sudo systemctl start php8.3-fpm
sudo journalctl -u php8.3-fpm -n 50 --no-pagerFor a Node.js or Python app, check its process manager (pm2 status, systemctl status myapp) and confirm it listens on the port Nginx expects:
sudo ss -ltnp | grep -E ':3000|:8000'Compare that with the proxy_pass line in your config. proxy_pass http://127.0.0.1:3000; fails if the app listens on 8000, or only on IPv6 [::1].
Socket path mismatch
Nginx and PHP-FPM must agree on the socket file. Find what Nginx uses:
sudo grep -rn fastcgi_pass /etc/nginx/Find what PHP-FPM creates:
sudo grep -rn "^listen" /etc/php/*/fpm/pool.d/ 2>/dev/null
sudo grep -rn "^listen" /etc/php-fpm.d/ 2>/dev/null
ls -l /run/php/ /run/php-fpm/ 2>/dev/nullThe first grep covers Ubuntu and Debian, the second AlmaLinux and Rocky. A typical mismatch after a PHP upgrade: Nginx still points at php8.1-fpm.sock but only php8.3-fpm.sock exists. Update fastcgi_pass to the real path, then test and reload.
Socket permissions
The Nginx worker user must be able to write to the socket. In the PHP-FPM pool file (for example /etc/php/8.3/fpm/pool.d/www.conf) check:
listen.owner = www-data
listen.group = www-data
listen.mode = 0660Use the user Nginx runs as: www-data on Ubuntu and Debian, nginx on AlmaLinux and Rocky. Confirm it with grep ^user /etc/nginx/nginx.conf. On RHEL-family systems the pool may use listen.acl_users instead; make sure nginx is in that list. Restart PHP-FPM after any change.
On AlmaLinux and Rocky, "Permission denied" when proxying to a TCP port is usually SELinux:
sudo setsebool -P httpd_can_network_connect 1See Fix Nginx permission denied caused by SELinux.
Crashed or exhausted workers
upstream prematurely closed connection or intermittent 502s under load usually mean PHP workers crash or run out. Look for clues:
sudo journalctl -u php8.3-fpm --since "1 hour ago" | grep -iE "max_children|segfault|exited"
sudo dmesg -T | grep -i "killed process"server reached pm.max_children: every worker was busy. Raisepm.max_childrenin the pool, within what your RAM allows. See Tune PHP-FPM pools.Killed process... php-fpmornode: the kernel ran out of memory and killed it. Reduce workers, fix the memory-hungry script, add swap or move to a larger plan.- Segfaults: often a broken PHP extension. Disable recently added extensions one by one.
Header too big
upstream sent too big header while reading response header appears when an app sends large cookies or headers. Raise the buffers in the PHP location:
fastcgi_buffer_size 32k;
fastcgi_buffers 16 16k;For proxied apps, use proxy_buffer_size and proxy_buffers instead.
Apply and retest
After each change:
sudo nginx -t && sudo systemctl reload nginxRestart the backend service if you changed its config.
Check it works
curl -I https://example.com/You want 200 or your app's normal redirect. Watch the error log for a minute while you browse; no new upstream errors should appear. Under load, check PHP-FPM status: PHP-FPM status page.
Troubleshooting
- 502 only on some pages: those scripts crash or exceed memory. Check the PHP error log and raise
memory_limitor fix the script. - 502 right after a PHP upgrade: the socket name changed. Update
fastcgi_pass. - 502 every few hours: workers leak memory. Set
pm.max_requests = 500in the pool to recycle them. - 502 after the server restarts: the app does not start on boot. Enable its service with
systemctl enableorpm2 startup. - Error log is empty: the site logs elsewhere, or a CDN shows its own 502. Check the origin directly with
curl --resolve example.com:443:203.0.113.10 https://example.com/.
Next steps
- Slow requests that end in a different error: Fix 504 Gateway Timeout in Nginx.
- Fix PHP-FPM that will not start: PHP-FPM not starting.
