Debugging Nginx Proxy: Solving “Connection Refused” When Routing Dockerized Apps


2 views

When implementing reverse proxy setups with Dockerized Nginx applications, a common pattern emerges where:

  • Each microservice runs in its own container with exposed ports
  • The main Nginx proxy routes traffic based on hostnames
  • Port forwarding bridges container networks with host interfaces

In this scenario, we observe:

2016/06/04 13:26:58 [error] 5#5: *2 connect() failed (111: Connection refused)
while connecting to upstream, client: 100.100.100.100, 
server: static.myserver.net, request: "GET / HTTP/1.1",
upstream: "http://127.0.0.1:8000/", host: "static.myserver.net"

Despite these key observations:

  • Direct access to myserver.net:8000 works externally
  • curl http://127.0.0.1:8000 succeeds on the host
  • Application logs show no requests received
  • The critical misunderstanding lies in Docker's networking model. When containers expose ports like:

    docker run -p 8000:80 my-app

    This creates port forwarding between:

    • The container's port 80 (internal bridge network)
    • The host's port 8000 (external interface)

    The current proxy setup:

    server {
        listen 80;
        server_name static.myserver.net;
    
        location / {
            proxy_pass http://127.0.0.1:8000;
        }
    }

    Fails because:

    1. The proxy container runs in its own network namespace
    2. 127.0.0.1 resolves to the proxy container's loopback
    3. The target app lives in a different network segment

    The proper approach involves:

    1. Creating a Shared Network

    docker network create app-network

    2. Launching Containers on the Network

    docker run --network app-network --name my-app -p 8000:80 my-app
    docker run --network app-network -p 80:80 nginx-proxy

    3. Updated Proxy Configuration

    server {
        listen 80;
        server_name static.myserver.net;
    
        location / {
            proxy_pass http://my-app:80;
        }
    }

    For those needing host-based access:

    Host Network Mode

    docker run --network host nginx-proxy

    Explicit IP Binding

    proxy_pass http://host.docker.internal:8000;

    When debugging similar issues:

    # Check container IPs
    docker network inspect app-network
    
    # Test connectivity between containers
    docker exec -it nginx-proxy curl http://my-app:80
    
    # Verify port exposure
    ss -tulnp | grep 8000

    When implementing a reverse proxy setup with Nginx, few things are more frustrating than seeing that dreaded "connection refused" error. Let me walk through a real-world scenario I recently encountered while setting up a Docker-based hosting environment.

    In this setup, we have:

    • Multiple web apps, each running in their own Docker container with Nginx
    • Each container exposes port 80, mapped to different host ports (e.g., 8000, 8001)
    • A main Nginx proxy on the host handling requests on port 80
    • DNS entries pointing to the host IP

    Despite everything appearing correctly configured, requests to static.myserver.net resulted in 502 errors with this log entry:

    2016/06/04 13:26:58 [error] 5#5: *2 connect() failed (111: Connection refused) while connecting to upstream,
    client: 100.100.100.100, server: static.myserver.net,
    request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:8000/",
    host: "static.myserver.net"

    Here's what I checked:

    1. Container Networking: The Docker container might be using bridge networking, making 127.0.0.1 inaccessible from the host.
    2. Port Binding: While myserver.net:8000 worked externally, the proxy needed internal access.
    3. Firewall Rules: Local firewall might be blocking container-to-container communication.

    For Docker containers, we need to use the special host.docker.internal DNS name (or the container's IP in bridge networks):

    server {
        listen 80;
        server_name static.myserver.net;
    
        location / {
            proxy_pass http://host.docker.internal:8000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }

    If you're using Docker Compose, you can leverage internal networking:

    version: '3'
    services:
      proxy:
        image: nginx
        ports:
          - "80:80"
        volumes:
          - ./nginx.conf:/etc/nginx/nginx.conf
        depends_on:
          - app
    
      app:
        image: your-app-image
        expose:
          - "80"

    Then in your Nginx config:

    proxy_pass http://app:80;

    Always include these headers for proper proxying:

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    • Verify container ports are properly exposed and published
    • Check Docker network configuration
    • Test connectivity between containers using docker exec and curl
    • Examine both Nginx access and error logs