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.