Fix 502 Bad Gateway: When Your Proxy Can't Get a Valid Response

intermediate🌐 Networking2026-05-10| Linux (Ubuntu/Debian/CentOS), Nginx, Apache, Node.js, Docker, reverse proxy setups

Error Message

502 Bad Gateway
#networking#http#nginx#gateway#proxy

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 3000 but config says 8080
  • Using localhost instead of 127.0.0.1 when 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.

Related Error Notes