What Is a 502 Bad Gateway Error?
When Nginx returns a 502, it is telling you: I received your request, tried to forward it to the upstream server, and got either no response or an invalid one. Nginx itself is healthy. The problem is with whatever is sitting behind it, your Node.js app, your Python/Django process, your PHP-FPM pool, your Docker container, or any other upstream service.
This is different from a 503 (service temporarily unavailable, often intentional) or a 504 (the upstream responded but too slowly). A 502 almost always means the upstream process is either not running, not listening on the expected address, or crashing immediately when contacted.
Common Symptoms
- Browser shows '502 Bad Gateway' with Nginx in the footer
- curl returns HTTP/1.1 502 Bad Gateway against the public URL
- Nginx error log shows: connect() failed (111: Connection refused) while connecting to upstream
- Nginx error log shows: recv() failed (104: Connection reset by peer)
- The error appeared after a deployment, server restart, or traffic spike
- Refreshing occasionally works, pointing to a process that crashes and restarts
- All routes return 502, not just one endpoint
Most Common Causes
1. Application Crashed
The most frequent cause. Your app threw an uncaught exception, ran out of memory, or hit a fatal startup error and exited. Nginx is still running and accepting connections, but there is nothing behind it to forward the request to.
2. Wrong Upstream Configuration
The proxy_pass directive in your Nginx config points to the wrong address or port. The app is running fine, Nginx is just knocking on the wrong door.
3. Service Not Running
The application was never started, or it did not survive a server reboot because it was not configured as a systemd service with auto-restart. Common after infrastructure work or deployments that reset the environment.
4. Port Mismatch
Your app was configured to listen on port 3001 but Nginx's proxy_pass is still pointing to port 3000 from a previous configuration. This is especially common during refactors or when environment variables controlling the port change.
5. Firewall Restrictions
A firewall rule on the server (iptables, ufw) is blocking traffic on the internal port your app uses. Nginx cannot reach the upstream even though both are on the same machine.
6. Resource Exhaustion
The server has run out of memory (OOM killer terminated the app process), file descriptors (too many open connections), or CPU (process unresponsive). The app process either died or is too busy to respond within Nginx's timeout window.
How to Diagnose the Issue
Run these in order. Each command narrows down the cause. You will usually find it within the first three steps.
Step 1, Read the Nginx error log first
The error log almost always names the specific upstream address that failed and the error code. This tells you exactly what Nginx tried to connect to.
sudo tail -50 /var/log/nginx/error.logLook for lines containing 'connect() failed', 'no live upstreams', or 'upstream timed out'. The address in brackets (e.g. 127.0.0.1:3000) is what Nginx is trying to reach, note that address for the next steps.
If you have a site-specific log, check /var/log/nginx/your-site-error.log first. The error address from these logs is what you will use to verify with curl in Step 4.
Step 2, Check if your application is running
sudo systemctl status your-app-serviceps aux | grep -E 'node|python|php|ruby|gunicorn|uvicorn' | grep -v grepIf systemctl shows 'inactive (dead)' or 'failed', the process is not running. If ps returns nothing, the process has exited. Either way, the cause is clear: the upstream is gone.
Step 3, Verify what is actually listening on the expected port
sudo ss -tlnp | grep LISTENsudo netstat -tlnp | grep LISTENFind the port your app should be using. If it does not appear in the output, nothing is listening there. If a different port appears, you have a port mismatch between the app and the Nginx config.
Step 4, Test the upstream directly with curl
Bypass Nginx entirely and hit the upstream address directly. Use the exact address from the Nginx error log.
curl -v http://127.0.0.1:3000/If curl returns 'Connection refused', the port is closed, the app is not running or not listening. If curl returns a valid response, the app is up and the problem is in the Nginx configuration or a network rule. If curl hangs, the process is running but unresponsive (resource exhaustion or deadlock).
Step 5, Check application logs
sudo journalctl -u your-app-service -n 100 --no-pagerpm2 logs --lines 100Look for unhandled exceptions, 'EADDRINUSE' (port already in use), 'ENOMEM' (out of memory), or any fatal error that would have caused the process to exit. The timestamp on the last log line tells you when the process died.
Step-by-Step Fixes
Fix 1, Restart the application
If the app crashed, restart it first to restore service. Then investigate the root cause from the logs before it crashes again.
sudo systemctl restart your-app-servicepm2 restart your-appRestarting without checking why it crashed will get you back online temporarily but the crash will repeat. Always read the logs after restarting to understand the root cause.
Fix 2, Correct the upstream port in the Nginx config
If ss -tlnp shows the app is listening on a different port than what Nginx is configured to use, update the proxy_pass directive to match.
server {
listen 80;
server_name example.com;
location / {
# Change this port to match what your app is actually listening on
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
proxy_send_timeout 300s;
}
}After editing, always test the config before reloading, a syntax error will take Nginx down entirely.
sudo nginx -tsudo systemctl reload nginxFix 3, Run the application as a managed systemd service
If the app is not starting on boot or not restarting after a crash, create a proper systemd service. This is the correct long-term solution for any production server.
[Unit]
Description=Your Application
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/your-app
ExecStart=/usr/bin/node /var/www/your-app/server.js
Restart=always
RestartSec=5
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=your-app
Environment=NODE_ENV=production
Environment=PORT=3000
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload && sudo systemctl enable your-app && sudo systemctl start your-appFix 4, Check and fix firewall rules
If curl from the server itself can reach the upstream but Nginx cannot, a firewall rule is likely blocking internal traffic. Allow the port on loopback.
sudo ufw status verbosesudo iptables -L -n | grep 3000For ufw, allow traffic on the internal port. Since the upstream is local, this typically should already be open, but an overly strict policy can block it.
sudo ufw allow from 127.0.0.1 to any port 3000Fix 5, Increase Nginx worker and file descriptor limits
If the server is under high load and connections are being exhausted, increase the limits in the main Nginx config.
# At the top level (outside any block)
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
# Upstream connection keep-alive
upstream your_app {
server 127.0.0.1:3000;
keepalive 32;
}
}Fix 6, Tune proxy timeout settings
If your application responds correctly to curl but Nginx returns 502, the upstream may be responding slower than Nginx's default timeout allows (60 seconds). For long-running requests, increase the timeout values.
location / {
proxy_pass http://127.0.0.1:3000;
# How long to wait for the upstream to accept the connection
proxy_connect_timeout 75s;
# How long to wait between data reads from the upstream
proxy_read_timeout 300s;
# How long to wait to send data to the upstream
proxy_send_timeout 300s;
}Do not blindly increase timeouts to very high values. A genuinely slow upstream is itself a problem. Increase timeouts only if you have verified the upstream response time and it is legitimately long (e.g., AI inference, video processing, report generation).
Verification Checklist
Run through these after applying the fix before declaring the issue resolved.
- Nginx config passes syntax check: sudo nginx -t returns 'syntax is ok'
- Application process is running: systemctl status shows 'active (running)'
- Port is listening: ss -tlnp shows the expected port with the app's PID
- Upstream responds directly: curl http://127.0.0.1:PORT/ returns a valid response
- Public URL is returning 200: curl -I https://example.com/ returns HTTP/2 200
- Nginx error log is clean: tail /var/log/nginx/error.log shows no new upstream errors
curl -o /dev/null -s -w '%{http_code}\n' https://example.com/That command should return 200. Anything else means the fix is not complete.
Prevention Best Practices
Use a process manager with auto-restart
Never run a production application with a bare node server.js or python app.py command in a terminal. Use systemd (shown above) or PM2 with the --watch flag. Configure Restart=always in systemd or pm2 startup so the process automatically recovers from crashes.
Monitor the upstream independently
Set up a health check endpoint in your application (e.g., GET /health returning 200) and configure an uptime monitor against it. Tools like UptimeRobot, Better Uptime, or a self-hosted Uptime Kuma instance alert you before users start reporting 502 errors.
Use Nginx upstream health checks
The Nginx Plus upstream health check feature (or the open-source nginx_upstream_check_module) actively monitors upstream health and marks servers down before sending real traffic to them. This does not prevent crashes but reduces the blast radius when they happen.
Log rotation and disk space monitoring
A full disk is a non-obvious cause of 502 errors, applications fail to write logs or temp files and crash. Set up logrotate for your application logs and monitor disk usage with an alert at 80% capacity.
df -h && du -sh /var/log/nginx/*Pin your port configuration with environment variables
Use an environment variable (PORT) that both your application and your Nginx configuration reference from the same source of truth. When the port changes, it changes in one place. Port mismatches are almost always caused by manual edits that only update one side.
Final Thoughts
A 502 Bad Gateway almost always has a simple, immediate cause: the upstream process crashed, was never started, or is being contacted on the wrong address. The diagnostic workflow is fast, five commands, read in order, will identify the cause in under five minutes in the vast majority of cases.
The harder problem is why it happened and whether it will happen again. A crash with no process manager means it will happen every time the server reboots. A port mismatch will happen every time that configuration file is touched. A resource exhaustion issue will happen again at the next traffic spike.
Fix the immediate 502 first, then spend fifteen minutes addressing the root cause. The five-minute fix that prevents the next three hours of downtime is always worth it.