Back to tutorials
Tutorial

Nginx 502 Bad Gateway Fix on Ubuntu VPS

Fix Nginx 502 Bad Gateway on Ubuntu VPS: pinpoint upstream failures, repair PHP-FPM sockets, tune timeouts, and verify recovery safely.

By Anurag Singh
Updated on Oct 29, 2026
Category: Tutorial
Share article
Nginx 502 Bad Gateway Fix on Ubuntu VPS

A 502 from Nginx almost always means the same thing. Nginx is running, but the upstream (PHP-FPM, Node, Python, Apache, or a container) didn’t return a usable response.

The quickest way to fix Nginx 502 Bad Gateway is to stop guessing and run a simple isolation loop. Identify the upstream. Confirm it’s listening. Confirm Nginx targets the right socket/port. Only then adjust timeouts.

This guide is for Ubuntu VPS operators (including WordPress site owners) who need the site back online. The commands below are production-safe if you verify after each change.

What a 502 from Nginx actually means (and what it doesn’t)

Nginx returns 502 Bad Gateway when it can’t get a valid response from an upstream. It’s not DNS. It’s not TLS. And it’s usually not “Nginx is down.”

  • Common upstreams: PHP-FPM (WordPress, Laravel), a Node.js app (localhost:3000), Gunicorn/Uvicorn (127.0.0.1:8000), Apache behind Nginx, or a Docker container port.
  • Typical root causes: the upstream crashed, the upstream is overloaded, the socket path is wrong, Unix socket permissions are blocking Nginx, IPv6/IPv4 mismatch (Nginx targets one while the app listens on the other), or timeouts are too low for heavy requests.

If you’re troubleshooting in a constrained or shared setup, root access matters. A HostMyCode VPS gives you the logs and service controls you need. That lets you fix the cause instead of rebooting and hoping.

Before changing anything: capture the exact 502 symptoms

First, decide if the 502 is intermittent or constant. Intermittent failures are often load-related. Constant failures usually point to a dead upstream or bad config.

From your laptop:

curl -I https://yourdomain.com/
curl -I https://yourdomain.com/wp-login.php

Then check whether it’s limited to dynamic requests:

curl -I https://yourdomain.com/wp-content/uploads/

Verify:

  • If static files work but PHP pages fail, focus on PHP-FPM or FastCGI config.
  • If everything fails, focus on Nginx upstream blocks, container ports, or local firewall rules.

Fix Nginx 502 Bad Gateway by reading the right logs first

Don’t tweak configs until you’ve found the error line that explains the 502. On Ubuntu, Nginx logs are typically in /var/log/nginx/.

sudo tail -n 200 /var/log/nginx/error.log

On multi-site servers, the access log helps you spot patterns. Look at status codes and upstream timing.

sudo tail -n 200 /var/log/nginx/access.log | grep ' 502 '

Look for these patterns in error.log:

  • connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory) → wrong socket path or PHP-FPM isn’t running.
  • connect() failed (111: Connection refused) while connecting to upstream → upstream isn’t listening, crashed, or the port is wrong.
  • upstream timed out (110: Connection timed out) while reading response header from upstream → upstream is slow or stuck; may require PHP-FPM tuning or higher timeouts.
  • permission denied on a socket → Nginx can’t access the socket file or its directory.

If you’re not sure whether this is TLS or application-layer, keep this handy: TLS setup guide tutorial (2026). It helps you avoid chasing certificates when the upstream is the real issue.

Identify which upstream is failing (PHP-FPM, Node, Docker, Apache)

Next, confirm what the site proxies to. In the active Nginx config, look for fastcgi_pass, proxy_pass, or uwsgi_pass.

sudo nginx -T | less

In less, search (/) for your domain. Then search for these directives:

  • fastcgi_pass unix:/run/php/php8.3-fpm.sock;
  • fastcgi_pass 127.0.0.1:9000;
  • proxy_pass http://127.0.0.1:3000;
  • proxy_pass http://unix:/run/app.sock;

Verify: once you have the exact upstream target (socket path or IP:port), the rest becomes a checklist. You can stop doing trial-and-error.

PHP-FPM upstream: check service status, socket path, and pool config

If your Nginx config uses fastcgi_pass, you’re probably dealing with PHP-FPM. First, confirm what PHP versions are installed and which unit is running:

ls -1 /etc/php/
systemctl status php8.3-fpm --no-pager

If you see “Unit not found,” you’re calling the wrong version. Common alternatives are php8.2-fpm or php8.3-fpm. List all PHP-FPM services:

systemctl list-units --type=service | grep php | grep fpm

Now confirm the socket exists. If Nginx points to /run/php/php8.3-fpm.sock:

sudo ls -l /run/php/

Fix 1: socket path mismatch

This is a common 502 after a PHP upgrade. PHP-FPM moves to a new socket name, but Nginx still targets the old one.

Update the vhost config (often in /etc/nginx/sites-available/yourdomain):

sudo nano /etc/nginx/sites-available/yourdomain

Change:

fastcgi_pass unix:/run/php/php8.2-fpm.sock;

To:

fastcgi_pass unix:/run/php/php8.3-fpm.sock;

Then test syntax and reload. Reload won’t drop established connections:

sudo nginx -t
sudo systemctl reload nginx

Verify:

curl -I https://yourdomain.com/

Fix 2: PHP-FPM is down or crash-looping

sudo systemctl restart php8.3-fpm
sudo systemctl status php8.3-fpm --no-pager

If it won’t stay up, read the service journal. It usually shows what failed:

sudo journalctl -u php8.3-fpm -n 200 --no-pager

Common mistakes that show up here:

  • Bad PHP extension config after an update.
  • Out of memory (OOM) killing PHP workers under load.
  • Invalid pool config in /etc/php/8.3/fpm/pool.d/www.conf.

Unix socket vs TCP: pick one and configure it correctly

PHP-FPM can listen on a Unix socket (fast, local) or TCP (handy for containers or split tiers). 502s happen when Nginx expects one, but PHP-FPM listens on the other.

Check what PHP-FPM is listening on:

sudo ss -lpn | grep php-fpm || true
sudo grep -R "^listen" -n /etc/php/8.3/fpm/pool.d/

You’ll usually see one of these:

  • listen = /run/php/php8.3-fpm.sock
  • listen = 127.0.0.1:9000

Fix: make Nginx match PHP-FPM

  • If PHP-FPM listens on a socket, set fastcgi_pass unix:/run/php/php8.3-fpm.sock;
  • If PHP-FPM listens on TCP, set fastcgi_pass 127.0.0.1:9000;

Verify: if you’re using TCP, confirm the port responds locally:

nc -vz 127.0.0.1 9000

Socket permission errors: fix ownership, group, and the directory execute bit

If Nginx can’t access the socket, you’ll typically see (13: Permission denied). You need correct permissions on the socket file and its parent directory.

First, confirm which user Nginx runs as:

ps -o user,group,comm -C nginx

On Ubuntu it’s usually www-data. Now check socket ownership:

sudo ls -l /run/php/php8.3-fpm.sock

Fix (recommended): configure PHP-FPM to create a socket Nginx can read/write

Edit the pool file (commonly www.conf):

sudo nano /etc/php/8.3/fpm/pool.d/www.conf

Set:

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

Restart PHP-FPM:

sudo systemctl restart php8.3-fpm

Verify: confirm the socket mode changed and the 502 is gone.

sudo ls -l /run/php/php8.3-fpm.sock
curl -I https://yourdomain.com/

Pitfall: per-site PHP-FPM pools often use per-site sockets. Fix the pool backing the failing vhost, not just whichever file you edited last.

Timeout-based 502s: raise Nginx timeouts only after confirming the upstream is healthy

If error.log shows upstream timed out, the upstream accepted the connection but didn’t respond in time. Raising timeouts can hide the symptom while performance gets worse.

Do a quick health check first.

Step 1: confirm the server isn’t resource-starved

uptime
free -h
sudo df -h
sudo top -o %CPU

If RAM is tight and swap is thrashing, PHP-FPM will stall and Nginx will time out. That’s not a config tweak. It’s capacity.

On busy sites, a larger plan or a dedicated box is often the clean fix. managed VPS hosting also helps if you’d rather not babysit worker counts, caching, and MySQL behavior.

Step 2: tune Nginx for realistic upstream response times

On PHP sites, slow admin tasks (plugin updates, imports) can trigger premature 502s. Raising FastCGI timeouts can help in those cases.

Edit:

sudo nano /etc/nginx/snippets/fastcgi-php.conf

Add (or place these in the server block if you want per-site scope):

fastcgi_connect_timeout 30s;
fastcgi_send_timeout 120s;
fastcgi_read_timeout 120s;

Test and reload:

sudo nginx -t
sudo systemctl reload nginx

Verify: hit the slow endpoint again (admin page, import route). Confirm it completes without 502.

PHP-FPM is running but still 502s: check worker limits and slow logs

On busy WordPress or WooCommerce sites, PHP-FPM can run out of workers. When the pool saturates, Nginx starts reporting upstream errors and timeouts.

Start with PHP-FPM logs:

sudo tail -n 200 /var/log/php8.3-fpm.log 2>/dev/null || true
sudo tail -n 200 /var/log/php8.3-fpm/www-error.log 2>/dev/null || true

Then review the pool settings:

sudo grep -nE "^pm\.|^request_terminate_timeout" /etc/php/8.3/fpm/pool.d/www.conf

Practical baseline for small-to-medium sites (adjust to RAM):

  • pm = dynamic
  • pm.max_children: start modest (e.g., 10–30) and raise only if you have RAM.
  • pm.max_requests = 500 to recycle workers and reduce memory bloat.
  • request_terminate_timeout = 120s to kill stuck scripts (use carefully).

Verify worker pressure (quick diagnostic):

ps --no-headers -o pid,etimes,rss,cmd -C php-fpm8.3 | head
sudo ss -s

If you see many long-running PHP processes and RSS creeping up, you’re usually dealing with a plugin/theme query issue or a database bottleneck. Add monitoring so you see the trend early.

This HostMyCode guide shows a practical setup: Server Monitoring Setup Guide Tutorial (2026).

502 behind proxy_pass: debug Node/Python/Apache upstreams the same way

If your Nginx config uses proxy_pass, treat it as upstream availability first. Confirm what Nginx points at:

sudo nginx -T | grep -R "proxy_pass" -n /etc/nginx | head -n 30

Then verify the service is listening:

sudo ss -ltnp | grep -E ":3000|:8000|:8080" || true
curl -sS -D- http://127.0.0.1:3000/ -o /dev/null

If curl to localhost fails: fix the app process, not Nginx. Check systemd:

sudo systemctl status yourapp.service --no-pager
sudo journalctl -u yourapp.service -n 200 --no-pager

Common mistake: the app binds to 127.0.0.1, but Nginx proxies to localhost. On some systems, localhost resolves to IPv6 first. If you see connect() failed (111) with [::1] in the log, set the upstream explicitly to 127.0.0.1 or make your app listen on both.

Docker upstreams: confirm container health, published ports, and networks

Container setups often throw 502s right after a rebuild. The container name, network, or internal port may have changed.

Start with the basics:

docker ps
docker logs --tail 200 your_container_name

Confirm which ports are published to the host:

docker port your_container_name

Fix patterns:

  • If Nginx proxies to 127.0.0.1:3000, make sure the container publishes 3000 to the host.
  • If Nginx runs in another container, proxy to the service name on a shared Docker network, not 127.0.0.1.

If you’re all-in on containers, you may prefer a reverse proxy designed around Docker labels. This HostMyCode tutorial shows an operator-friendly approach: Set Up Caddy Reverse Proxy for Docker on a VPS.

Zero-downtime reloads and safe rollbacks while you troubleshoot

During a 502 incident, avoid restart loops that add downtime. Reload Nginx for config changes. Also keep a rollback copy ready.

  • Test config before reload: sudo nginx -t
  • Graceful reload: sudo systemctl reload nginx
  • Snapshot current site config:
sudo cp /etc/nginx/sites-available/yourdomain /etc/nginx/sites-available/yourdomain.bak.$(date +%F-%H%M)

If you need to revert quickly:

sudo cp /etc/nginx/sites-available/yourdomain.bak.YYYY-MM-DD-HHMM /etc/nginx/sites-available/yourdomain
sudo nginx -t && sudo systemctl reload nginx

Hardening that prevents repeat 502 incidents (without overcomplicating)

After things stabilize, add two basic controls. They won’t fix today’s 502. They do reduce “mystery breakage” later.

Operator tip: if the 502 started right after patching PHP or Nginx, check /var/log/apt/history.log. Match the timeline. “Package update changed the running version” is a real root cause.

Recovery verification checklist (prove it’s fixed)

Don’t stop after the homepage loads once. Prove the upstream is stable, and confirm Nginx isn’t logging new upstream errors.

  • HTTP checks:
curl -I https://yourdomain.com/
curl -I https://yourdomain.com/wp-login.php
curl -I https://yourdomain.com/?nocache=1
  • Log checks (no fresh 502s):
sudo tail -n 200 /var/log/nginx/error.log | grep -E "502|upstream" || true
sudo tail -n 200 /var/log/nginx/access.log | grep " 502 " || true
  • Upstream health: PHP-FPM active and stable
systemctl is-active php8.3-fpm && echo "php-fpm OK"

If you host multiple domains on one VPS, run the curl checks for each vhost. One broken pool or socket can look “random” across only certain sites.

Summary: the shortest path to a stable upstream

To fix Nginx 502 Bad Gateway on an Ubuntu VPS, keep the order simple. Read error.log. Identify the upstream target. Confirm the process listens on that socket/port. Correct the mismatch (version, path, permissions). Then adjust timeouts if the upstream is healthy but slow.

If 502s repeat because the server is out of CPU/RAM, it’s usually cheaper to right-size the environment. A HostMyCode VPS gives you the control to debug properly, and managed VPS hosting is there if you want the operational layer handled.

If your site is throwing intermittent 502s under load, you’re usually bumping into CPU/RAM limits or an upstream that can’t keep up. HostMyCode can help you stabilize it on a HostMyCode VPS, or handle day-to-day ops with managed VPS hosting, including safe reloads, monitoring, and recovery runbooks.

FAQ

Why does Nginx show 502 but PHP-FPM says it’s running?

Most often, Nginx is pointing at the wrong socket/port. Socket permissions can also block Nginx from connecting. Check /var/log/nginx/error.log, then confirm the PHP-FPM listen = value and the socket file mode in /run/php/.

Should I switch from Unix socket to TCP to stop 502s?

Not automatically. Unix sockets work well on a single server, but they fail if the path/version changes or permissions are wrong. TCP is easier to test with nc and can be simpler in container setups. Pick one approach and keep both sides consistent.

Is raising fastcgi_read_timeout a real fix?

It can prevent premature 502s on legitimately slow requests, but it won’t fix a broken upstream. If PHP is stuck due to memory pressure or a slow database query, higher timeouts just delay the failure.

Can SSL or DNS cause a 502 Bad Gateway?

Not usually. SSL/DNS issues typically show up as connection failures, certificate warnings, or different 4xx/5xx responses. A 502 from Nginx points to an upstream connectivity/response problem behind the proxy.