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.
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_passdirective referenceslocalhostor127.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.1inside its own container instead of0.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.