Your site was fine ten minutes ago. Now every page returns 502 Bad Gateway and your heart rate is doing the same thing as your error log. Take a breath: a 502 Bad Gateway on Nginx is one of the most debuggable errors in all of server land, because the error message is telling you exactly where to look — everywhere behind Nginx.
Nginx itself is healthy. It received your visitor’s request just fine. The problem is that when it tried to pass that request to your app server — PHP-FPM, a Node.js process, Gunicorn, whatever you run behind it — it got back garbage, or nothing at all. So debugging a 502 is not “fix Nginx.” It is “find out which upstream is broken, and why.” This guide to the 502 bad gateway nginx error walks you through that, log line by log line.
Last verified against official Nginx documentation and current Ubuntu LTS releases on 4 October 2026.
Step 0: Read the Error Log Before You Touch Anything
Most 502 guides hand you a checklist of ten possible fixes and wish you luck. Skip that. Your server already knows what is wrong, and it wrote it down. Start here:
sudo tail -100 /var/log/nginx/error.log
If you run several sites, narrow it to the site’s own log, usually /var/log/nginx/your-site.error.log or whatever error_log directive you set in the server block. What you are looking for is a line containing the request that 502’d — it will say something like connect() failed, upstream timed out, or upstream prematurely closed connection.
Do not restart anything yet. A restart wipes the evidence: the exact log line is the difference between a five-minute fix and an hour of guessing. Note the timestamp, the client IP, and — most importantly — the upstream: value, which tells you exactly which backend address Nginx tried and failed to reach.
Diagnosing the 502 Bad Gateway Nginx Error: Match Your Log Line
Here is the decision table. Find your line, then jump to the matching fix below:
connect() failed (111: Connection refused) while connecting to upstream— nothing is listening at that address and port. Fix 1.connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory)— PHP-FPM socket path mismatch, or PHP-FPM is down. Fix 2.upstream prematurely closed connection while reading response header from upstream— your app accepted the connection then died mid-response: crash, fatal error, or OOM kill. Fix 3.upstream sent too big header while reading response header from upstream— response headers exceed Nginx’s buffers. Fix 4.connect() failed (13: Permission denied) while connecting to upstream— SELinux or AppArmor is blocking Nginx. Fix 5.
Fix 1: “Connection refused” — Your App Is Not Listening
Error 111 means Nginx knocked on the door and nobody was home. Either your app crashed, it never started, or Nginx is knocking on the wrong door (wrong port or address).
First, confirm what is actually listening:
sudo ss -tlnp | grep -E ':3000|:8000|:9000'
curl -I http://127.0.0.1:3000
Replace 3000 with whatever port your proxy_pass points to. If curl says Connection refused, your app is down — this is not an Nginx problem at all. Restart it with whatever process manager owns it:
# PM2
pm2 restart all && pm2 logs --lines 50
# systemd
sudo systemctl restart myapp
journalctl -u myapp -n 50 --no-pager
# Docker
docker ps -a
docker restart my-container
docker logs my-container --tail 50
If the app is running but on a different port, update proxy_pass in your site config to match, then test and reload:
sudo nginx -t && sudo systemctl reload nginx
One subtle classic: the app binds to localhost, which on some systems resolves to IPv6 ::1, while Nginx tries 127.0.0.1 — or vice versa. Use an explicit IP in both places and this class of bug disappears. In your upstream, prefer 127.0.0.1:3000 over localhost:3000. If you need a refresher on the proxy setup itself, see our Nginx reverse proxy for Node.js guide.
Fix 2: PHP-FPM Socket Mismatch — The WordPress/Laravel Special
If you run PHP behind Nginx, this is the single most common 502 on the internet. The log line looks like this:
connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory)
while connecting to upstream, upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:"
Translation: Nginx is looking for a PHP-FPM socket file that does not exist. This happens after PHP upgrades (the socket name carries the version number) or when PHP-FPM simply is not running. Work through these checks in order: (Starting from a bare server? Our VPS setup guide for Ubuntu, Nginx, and SSL covers the full stack.)
1. Is PHP-FPM running? Use the unit name matching your PHP version — Ubuntu 22.04 ships PHP 8.1, Ubuntu 24.04 ships PHP 8.3:
sudo systemctl status php8.3-fpm
# if it is dead:
sudo systemctl start php8.3-fpm
sudo systemctl enable php8.3-fpm
2. Does the socket exist?
ls -l /run/php/
3. Do Nginx and PHP-FPM agree on the path? Compare the two sides:
grep -r fastcgi_pass /etc/nginx/sites-enabled/
grep listen /etc/php/8.3/fpm/pool.d/www.conf
The fastcgi_pass unix:/run/php/php8.3-fpm.sock; line in Nginx must match the listen = /run/php/php8.3-fpm.sock line in the pool file. Fix whichever side is wrong, then sudo nginx -t && sudo systemctl reload nginx.
4. Check socket permissions. Nginx runs as www-data and needs read/write access to the socket. If the socket exists but Nginx still cannot connect, check listen.owner and listen.group in the pool file — they should be www-data (or whichever user your Nginx workers run as).
Fix 3: “Upstream Prematurely Closed Connection” — Your App Died Mid-Request
This one is different: the connection succeeded, your app started responding, then died before finishing the headers. Something is killing your app while it works. Common killers:
The OOM killer. If the box ran out of RAM, the kernel murders processes to survive. Check:
dmesg | grep -i "killed process"
If you see your app or PHP-FPM workers in there, you are under-provisioned or leaking memory. Either add RAM, or reduce workers (see the PHP-FPM tuning below).
Fatal errors in your app. A PHP fatal error, an uncaught Node exception, a segfaulting extension — all of these close the connection mid-response. Read the app’s logs, not just Nginx’s:
sudo journalctl -u php8.3-fpm -n 100 --no-pager
# or for Node:
pm2 logs --lines 100 --err
PHP-FPM ran out of workers. When every worker is busy, new requests queue — and under load that queue can manifest as 502s. Look for server reached max_children in the PHP-FPM log. If you see it, raise pm.max_children in /etc/php/8.3/fpm/pool.d/www.conf, sized to your RAM. A rough formula:
max_children = (Total RAM - RAM for other services) / Average PHP process size
Check average worker size with ps -ylC php-fpm8.3 --sort:rss | awk '{sum+=$8; count++} END {print sum/count/1024 " MB"}', then restart PHP-FPM. On a small 2 GB VPS, starting around pm.max_children = 10 with pm = dynamic is sane; raise it only with evidence, not hope.
A note on timeouts: if your upstream is simply slow rather than dead, Nginx normally returns 504 Gateway Timeout, not 502. In other words, a 502 bad gateway nginx error with timeout-like symptoms usually means the app died, not that it is slow. So if you see a genuine 502, resist the urge to blindly raise proxy_read_timeout — that treats the symptom of a timeout, not a broken upstream. Fix the app first; tune timeouts only when the logs prove the app is healthy but slow.
Fix 4: “Upstream Sent Too Big Header” — Buffer Trouble
Less common, but distinctive: your app returned response headers larger than Nginx’s default buffers. This happens with apps that set enormous cookies or very long Set-Cookie / auth headers. The fix is to enlarge the proxy buffers in the relevant location block:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
}
For FastCGI, the equivalents are fastcgi_buffer_size and fastcgi_buffers. Test with nginx -t and reload. But also ask why your headers are that big — a runaway cookie is usually a bug worth fixing on the app side too.
Fix 5: “Permission Denied” — SELinux or AppArmor Is Blocking You
On RHEL-family systems (RHEL, AlmaLinux, Rocky), error 13 almost always means SELinux is stopping Nginx from making outbound connections. The giveaway is that everything works when you temporarily set SELinux to permissive mode — do that only as a five-minute diagnostic, never as a fix:
# diagnose only — do not leave it like this
sudo setenforce 0
# if the 502 disappears, SELinux was the cause; re-enable it:
sudo setenforce 1
# then allow Nginx network connections permanently:
sudo setsebool -P httpd_can_network_connect 1
On Ubuntu, AppArmor profiles can similarly block socket access after a PHP upgrade. Check dmesg or /var/log/syslog for DENIED lines mentioning nginx or php-fpm to confirm before changing any profile.
Still Seeing the 502 Bad Gateway Nginx Error? Systematic Checklist
Still getting the 502 bad gateway nginx error with a log line you do not recognise? Run this sequence and stop at the first step that looks wrong:
sudo nginx -t— is the config even valid?systemctl status nginx— is Nginx itself running?curlthe upstream directly from the server, bypassing Nginx. If that fails, the bug is 100% behind Nginx.sudo tail -50 /var/log/nginx/error.log— one exact line, read slowly.- Check the app’s own logs — PHP-FPM log,
journalctl, PM2 logs, Docker logs. free -handdf -h— out of RAM or disk causes bizarre 502s (a full disk can prevent PHP-FPM from writing its socket).sudo nginx -T | grep -E 'proxy_pass|fastcgi_pass'— dumps the effective config; make sure the site you think is serving is the one actually serving.
Preventing the Next 502
Fixing one 502 bad gateway nginx incident is good; not getting paged at 2 a.m. again is better:
- Watch the error log, not just uptime. A simple
logwatchor a cron job greppingerror.logfor502andupstreamcatches degrading upstreams hours before users notice. - Give Nginx a health-checked upstream. With multiple backends,
max_failsandfail_timeoutlet Nginx mark a dead backend down and route around it instead of 502ing every request. - Keep PHP and Nginx configs in version control. Half of all 502s are born in a deploy that changed a socket path or a port on one side but not the other. A deploy checklist with “socket path matches” as a line item pays for itself the first time.
- Monitor worker saturation. Enable PHP-FPM’s status page (
pm.status_path = /fpm-statusin the pool file, with an Nginx location block restricted to your IP) so you can see worker exhaustion coming instead of discovering it via 502s.
Further Reading & References
- How to Fix 502 Bad Gateway in Nginx (With Exact Commands) — dev.to walkthrough with copy-paste diagnostics.
- NGINX 502 Bad Gateway: Every Cause and Fix (2026 Guide) — GetPageSpeed’s exhaustive cause-by-cause reference, including PHP-FPM memory math.
- How to Fix 502 Bad Gateway in NGINX? Complete Troubleshooting Guide — ServerAvatar’s 2026 guide covering PHP-FPM worker exhaustion.
- Nginx 502 Bad Gateway: A Real Debugging Story — a real incident where a slow database query masqueraded as 502s, and the two-layer fix.
- How to Configure PHP-FPM with NGINX — DigitalOcean tutorial with an ordered 502 checklist: service status, socket path matching, permissions, logs, timeouts.
- 502 Bad Gateway NGINX: Fix PHP-FPM Errors — MetricFire’s guide to the PHP-FPM side: timeouts, worker limits, firewall and DNS issues.
- ngx_http_proxy_module documentation — official Nginx reference for
proxy_pass, timeouts, and buffer directives. - NGINX logging guide — official docs on error log levels and the upstream timing variables (
$upstream_addr,$upstream_response_time) for deeper diagnosis.



