Does Docker ‘Bypass’ UFW? Essential Container Port Security for VPS

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Firewall Deception: When Docker Quietly Goes Through the “Backdoor”

You just rented a VPS, eagerly installed UFW, and typed ufw deny 8080 to “close the door” on your server. But as soon as you run Docker with the -p 8080:80 parameter, the port you thought was locked is wide open to the world. Trust me, this is a “painful lesson” many SysAdmins experience when starting out.

In reality, this isn’t a software bug. It’s a consequence of how Docker “talks” directly to the Linux kernel. If you’re careless, sensitive containers like Redis or MongoDB can become “easy prey” for hackers, even if you’re certain your firewall is strictly active.

The Nature of the Conflict: Docker vs iptables

Imagine UFW is just a decorative layer (frontend) that makes typing iptables commands easier. Meanwhile, Docker is a power player that also uses iptables to coordinate data traffic in and out of containers.

Upon startup, Docker “cuts the line” and inserts its rules into the #1 priority position in iptables. When a packet arrives at the VPS, it hits Docker’s rules before it ever reaches UFW’s protection. Thus, Docker “opens the door” and invites guests in, leaving UFW shouting helplessly from the outside.

Practical experience shows this is a deadly vulnerability. I’ve seen a client’s Redis server completely “wiped” of data and hit with ransomware just 2 hours after port 6379 was exposed, even while UFW reported status: active.

Solution 1: Binding Ports to Localhost (Quick but Effective)

If you only need a container to serve another application on the same server (like Nginx acting as a Reverse Proxy), never use the standard public port method.

Instead of running this command:

docker run -d -p 8080:80 nginx

Change it to:

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

This trick forces Docker to only “listen” to requests from within the server itself. Even if Docker bypasses UFW, attackers from the internet cannot reach port 8080 because it doesn’t exist on the Public IP. I’ve applied this to my company’s entire microservices stack; it’s extremely secure and easy to manage.

Solution 2: Using ufw-docker (Professional and Systematic)

If you want to manage Docker with UFW’s “simple yet powerful” style, ufw-docker is the perfect match. This script perfectly synchronizes rules between Docker and the firewall.

First, download the script:

sudo wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
sudo chmod +x /usr/local/bin/ufw-docker

Then, run the initialization command to let the script automatically reconfigure iptables chains:

ufw-docker install

Now, instead of using ufw allow, use ufw-docker allow. For example, to open port 80 for a container named web-app:

ufw-docker allow web-app 80/tcp

Everything will work as expected: UFW regains control, and Docker no longer “opens the door” on its own.

Solution 3: Manually Intervening in DOCKER-USER

For those who like to “tinker” deeply, Docker reserves a specific area called DOCKER-USER where we can insert custom rules. These rules are always prioritized before Docker processes the packets.

Open the UFW configuration file:

sudo nano /etc/ufw/after.rules

And add this code block to the end of the file:

# Block all external access to Docker by default
*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
# Only allow office IP (e.g., 1.2.3.4) to access port 8080
-A DOCKER-USER -p tcp -m tcp --dport 8080 -s 1.2.3.4 -j RETURN
# Block all other external traffic
-A DOCKER-USER -p tcp -m tcp --dport 8080 -j DROP
COMMIT

A small warning: You need to clearly understand internal IP ranges (e.g., 172.17.0.0/16) to avoid misconfiguration. A small mistake here could leave your container completely “isolated” from the internet.

Summary and Advice

Docker automatically opening ports is a feature to get apps running instantly, but in a Production environment, it’s a risk. Don’t wait until data is lost to start scrambling for ways to block ports.

In summary, remember these 3 golden rules:

  • Internal Services: Always bind ports to 127.0.0.1.
  • Centralized Management: Use ufw-docker for professionalism.
  • High Vigilance: Absolutely do not set iptables: false in the daemon.json file unless you want Docker’s networking to break.

Server security is a long journey. Start by strictly controlling every single port you open today.

Share: