Why Docker Bypasses UFW Rules (And How to Fix It)

Many system administrators configure Uncomplicated Firewall (UFW), set the default incoming policy to deny, and assume the server network perimeter is locked. When they run a container with docker run -p 8080:8080 to test an internal service, they expect UFW to block outside traffic. Minutes later, automated port scanners find the port open to the internet.

Myth

The myth is that UFW acts as a global firewall for all network interfaces on a Linux host. System administrators assume that when UFW reports Default: deny (incoming), any port opened by a host process or container engine stays blocked until an explicit ufw allow rule exists.

Reality

Docker manipulates iptables rules directly when the daemon starts and when containers launch. By default, Docker inserts custom iptables rules into the PREROUTING chain of the nat table and the FORWARD chain of the filter table.

When inbound packets target port 8080, Linux Netfilter processes iptables rules in a fixed order:

First, Netfilter routes incoming traffic through the PREROUTING chain. Docker applies Destination Network Address Translation (DNAT), rewriting the target IP from the public host interface to the container internal IP (such as 172.17.0.2).

Second, because the packet is now destined for another network interface (the docker0 bridge), Netfilter routes it through the FORWARD chain instead of the INPUT chain.

UFW attaches its filtering rules to custom chains off the INPUT chain (such as ufw-user-input). Published container packets pass directly through FORWARD, so UFW input rules never process them. Docker’s DOCKER chain inside FORWARD matches the packet and forwards it straight into the container bridge.

UFW evaluates rules on the INPUT chain by default. Docker routes container traffic through Network Address Translation and the FORWARD chain, bypassing UFW input rules entirely.

To inspect how Netfilter orders these rules on your system, list the active FORWARD chain rules:

sudo iptables -L FORWARD -n -v --line-numbers

The output shows that the top rule in FORWARD jumps straight to DOCKER-USER and DOCKER. These chains accept mapped container traffic long before UFW input checks execute.

How to protect

Fixing this network exposure requires explicit host configuration. Use one of these three methods based on your deployment requirements:

Method 1: Bind published ports to loopback

If the containerized application only needs to accept traffic from a local reverse proxy (like Nginx or HAProxy) running on the same host, bind the published port explicitly to 127.0.0.1:

docker run -d -p 127.0.0.1:8080:8080 nginx:alpine

In Docker Compose files, format the port mapping the same way:

ports:
  - "127.0.0.1:8080:8080"

Binding to loopback prevents Docker from listening on public host interfaces like 0.0.0.0.

Method 2: Define rules in the DOCKER-USER chain

Docker reserves the DOCKER-USER chain for custom administrator firewall rules. Netfilter evaluates rules in DOCKER-USER before Docker processes its internal forward rules.

To block external traffic on a public interface while allowing a specific management IP, add rules to DOCKER-USER:

# Allow a specific trusted IP address
sudo iptables -I DOCKER-USER -i eth0 -s 198.51.100.45 -j ACCEPT

# Block all other incoming traffic on the public interface
sudo iptables -A DOCKER-USER -i eth0 -j DROP

Save iptables rules with iptables-persistent to ensure rules persist across system reboots.

Method 3: Disable Docker iptables management

You can stop Docker from modifying iptables by setting the configuration in /etc/docker/daemon.json:

{
  "iptables": false
}

Restart the daemon afterwards:

sudo systemctl restart docker

Note that setting iptables to false breaks container outbound network translation and inter-container communication unless you manually write NAT forwarding rules in UFW or raw iptables.

For most Linux hosts, binding internal container ports to 127.0.0.1 and managing public access through `DOCKER-USER` iptables rules offers the safest operational model.

Press Cmd K to search