Nginx

Nginx 502 Bad Gateway With Docker Container

A 502 Bad Gateway from Nginx in a Docker setup almost always means Nginx successfully received the request but couldn't get a valid response from the upstream container it's trying to proxy to — the problem is in the connection between Nginx and your app, not in Nginx's own configuration syntax.

The Problem

Requests to your site return a generic Nginx error page:

502 Bad Gateway
nginx

The Nginx container itself is running fine, and your application container also shows as running in docker ps, yet Nginx can't seem to reach it to get a real response.

Request path when 502 happens Browser Nginx container App container unreachable Broken link is usually: wrong network, wrong hostname, or app not ready yet

Why It Happens

Inside Docker, Nginx can't reach an application container the same way it would reach a service running directly on the same machine. The most common causes are:

  • Nginx's proxy_pass directive references localhost or 127.0.0.1, which inside a container refers to the Nginx container itself, not the app container — they need to be reached by container/service name instead
  • The Nginx and application containers aren't on the same Docker network, so Nginx can't resolve the app's hostname at all
  • The application inside its container hasn't finished starting yet when Nginx tries to proxy a request, especially right after a fresh docker compose up
  • The application is listening on a different port than what Nginx's config expects, or is only listening on 127.0.0.1 inside its own container instead of 0.0.0.0, making it unreachable even from other containers on the same network

The Fix

First, check Nginx's error log for the exact underlying reason — the browser's generic 502 page hides useful detail that the log reveals:

docker logs your-nginx-container

Look for lines like connect() failed (111: Connection refused) or no resolver defined to resolve — these point to a networking or startup timing issue specifically.

Confirm your proxy_pass uses the Docker Compose service name, not localhost:

location / {
    proxy_pass http://app:3000;
}

Here, app refers to the service name defined in docker-compose.yml, which Docker's internal DNS resolves automatically — but only if both containers are on the same network.

Confirm both services are actually on the same network:

services:
  nginx:
    networks:
      - app-network
  app:
    networks:
      - app-network

networks:
  app-network:

If the app just hasn't started yet, add a healthcheck and make Nginx's container wait for it rather than starting at the same moment:

app:
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
    interval: 5s
    retries: 5

nginx:
  depends_on:
    app:
      condition: service_healthy

Finally, confirm the app inside its own container is listening on 0.0.0.0, not just 127.0.0.1 — binding only to loopback makes the service invisible to other containers even on the correct network.

Still Not Working?

If everything above checks out, test connectivity directly from inside the Nginx container, bypassing Nginx's own config entirely, to isolate whether the issue is networking or Nginx configuration:

docker exec -it your-nginx-container curl http://app:3000

If this curl succeeds but requests through Nginx still 502, the problem is in the Nginx config itself (wrong port, wrong path, timeout settings). If the curl also fails, the issue is confirmed to be network-level, not Nginx-specific.

It's also worth checking Nginx's proxy timeout settings if the app is genuinely reachable but slow to respond under load, since a 502 can also occur when the upstream takes longer to respond than Nginx is configured to wait:

proxy_connect_timeout 10s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;

Increasing these values won't fix a genuinely broken connection, but it can resolve intermittent 502s that only appear under heavier load, when the application briefly takes longer than Nginx's default timeout to start responding.

Finally, if you're running multiple replicas of the same app container behind Nginx (for load balancing), confirm that Docker's internal DNS is actually resolving to a currently healthy container. A recently restarted or unhealthy replica still registered under the same service name can cause intermittent 502s that seem to come and go without any obvious pattern, since some requests land on the healthy instance and others briefly hit the one still starting up.