The Error
502 Bad Gateway
This one happens at the proxy layer. Nginx, Apache, a load balancer, or Cloudflare received your request fine β but when it tried to forward that request upstream, it got nothing useful back. The proxy is alive. The backend isn't cooperating.
Common triggers: the app server crashed, port mismatch, upstream timeout, or a misconfigured proxy_pass directive.
Step 1 β Check If Your Backend Is Actually Running
Before touching Nginx config, confirm the upstream process is alive.
# Check if your Node.js/Python/Gunicorn app is listening
sudo ss -tlnp | grep 3000
# or
sudo netstat -tlnp | grep 3000
# If nothing shows, the app is down β restart it
sudo systemctl restart myapp
# Check the app logs
journalctl -u myapp -n 50 --no-pager
If that port isn't open, Nginx has nothing to talk to. No amount of config changes will help. Fix the app first.
Step 2 β Verify Nginx Is Pointing to the Right Place
Open your site config:
sudo nano /etc/nginx/sites-available/mysite
Check the proxy_pass line carefully:
location / {
proxy_pass http://127.0.0.1:3000; # must match your app's actual port
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
Four mistakes that reliably cause this error:
- Wrong port β app runs on
3000but config says8080 - Using
localhostinstead of127.0.0.1when IPv6 resolution behaves unexpectedly - Trailing slash inconsistency β
proxy_pass http://127.0.0.1:3000/vs no slash changes how paths get rewritten - Pointing to a Unix socket that doesn't exist or has wrong permissions
Step 3 β Check Upstream Timeout Settings
Heavy DB queries, cold starts, or slow external API calls can push your backend past Nginx's default 60-second timeout. The proxy gives up and returns a 502 before the response ever arrives.
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
Bumping these buys time, but it's a band-aid. A backend that regularly takes 90+ seconds has a performance problem worth fixing at the source.
Step 4 β Read the Nginx Error Log
Tail the error log first β it usually tells you exactly what broke:
sudo tail -f /var/log/nginx/error.log
You'll see something like:
[error] 12345#0: *1 connect() failed (111: Connection refused) while connecting to upstream,
client: 1.2.3.4, server: example.com, request: "GET / HTTP/1.1",
upstream: "http://127.0.0.1:3000/", host: "example.com"
Connection refused = backend isn't running on that port.
upstream timed out = backend is running but too slow or hanging.
Step 5 β Docker / Container Setups
Running your app in Docker with Nginx on the host (or in a separate container)? Then 127.0.0.1 won't work β inside a container, that address resolves to the container itself, not the host machine.
# Option 1: Use the host gateway IP (Docker Desktop on Linux)
proxy_pass http://172.17.0.1:3000;
# Option 2: Use Docker's internal DNS if both are in the same network
proxy_pass http://app-container-name:3000;
# Find the gateway IP
docker network inspect bridge | grep Gateway
In docker-compose.yml, both services need to share a network:
services:
nginx:
networks:
- webnet
app:
networks:
- webnet
networks:
webnet:
Step 6 β Unix Socket Instead of TCP Port
Gunicorn and uWSGI often use Unix sockets rather than TCP ports. Two things can go wrong: the socket file doesn't exist, or Nginx can't read it.
# Check if socket exists
ls -la /run/myapp.sock
# Nginx config for socket
location / {
proxy_pass http://unix:/run/myapp.sock;
}
# Fix permission if Nginx user (www-data) can't read it
sudo chown www-data:www-data /run/myapp.sock
Verify the Fix
Test in this order β each step confirms a different layer:
# Test Nginx config syntax first
sudo nginx -t
# Reload without downtime
sudo systemctl reload nginx
# Confirm backend is reachable directly (bypass Nginx)
curl -v http://127.0.0.1:3000/
# Then test through Nginx
curl -v http://yourdomain.com/
# Watch live access logs
sudo tail -f /var/log/nginx/access.log
A 200 OK on the direct curl and then through Nginx confirms everything is wired up correctly.
Tips
Intermittent 502s β only under load, not consistently β usually mean an exhausted upstream pool. Add a second instance and Nginx will round-robin between them:
upstream backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
keepalive 32;
}
While you're at it, add a custom error page so users see something useful instead of a raw browser 502:
error_page 502 /502.html;
location = /502.html {
root /var/www/html;
internal;
}
When debugging routing issues between proxy and upstream β especially in multi-network Docker setups β the subnet calculator on ToolCraft quickly verifies CIDR ranges and confirms hosts are actually in the same network segment. Catches a lot of "why can't they reach each other" problems fast.

