
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 deniedon 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.socklisten = 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 = dynamicpm.max_children: start modest (e.g., 10–30) and raise only if you have RAM.pm.max_requests = 500to recycle workers and reduce memory bloat.request_terminate_timeout = 120sto 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 publishes3000to 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.
- Enable unattended security updates on Ubuntu: follow Set Up Automated Security Updates on Ubuntu VPS.
- Lock down inbound ports with UFW (allow SSH + HTTP/HTTPS): Set Up UFW Firewall on Ubuntu for a Web Server.
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.