HTTP 502 Bad Gateway: What It Means and How to Fix
The 502 Bad Gateway error means a proxy server (nginx, Apache, Cloudflare) did not receive a valid response from the upstream server (PHP-FPM, Node.js, uwsgi, Gunicorn). It is not an application code problem — it is a failure between the proxy and the backend.
This article covers what 502 means across different stacks, 7 common causes, and step-by-step fixes for nginx + PHP-FPM, Node.js, and Apache with config examples.
What 502 Means
Per RFC 9110 §15.6.3, 502 indicates that the server, acting as a gateway or proxy, received an invalid response from upstream. Unlike 504 (timeout), 502 is a syntactic or connection failure: upstream crashed, returned garbage, or reset the connection.
Request Flow and Where 502 Arises
Browser → CDN/Cloudflare → nginx → PHP-FPM / Node.js / uwsgi
↑
502 from this hop if upstream:
- does not respond
- returns invalid HTTP
- closes the connection
- connect() times out7 Common Causes
- PHP-FPM / Node.js crashed or is not running.
- Worker pool exhausted — all children busy.
- OOM Killer terminated the process — system out of RAM.
- Upstream connect() timeout.
- Broken socket (unix socket deleted or permission issue).
- Deploy in progress — upstream restarting.
- Oversized headers — buffer overflow.
Diagnosing 502
Check externally via Enterno.io HTTP Header Checker, then dig into server logs.
# Process status
systemctl status php8.4-fpm
systemctl status nginx
pm2 list # for Node.js
# Logs
tail -100 /var/log/nginx/error.log
tail -100 /var/log/php8.4-fpm.log
# Check OOM Killer
dmesg | grep -i "killed process\|out of memory"
journalctl --since "10 minutes ago" | grep -i oom
# Check socket
ls -la /var/run/php/php8.4-fpm.sock
# Expected: srw-rw---- 1 www-data www-dataSolutions
1. Restart upstream
systemctl restart php8.4-fpm
systemctl restart nginx
# or PM2
pm2 restart all2. Increase PHP-FPM worker pool
# /etc/php/8.4/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
# For high load:
pm = static
pm.max_children = 100
pm.max_requests = 1000
systemctl reload php8.4-fpmCorrect pool size: max_children = (RAM - 500MB) / avg_worker_MB. Monitor via pm.status_path = /fpm-status.
3. Tune nginx buffers and timeouts
http {
# Increase if upstream sends large headers
fastcgi_buffer_size 128k;
fastcgi_buffers 8 128k;
fastcgi_busy_buffers_size 256k;
# Extend timeout for slow upstream
fastcgi_connect_timeout 30s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
# For proxying to Node.js
proxy_buffer_size 128k;
proxy_buffers 8 128k;
proxy_busy_buffers_size 256k;
proxy_connect_timeout 30s;
proxy_read_timeout 60s;
}4. Check and fix the socket
# If using unix socket
listen = /var/run/php/php8.4-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
# Alternative — TCP (fewer permission issues):
listen = 127.0.0.1:90005. OOM protection
# Raise memory_limit carefully
memory_limit = 256M
# Swap file (for low-RAM VPS)
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo "/swapfile none swap sw 0 0" >> /etc/fstab
# OOM priority for critical processes
systemctl edit php8.4-fpm
# [Service]
# OOMScoreAdjust=-5006. Graceful zero-downtime deploy
# Blue-green via nginx
upstream backend {
server 127.0.0.1:3000; # blue
server 127.0.0.1:3001 backup; # green
}
# Reload instead of restart
systemctl reload php8.4-fpm # no downtime
pm2 reload all # zero-downtime restart7. Node.js specifics
// Unhandled rejection = process crash = 502
process.on('unhandledRejection', (reason) => {
logger.error('Unhandled Rejection:', reason);
// Do not exit — keep running
});
process.on('uncaughtException', (err) => {
logger.error('Uncaught Exception:', err);
// Strategy: log and continue (or graceful shutdown)
});
// PM2 for auto-restart
// pm2 start app.js --max-memory-restart 500M502 from Cloudflare — What to Do
If Cloudflare shows 502 with a cf-ray ID:
- Verify origin responds directly (bypass CF:
curl -H 'Host: example.com' http://ORIGIN_IP). - Check the SSL certificate on origin — expired = 502 from CF.
- Ensure origin does not block CF IP ranges (WAF, firewall).
- Check CF Workers — errors in worker code return 502.
Monitoring and Prevention
Configure Enterno.io Monitors with expected_code=200 and regions ru-msk + eu-de — detect 502 instantly even if it only appears from specific geos. Hook up Telegram or Slack for immediate alerts.
Frequently Asked Questions
Q: How does 502 differ from 504?
A: 502 — upstream returned garbage or closed the connection. 504 — upstream did not respond in time.
Q: Why does deploy cause 502?
A: Upstream is restarting. Solution: graceful reload instead of restart + zero-downtime deploy via PM2/systemd.
Q: 502 only sometimes — what to check?
A: Worker pool, OOM Killer, timeouts on slow requests, socket state.
Conclusion
502 is a problem between proxy and backend. Always start by checking upstream process status, then logs. Size your worker pool, buffers, timeouts, and OOM protection correctly. Monitoring via Enterno.io catches regressions instantly.
Check your website right now
Check now →