BlogCybersecurity
Cybersecurity

UFW + Docker: Why Your Firewall Rules Are Being Completely Bypassed (And How to Fix It)

You configured UFW to block all incoming traffic except SSH and HTTP, but Docker containers are accessible on every published port. Docker manipulates iptables directly, bypassing UFW entirely. Here is the fix.

S

Sarah Chen

Senior Cybersecurity Engineer with 12+ years of experience in penetration testing and security architecture.

January 31, 2026
14 min read

You set up a Linux server, configure UFW (Uncomplicated Firewall) to allow only SSH on port 22 and HTTP/HTTPS on ports 80 and 443, and verify that all other ports are blocked. Then you deploy a Docker container that publishes port 5432 for PostgreSQL. You assume the firewall will block external access to port 5432 — but it does not. Your database is accessible to the entire internet.

This is one of the most dangerous and least understood security issues in Docker deployments. Docker modifies iptables rules directly, creating DOCKER chains that take priority over UFW's rules. Your firewall is technically still running, but Docker's iptables rules are evaluated before UFW's rules, effectively making your firewall invisible for any port published by Docker.

Why This Happens: iptables Chain Priority

Linux uses iptables (or its successor nftables) for packet filtering. Iptables has several built-in chains that packets traverse in a specific order: PREROUTING, INPUT, FORWARD, OUTPUT, and POSTROUTING. UFW adds rules to the INPUT chain, which handles traffic destined for the host machine.

Docker, however, routes container traffic through the FORWARD chain, not the INPUT chain. When you publish a port with -p 5432:5432, Docker creates NAT rules in the PREROUTING chain that redirect incoming traffic on port 5432 to the container, and FORWARD rules that allow this traffic. The packets never reach the INPUT chain where UFW's rules live.

# See Docker's iptables rules
sudo iptables -L -n -v
sudo iptables -t nat -L -n -v

# Notice the DOCKER chain and DOCKER-USER chain
sudo iptables -L DOCKER -n -v
sudo iptables -L DOCKER-USER -n -v

This is by design — Docker needs to manage its own networking. But the result is that any security administrator who relies solely on UFW for access control has a massive blind spot.

How Bad Is This Problem?

To demonstrate the severity, here is what happens on a typical server:

# Configure UFW to deny everything except SSH and HTTP
sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status  # Shows only 22, 80, 443 allowed

# Start a container with a published port
docker run -d -p 5432:5432 postgres:16

# From another machine, try to connect
nmap -p 5432 your-server-ip
# Result: Port 5432 is OPEN, despite UFW denying it

Now imagine this with Redis on port 6379 (default: no password), MongoDB on port 27017 (default: no authentication), or an admin panel on port 8080. This is not a theoretical risk — Shodan searches reveal millions of Docker-exposed databases and services on the internet.

Solution 1: Never Publish Ports to 0.0.0.0

The simplest fix is to never publish container ports to all interfaces. Instead, bind to localhost (127.0.0.1) so the port is only accessible from the host machine:

# BAD: Accessible from anywhere (bypasses UFW)
ports:
  - "5432:5432"

# GOOD: Only accessible from localhost
ports:
  - "127.0.0.1:5432:5432"

For services that need to be accessed externally (like a web application), use Nginx as a reverse proxy. Nginx listens on ports 80/443 (allowed by UFW), and the application container only publishes to localhost:

# docker-compose.yml
services:
  web:
    image: myapp:latest
    ports:
      - "127.0.0.1:3000:3000"  # Only accessible via Nginx, not directly
  
  db:
    image: postgres:16
    ports:
      - "127.0.0.1:5432:5432"  # Only accessible from the host

This approach is the most reliable because it does not depend on iptables rules — the port is simply not listening on external interfaces.

Solution 2: Use Docker Networks Instead of Published Ports

An even better approach for inter-container communication is to not publish ports at all. Use Docker networks so containers can communicate with each other by service name without exposing any ports to the host:

services:
  web:
    image: myapp:latest
    ports:
      - "127.0.0.1:3000:3000"  # Only this service needs a published port
    networks:
      - app-network
  
  db:
    image: postgres:16
    # NO ports published — only accessible from other containers on the same network
    networks:
      - app-network
  
  redis:
    image: redis:7-alpine
    # NO ports published
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

In this configuration, the web container can connect to PostgreSQL at db:5432 and Redis at redis:6379 using the service names as hostnames. But neither PostgreSQL nor Redis is accessible from outside the Docker network — not even from the host machine.

Solution 3: The DOCKER-USER Chain

Docker provides the DOCKER-USER iptables chain specifically for user-defined firewall rules. Rules in DOCKER-USER are evaluated before Docker's own rules, giving you a way to filter traffic to containers:

# Allow established connections (important!)
sudo iptables -I DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN

# Allow traffic from the internal Docker network
sudo iptables -I DOCKER-USER -s 172.16.0.0/12 -j RETURN

# Allow traffic from your trusted IP
sudo iptables -I DOCKER-USER -s YOUR_OFFICE_IP -j RETURN

# Allow traffic to web ports (80, 443)
sudo iptables -I DOCKER-USER -p tcp --dport 80 -j RETURN
sudo iptables -I DOCKER-USER -p tcp --dport 443 -j RETURN

# Drop everything else to containers
sudo iptables -A DOCKER-USER -j DROP

The DOCKER-USER rules persist across Docker restarts (Docker recreates its own chains but does not touch DOCKER-USER). However, they do not persist across system reboots unless you save them.

Save the rules so they survive reboots:

# Install iptables-persistent
sudo apt install iptables-persistent -y

# Save current rules
sudo netfilter-persistent save

Solution 4: Disable Docker's iptables Management

You can tell Docker to stop managing iptables entirely by adding "iptables": false to /etc/docker/daemon.json:

{
  "iptables": false
}

After restarting Docker, it will no longer create any iptables rules. This means UFW will work as expected, but you must manually configure networking for containers. Container-to-container communication, port publishing, and internet access from containers will need manual iptables rules. This approach is only recommended for experienced administrators who are comfortable managing iptables directly.

Solution 5: Use the ufw-docker Utility

The ufw-docker project provides a wrapper that makes UFW and Docker work together correctly. It modifies UFW's after.rules to properly filter Docker traffic:

# Install ufw-docker
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

# Install the UFW integration
sudo ufw-docker install

# Restart UFW
sudo systemctl restart ufw

# Now manage Docker container access through ufw-docker
sudo ufw-docker allow mycontainer 80/tcp
sudo ufw-docker allow mycontainer 443/tcp

Verification

After implementing any of these solutions, verify from an external machine that only the intended ports are accessible:

# From another machine or your local computer
nmap -p 1-65535 your-server-ip

# Or use an online port scanner
# https://www.yougetsignal.com/tools/open-ports/

Only ports 22, 80, and 443 should be open. If you see other ports open (especially database ports like 3306, 5432, 27017, or 6379), your firewall is being bypassed and immediate action is required.

ZeonEdge provides server security hardening, firewall configuration, and Docker security audits. Learn more about our security services.

S

Sarah Chen

Senior Cybersecurity Engineer with 12+ years of experience in penetration testing and security architecture.

Related Articles

Cloud & Infrastructure

DNS Deep Dive in 2026: How DNS Works, How to Secure It, and How to Optimize It

DNS is the invisible infrastructure that makes the internet work. Every website visit, every API call, every email delivery starts with a DNS query. Yet most developers barely understand how DNS works, let alone how to secure it. This exhaustive guide covers DNS resolution, record types, DNSSEC, DNS-over-HTTPS, DNS-over-TLS, split-horizon DNS, DNS-based load balancing, failover strategies, and common misconfigurations.

Marcus Rodriguez•42 min read
Business Technology

Self-Hosting in 2026: The Complete Guide to Running Your Own Services

Why pay monthly SaaS fees when you can run the same (or better) services on your own hardware? This comprehensive guide covers self-hosting everything from email and file storage to Git repositories, project management, analytics, and monitoring. Learn about hardware selection, Docker Compose configurations, reverse proxy setup with Nginx, SSL certificates, backup strategies, and maintaining uptime.

Alex Thompson•42 min read
Best Practices

Data Privacy Engineering and GDPR Compliance in 2026: A Developer's Complete Guide

Data privacy regulations are becoming stricter and more widespread. GDPR, CCPA, LGPD, and India's DPDPA create a complex web of requirements for any application that handles personal data. This technical guide covers privacy-by-design architecture, data classification, consent management, right-to-erasure implementation, data minimization, pseudonymization, encryption strategies, breach notification workflows, and audit logging.

Emily Watson•38 min read

Ready to Transform Your Infrastructure?

Let's discuss how we can help you achieve similar results.