Containers Advanced

Troubleshooting Docker Host Port Mapping Ignored on Bridge Network (Ubuntu 20.04 LTS)

Resolve Docker's host port mapping issues when containers on a bridge network fail to expose ports on Ubuntu 20.04 LTS. An expert SysAdmin guide.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Resolve Docker's host port mapping issues when containers on a bridge network fail to expose ports on Ubuntu 20.04 LTS. An expert SysAdmin guide.

Introduction

Deploying Docker containers with host port mappings (e.g., using the -p flag) is a fundamental aspect of container orchestration, allowing services running inside a container to be accessible from the host system and external networks. On Ubuntu 20.04 LTS, particularly when using the default bridge network, administrators occasionally encounter a perplexing issue where these crucial port mappings appear to be ignored. The containerized application runs perfectly fine internally, but attempts to reach it via the host's exposed port result in connection failures. This guide provides an in-depth, technical walkthrough to diagnose and resolve this specific Docker networking problem, focusing on iptables, firewall interactions, and core Linux network configurations.

Symptom & Error Signature

The primary symptom is that a Docker container configured with a host port mapping (e.g., -p 80:80) is not accessible on the specified host port. There isn't a direct Docker error message, but rather a failure to connect to the service from the host or external systems.

Consider an Nginx container mapped to host port 80:

docker run -d --name my-nginx -p 80:80 nginx:latest

Typical observations include:

  1. Docker Reports Port Mapping as Active: The docker ps output indicates that Docker believes the port is mapped.

    docker ps
    
    CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                               NAMES
    b123abcd4567   nginx:latest   "/docker-entrypoint.…"   2 minutes ago   Up 2 minutes   0.0.0.0:80->80/tcp, :::80->80/tcp   my-nginx
    

    The PORTS column showing 0.0.0.0:80->80/tcp suggests Docker has configured the mapping.

  2. Connection Refused or Timed Out from Host: Attempts to access the service via the host port fail.

    curl http://localhost:80
    
    curl: (7) Failed to connect to localhost port 80: Connection refused
    

    If a firewall is actively blocking, it might present as a timeout:

    curl: (7) Failed to connect to localhost port 80: Connection timed out
    
  3. Host Port Not Listening (or Not by Docker): Inspecting the host's listening ports (netstat or ss) does not show the expected Docker-mapped port, or it shows another service, but not the Docker container.

    sudo netstat -tulnp | grep :80
    

    (If working, you'd expect to see a line similar to tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN - with Docker's process ID, or [...] LISTEN <docker-proxy-pid>/docker-proxy.) Often, this command returns no output related to Docker on port 80.

  4. Missing or Incorrect iptables Rules: Docker extensively uses iptables for NAT and forwarding. If these rules are absent or corrupted, port mappings will fail.

    sudo iptables -t nat -L -n -v | grep DOCKER
    sudo iptables -L DOCKER -n -v
    

    You might find the DOCKER chain missing from the nat table, or specific DNAT rules for your mapped port are absent.

Root Cause Analysis

The issue of Docker host port mappings being ignored on Ubuntu 20.04 LTS typically arises from conflicts or misconfigurations within the system's networking stack, primarily involving iptables. Common root causes include:

  1. iptables Interference from External Firewalls:

    • UFW Conflicts: Uncomplicated Firewall (UFW) is the most frequent cause on Ubuntu. Docker manages its own iptables rules. If UFW's default policies (especially for the FORWARD chain) are too restrictive (e.g., DROP), or if UFW operations (like ufw reload) flush or reorder rules without proper Docker integration, Docker's NAT and forwarding rules can be bypassed or deleted.
    • Manual iptables Management: Custom iptables scripts or other firewall daemons (firewalld, nftables directly) can clash with Docker's rule management.
    • iptables Backend Issues: While Ubuntu 20.04 uses nf_tables as its default iptables backend, Docker might still prefer legacy mode. Mismatches or misconfigurations can lead to rule application failures.
  2. ip_forward Disabled: The Linux kernel's net.ipv4.ip_forward parameter must be enabled (1) for packets to be routed between different network interfaces (e.g., from your host's primary interface to the docker0 bridge). If this is disabled, Docker cannot forward traffic to containers.

  3. Docker Daemon iptables Disablement: A misconfigured /etc/docker/daemon.json file explicitly setting "iptables": false will prevent Docker from generating and managing the necessary iptables rules for port forwarding.

  4. NetworkManager Interference: Although less common, NetworkManager can, in rare scenarios, attempt to manage the docker0 bridge interface, leading to unexpected network behavior and rule conflicts.

  5. Container Application Listening Address: The application inside the container might be bound only to its loopback interface (127.0.0.1) instead of listening on 0.0.0.0 (all interfaces). If the application isn't listening on 0.0.0.0 inside the container, Docker's port mapping, which targets the container's bridge IP, will fail to establish a connection.

  6. Stale Docker Network Configurations: Occasionally, a Docker daemon restart or system reboot might not properly re-establish all iptables rules, especially after system updates or unexpected shutdowns.

Step-by-Step Resolution

Follow these steps systematically to diagnose and resolve the port mapping issue. Always test your Docker container's accessibility after each major change.

1. Verify ip_forward Status

Ensure that IP forwarding is enabled, as it's fundamental for Docker's NAT and routing capabilities.

sysctl net.ipv4.ip_forward

If the output is net.ipv4.ip_forward = 0, enable it:

echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-sysctl.conf
sudo sysctl -p /etc/sysctl.d/99-sysctl.conf

Enabling ip_forward allows your system to route packets between network interfaces. While essential for Docker, ensure your firewall rules are robust, especially on internet-facing servers, to prevent unintended network exposure.

2. Inspect Docker Daemon Configuration

Check Docker's main configuration file for explicit iptables disabling.

cat /etc/docker/daemon.json

Look for a line "iptables": false. If present, remove it or change it to "iptables": true. If the file doesn't exist or is empty, Docker defaults to iptables integration, which is desired.

Example of problematic daemon.json:

{
  "iptables": false,
  "log-driver": "json-file"
}

If you modify daemon.json, you must restart the Docker daemon:

sudo systemctl restart docker
sudo systemctl status docker

3. Address UFW (Uncomplicated Firewall) Conflicts

UFW is the most common disruptor of Docker port mappings on Ubuntu. Docker's iptables rules often need to be processed before UFW's general policies take effect.

The following steps involve modifying UFW configurations or temporarily disabling it. Proceed cautiously, especially on production systems, as this might briefly alter network security.

Option A: Temporarily Disable UFW (for rapid diagnosis)

This quickly determines if UFW is the root cause.

sudo ufw disable
sudo systemctl restart docker
# Stop and restart your container to ensure rules are reapplied
docker stop my-nginx && docker rm my-nginx
docker run -d --name my-nginx -p 80:80 nginx:latest
# Test accessibility
curl http://localhost:80

If the port mapping now works, UFW is the culprit. Re-enable UFW with sudo ufw enable and proceed to Option B for a permanent solution.

Option B: Configure UFW for Docker Integration (Recommended)

Modify UFW's rule files to accommodate Docker's iptables management.

  1. Edit ufw before.rules: Open /etc/ufw/before.rules in a text editor:

    sudo nano /etc/ufw/before.rules
    

    Navigate to the *filter section. Before the line -A ufw-before-forward -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT, add the following rules to permit Docker-related forwarding:

    # START DOCKER UFW RULES
    -A ufw-before-forward -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    -A ufw-before-forward -i docker0 -j ACCEPT
    -A ufw-before-forward -o docker0 -j ACCEPT
    # Allow traffic from/to docker containers (adjust if your bridge network is not default)
    -A ufw-before-forward -s 172.17.0.0/16 -j ACCEPT
    -A ufw-before-forward -d 172.17.0.0/16 -j ACCEPT
    # END DOCKER UFW RULES
    

    Next, go to the *nat section. Before any MASQUERADE rules (e.g., -A POSTROUTING -s 192.168.0.0/16 -o eth0 -j MASQUERADE), add rules for Docker's NAT. You may need to inspect your Docker bridge network for its exact subnet using docker network inspect bridge. The default is 172.17.0.0/16.

    # START DOCKER NAT RULES
    *nat
    :POSTROUTING ACCEPT [0:0]
    # Allow Docker to masquerade traffic from its bridge network
    -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
    # END DOCKER NAT RULES
    

    Replace 172.17.0.0/16 with your actual Docker bridge network subnet if it differs. You can find this with docker network inspect bridge and looking for Subnet under IPAM.Config.

  2. Modify UFW Default Forward Policy (if restrictive): If your ufw default FORWARD policy is DENY or DROP, this can override Docker's rules. Check your policy with:

    sudo ufw status verbose | grep "Default:"
    

    If it states Default: deny (forward), you have two options:

    • Change the default in /etc/default/ufw: Set DEFAULT_FORWARD_POLICY="ACCEPT". (Requires sudo ufw reload).
    • Add specific UFW rules to allow forwarding for Docker: sudo ufw route allow in on docker0 from any to any (and similar for out on docker0). This is generally handled by the before.rules modifications.
  3. Restart UFW and Docker:

    sudo ufw reload
    sudo systemctl restart docker
    # Re-create and test your container
    docker stop my-nginx && docker rm my-nginx
    docker run -d --name my-nginx -p 80:80 nginx:latest
    curl http://localhost:80
    

4. Manually Inspect iptables Rules

After addressing ip_forward, daemon.json, and UFW, verify that Docker's iptables rules are correctly instantiated.

# Check NAT table for DOCKER chain and DNAT rules
sudo iptables -t nat -L -n -v | grep -E 'DOCKER|DNAT'

# Expected output should include:
# Chain DOCKER (1 references)
# DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.17.0.2:80

# Check the FILTER table for DOCKER chain rules
sudo iptables -L DOCKER -n -v
# Expected output will show ACCEPT rules for forwarded traffic

# Check the DOCKER-USER chain (if you've added custom iptables rules)
sudo iptables -L DOCKER-USER -n -v

If these critical rules are missing after restarting Docker and UFW, try a full system reboot as a last resort to ensure a clean iptables slate.

5. Verify Container Application Listening Address

Confirm that the application inside your Docker container is listening on 0.0.0.0 (all interfaces) and not just 127.0.0.1 (localhost within the container).

  1. Get Container IP Address:

    docker inspect -f '{{.NetworkSettings.IPAddress}}' my-nginx
    

    (Example output: 172.17.0.2)

  2. Execute netstat (or ss) inside the Container:

    docker exec my-nginx netstat -tulnp | grep :80
    

    For most web servers (like Nginx), you should see it listening on 0.0.0.0:80:

    tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1/nginx
    

    If it shows 127.0.0.1:80, the application is not configured to accept connections from other interfaces within the container, preventing Docker's port mapping from reaching it. You would need to reconfigure the application within the container image to listen on 0.0.0.0.

6. Recreate Docker Container and Restart Docker Service

Sometimes, lingering network configurations or iptables issues can be resolved with a clean restart and container recreation.

# Stop and remove the problematic container
docker stop my-nginx
docker rm my-nginx

# Restart Docker service
sudo systemctl restart docker

# Verify Docker service is running and healthy
sudo systemctl status docker

# Relaunch your container with the port mapping
docker run -d --name my-nginx -p 80:80 nginx:latest

# Test accessibility
curl http://localhost:80

7. Check for NetworkManager Interference (Advanced/Rare)

While NetworkManager usually ignores docker0, in specific configurations, it might interfere. You can explicitly instruct NetworkManager to ignore the docker0 bridge.

  1. Create a configuration file:
    sudo nano /etc/NetworkManager/conf.d/docker-bridge.conf
    
  2. Add the following content:
    [keyfile]
    unmanaged-devices=interface-name:docker0
    
  3. Restart NetworkManager (this will temporarily disrupt network connectivity):
    sudo systemctl restart NetworkManager
    
    After NetworkManager restarts, restart Docker and retest your container.

By systematically applying these expert-level troubleshooting steps, you can effectively diagnose and resolve persistent Docker host port mapping issues on your Ubuntu 20.04 LTS systems, restoring expected network accessibility for your containerized services.

👨‍💻

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

🛡️

Our Production Verification Guarantee

Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.