How to Fix the Docker UFW Security Bypass on Ubuntu (iptables Flaw)
You spin up a fresh Ubuntu Linux VPS on Hetzner, AWS, or DigitalOcean. You configure UFW, execute sudo ufw default deny incoming, permit only SSH on port 22, and enable the firewall. Running sudo ufw status confirms port 5432 (PostgreSQL) or 3306 (MySQL) is nowhere to be seen.
A few minutes later, you launch a production database using Docker:
docker run -d -p 5432:5432 --name postgres-prod postgres:16
You assume UFW is guarding the port. It is not. From anywhere on the public internet, port scanners and automated bots can connect directly to your PostgreSQL database. Your firewall was completely bypassed.
Why Docker Bypasses UFW Rules
The root cause lies in how Docker interfaces with Linux packet filtering (iptables / netfilter).
UFW is a simplified user-space wrapper over iptables. By default, UFW rules (ufw allow, ufw deny) are injected into the INPUT chain. The INPUT chain only inspects packets destined for local host processes.
Docker, however, uses virtual bridge interfaces (docker0). When you bind a port with -p 5432:5432, Docker installs automated NAT rules in the PREROUTING chain that forward incoming traffic directly into the FORWARD chain before UFW's INPUT chain ever gets evaluated.
Incoming Internet Packet (eth0:5432)
↓
[PREROUTING (NAT)] → Docker reroutes destination to container (172.17.0.2:5432)
↓
[FORWARD] → UFW does not filter this chain by default; Docker accepts it
↓
[Docker Container] → Exposed directly to the internet!
(The [INPUT] chain where UFW rules live is never touched)
The result is silent exposure of sensitive databases, caches, and internal services without any warnings in your terminal.
Solution 1: Bind Port Exclusively to Localhost (Recommended)
If your Docker service (PostgreSQL, Redis, RabbitMQ, Elasticsearch) only needs to be reached by other containers, local host applications, or a reverse proxy (such as Nginx or Traefik), the cleanest solution is explicit loopback binding to 127.0.0.1:
In Docker CLI:
docker run -d -p 127.0.0.1:5432:5432 --name postgres-prod postgres:16
In Docker Compose (docker-compose.yml):
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432" # Accessible only from local host
environment:
POSTGRES_PASSWORD: my_secret_password
By prepending 127.0.0.1:, Docker binds the socket only on the lo loopback interface. No outside traffic on your VPS public IP will be able to connect.
Solution 2: Use the DOCKER-USER Chain in UFW
If you must expose a Docker port publicly but want UFW to restrict which external IPs are allowed to connect (e.g. allowing PostgreSQL access only from your office static IP or a backend worker), Docker reserves a dedicated chain called DOCKER-USER.
This chain executes before Docker's internal forwarding rules.
Edit your UFW after-rules file:
sudo nano /etc/ufw/after.rules
Add the following block right before the final COMMIT:
# Filter Docker external traffic
*filter
:DOCKER-USER - [0:0]
:ufw-user-forward - [0:0]
# Allow established connections
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Allow PostgreSQL (5432) only from trusted IP
-A DOCKER-USER -m conntrack --ctorigdstport 5432 -s 203.0.113.45 -j ACCEPT
# Drop all other external incoming traffic on eth0
-A DOCKER-USER -i eth0 -j DROP
COMMIT
Reload UFW to apply the rules:
sudo ufw reload
What NOT to Do: iptables: false
Do not disable Docker's iptables integration by adding {"iptables": false} in /etc/docker/daemon.json. Doing so breaks inter-container DNS resolution, destroys network isolation, and prevents containers from accessing the outbound internet unless you manually configure complex SNAT and MASQUERADE rules.
Automated Continuous Audit with FerroSentry
Auditing every docker-compose.yml across multiple VPS nodes manually is error-prone.
SecuryBlack's open source security auditor, FerroSentry, automatically monitors internal firewall state in real time:
- Compares Docker published ports in
iptablesagainst active UFW rules. - Flags Docker-UFW bypasses as critical security findings.
- Provides the exact copy-paste remediation commands.
Run in under 15 MB RAM with zero lock-in. Connect your first VPS for free or test your external perimeter using our free perimeter analyzer.