Dev.to · 10 min read

Nginx in Docker: Fix host not found in upstream

Nginx in Docker: Fix host not found in upstream

Your nginx container exits a second after it starts, and docker logs shows nginx: [emerg] host not found in upstream "app" in /etc/nginx/conf.d/default.conf:4. nginx looks up every hostname in proxy_pass once, at startup. If Docker's DNS can't answer for app at that moment, nginx refuses to run at all. This guide reproduces the error with nginx:1.30.6-alpine in front of traefik/whoami:v1.12.0 on Docker Engine 29.9.2, then fixes each cause. Every message below comes from that lab run on November 9, 2026. Plenty of people hit this. On that day the Stack Overflow question Docker Networking - nginx: [emerg] host not found in upstream had 302,677 views and stopped: [emerg] 1#1: host not found in upstream had 177,100. The short version: The app container must be running and attached to the same user-defined network as nginx when nginx starts. The default bridge network has no container DNS. Create a network with docker network create and attach both containers. Two Compose projects get two separate networks. Join both to one external network. Use the Compose service name, a network alias or container_name. The generated name like projc-app-1 does not resolve once container_name is set. To let nginx start without the app and follow IP changes, add resolver 127.0.0.11 valid=10s and either a variable in proxy_pass or server app:3000 resolve in an upstream block. Table of contents Why nginx says host not found in upstream Start with the map. Match your setup to a cause, check it, then apply the fix: Reproduce it with a missing app This is the whole nginx config used in the lab: server { listen 80; location / { proxy_pass http://app:3000; } } Start nginx on a user-defined network before any container called app exists: docker network create labnet docker run -d --name proxy --network labnet -p 8080:80 \ -v "$PWD/static.conf:/etc/nginx/conf.d/default.conf:ro" nginx:1.30.6-alpine The container exited with code 1 two seconds later: nginx: [emerg] host not found in upstream "app" in /etc/nginx/conf.d/default.conf:4 The line number points at the proxy_pass line. If you define the backend in an upstream block instead, the message includes the port, host not found in upstream "app:3000", and points at the server line. nginx -t fails with the same message, so you can test a config before you deploy it. Start the app on the same network Containers on a user-defined network find each other through Docker's embedded DNS server at 127.0.0.11. Start the app with the name nginx expects, then start nginx again: docker run -d --name app --network labnet -e WHOAMI_PORT_NUMBER=3000 traefik/whoami:v1.12.0 docker start proxy curl -s localhost:8080 | head -1 docker exec proxy getent hosts app The proxy stayed up, curl returned the whoami page, and getent showed 172.19.0.2 app. Inside the container, /etc/resolv.conf listed nameserver 127.0.0.11. Fix the default bridge network Why plain docker run fails Run both containers without --network and they land on the default bridge network: docker run -d --name app -e WHOAMI_PORT_NUMBER=3000 traefik/whoami:v1.12.0 docker run -d --name proxy -p 8080:80 \ -v "$PWD/static.conf:/etc/nginx/conf.d/default.conf:ro" nginx:1.30.6-alpine The app was up, both containers showed bridge in docker ps, and nginx still exited with the same [emerg] line. A busybox:1.37.0 lookup of app on that network returned NXDOMAIN. The Docker bridge network docs say it plainly: "Containers on the default bridge network can only access each other by IP addresses, unless you use the --link option, which is considered legacy." Check which network a container uses: docker inspect -f '{{.HostConfig.NetworkMode}}' app It printed bridge for the plain docker run container. Connect both containers to a user-defined network You don't need to recreate anything. Create a network, connect both containers and start nginx: docker network create web docker network connect web app docker network connect web proxy docker start proxy curl -s -o /dev/null -w '%{http_code}\n' localhost:8080 returned 200. Fix separate Docker Compose projects Each project gets its own network A common layout keeps the reverse proxy in one directory and each app in another. The Compose networking docs explain the catch: "Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by its service name." That default network belongs to one project only. In the lab, projA ran the app service and projB ran nginx with the same config. docker compose ls -a showed proja as running(1) and projb as exited(1). The proxy logged host not found in upstream "app", and docker network ls listed two networks, proja_default and projb_default. Join both projects to an external network Create the shared network once: docker network create edge Add it to the app project. Keep default too if the app talks to its own database: services: app: image: traefik/whoami:v1.12.0 environment: WHOAMI_PORT_NUMBER: "3000" networks: - default - edge networks: edge: external: true And to the proxy project: services: proxy: image: nginx:1.30.6-alpine ports: - "8080:80" volumes: - ../conf/static.conf:/etc/nginx/conf.d/default.conf:ro networks: - edge networks: edge: external: true Run docker compose up -d in each directory. The proxy returned the whoami page, and docker network inspect edge listed proja-app-1 and projb-proxy-1. If you forget the docker network create step, Compose stops with network edge declared as external, but could not be found. Which names resolve In one Compose project with service app, container_name: myapp and a network alias backend, the lab tested names from inside the nginx container with getent hosts: Name Result app (service name) resolves myapp (container_name) resolves backend (network alias) resolves App (different case) resolves projc-app-1 not found ap (typo) not found Copy the service name from compose.yaml into proxy_pass. A typo produces the same [emerg] error as a missing container. Fix 502 errors and startup order nginx keeps the old IP address The static config has a second problem. nginx resolved app once, so it keeps that IP until it restarts. In the lab, the app had 172.22.0.2. After docker compose stop app, another container took that address, and docker compose up -d app gave the app 172.22.0.4. nginx still sent traffic to the old one: [error] 23#23: *3 connect() failed (111: Connection refused) while connecting to upstream, client: 172.22.0.1, upstream: "http://172.22.0.2:3000/" curl returned 502. Restarting nginx fixes it until the next redeploy. When the app is down at that moment, a restart brings back the [emerg] exit. The lab stopped the app, ran docker compose restart proxy, and the proxy exited with code 1. depends_on helps only at first start A popular answer by czerasz on the original question says: "This can be solved with the mentioned depends_on directive since it's implemented now (2016)". In the lab, depends_on: [app] in the same project did make the first docker compose up -d work. It did not help when the app was recreated or stopped later, as shown above. It also does nothing across two projects. Resolve the upstream at run time Use resolver 127.0.0.11 with a variable When proxy_pass contains a variable, nginx resolves the name per request through the resolver you set, not at startup. The proxy_pass docs describe this. Point the resolver at Docker's DNS: server { listen 80; resolver 127.0.0.11 valid=10s ipv6=off; location / { set $upstream http://app:3000; proxy_pass $upstream; } } With this config, nginx started with no app container. Requests returned 502 and the log showed app could not be resolved (3: Host not found). One second after docker compose up -d app, the same URL returned 200. After the stale IP test from the previous section, it returned 200 within 11 seconds without a restart. Drop the resolver line and requests fail with no resolver defined to resolve app. One catch. If the variable includes a path, such as http://app:3000/api/, the proxy_pass docs say nginx passes that URI as is and replaces the original request URI. Prefix rewriting from a location no longer happens, so test your paths after the change. Use the resolve parameter in an upstream block Open source nginx can also re-resolve servers inside an upstream block. The upstream module docs note: "In order for this parameter to work, the resolver directive must be specified in the http block or in the corresponding upstream block. Prior to version 1.27.3, this parameter was available only as part of our commercial subscription." The 1.30.6 image includes it: resolver 127.0.0.11 valid=10s ipv6=off; upstream app_backend { zone app_backend 64k; server app:3000 resolve; } server { listen 80; location / { proxy_pass http://app_backend; } } nginx started without the app and returned 502 with no live upstreams. Within 11 seconds of starting the app it returned 200, and it followed the app to a new IP the same way. Without the resolver line, nginx -t failed with no resolver defined to resolve names at run time in upstream "app_backend". This form keeps normal proxy_pass URI handling, so it is the cleaner choice on nginx 1.27.3 or later. For other reverse proxies, see Traefik Docker labels: fix 404, 502 and gateway timeout and Nginx Proxy Manager with SSL. If nginx starts but the host port clashes, read Docker port is already allocated. Apps behind nginx often need proxy headers too, as in Keycloak behind a reverse proxy and Home Assistant 400 Bad Request. FAQ What does "host not found in upstream" mean in nginx? nginx could not resolve a hostname from proxy_pass or an upstream server while loading its config, so it stopped. In Docker the name is usually a container that is not running or not on nginx's network. See SO 42720618. How do I reach another container from a Dockerized nginx? Put both containers on the same user-defined network and use the other container's service name or container name with its internal port, for example http://app:3000. Don't use localhost, which points at the nginx container itself. See SO 39202964. Does depends_on fix host not found in upstream? Only for the first start of a single Compose project. In the lab, a recreated or stopped app still broke nginx, and depends_on cannot point at a service in another project. Run-time resolution covers those cases. Why does nginx return 502 after I recreate a container? nginx cached the container's old IP at startup. The recreated container got a new address, and nginx kept connecting to the old one. Restart nginx, or add resolver 127.0.0.11 valid=10s with a variable or resolve. What is 127.0.0.11 in Docker? It is Docker's embedded DNS server. Containers on user-defined networks get nameserver 127.0.0.11 in /etc/resolv.conf, and it answers service names, container names and aliases. Containers on the default bridge use the daemon's normal DNS servers instead and can't look up container names. Can nginx start when the upstream container is down? Yes, with a resolver and either a variable in proxy_pass or server ... resolve. nginx then starts, returns 502 until the app is up, and recovers without a restart. With a plain hostname in proxy_pass, it exits with [emerg].

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Startup & VC News