Troubleshooting

Fix 502 Bad Gateway in Nginx

A 502 Bad Gateway means Nginx could not get a valid answer from PHP-FPM or your app. Read the error log, then check the backend service, socket path, permissions and ports.

IntermediateLinux server5 min read

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

Bash
sudo tail -n 30 /var/log/nginx/error.log

If 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:

Bash
sudo tail -f /var/log/nginx/error.log

Match the message to a section below:

  • connect to unix:/run/php/... failed (2: No such file or directory) → socket path
  • connect to unix:... failed (13: Permission denied) → permissions
  • connect failed (111: Connection refused) while connecting to upstream → backend down or wrong port
  • upstream prematurely closed connection → crashed worker
  • upstream sent too big header → buffers

Backend not running or wrong port

Check the service. For PHP-FPM on Ubuntu or Debian (adjust the version):

Bash
systemctl status php8.3-fpm

On AlmaLinux and Rocky Linux:

Bash
systemctl status php-fpm

If it is stopped or failed, start it and read why it stopped:

Bash
sudo systemctl start php8.3-fpm
sudo journalctl -u php8.3-fpm -n 50 --no-pager

For a Node.js or Python app, check its process manager (pm2 status, systemctl status myapp) and confirm it listens on the port Nginx expects:

Bash
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:

Bash
sudo grep -rn fastcgi_pass /etc/nginx/

Find what PHP-FPM creates:

Bash
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/null

The 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:

INI
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

Use 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:

Bash
sudo setsebool -P httpd_can_network_connect 1

See 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:

Bash
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. Raise pm.max_children in the pool, within what your RAM allows. See Tune PHP-FPM pools.
  • Killed process... php-fpm or node: 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:

Nginx
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:

Bash
sudo nginx -t && sudo systemctl reload nginx

Restart the backend service if you changed its config.

Check it works

Bash
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_limit or 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 = 500 in the pool to recycle them.
  • 502 after the server restarts: the app does not start on boot. Enable its service with systemctl enable or pm2 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

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.