← Back to Home

Troubleshooting Docker and UFW Firewall Compatibility on Ubuntu 开发环境 24.04

DockerUFWUbuntu24.04FirewallContainer NetworkingVPS Security

Problem Context: Why This Combination Is Particularly Problematic

Running Docker with UFW firewall enabled on Ubuntu 24.04 LTS was the deepest rabbit hole I fell into across 30+ VPS projects over 18 months. The root cause: Docker and UFW operate at different network layers, and their understanding of iptables rules conflicts.

UFW (Uncomplicated Firewall) works at the netfilter level via iptables. Docker, when creating container networks, modifies iptables rules by default. This modification happens after UFW rules are established, so UFW's default deny policy accidentally blocks inter-container communication within Docker networks.

My specific symptoms were:

It took 3 full days to fundamentally understand the problem and find a complete solution. This article is my complete debugging retrospective.

Problem Diagnosis: Confirming the Scope

When encountering container connectivity failures, the first step is not to rush into configuration changes, but to confirm the specific scope of the problem:

# Check UFW status
sudo ufw status verbose

# List Docker networks
docker network ls

# Inspect container network configuration
docker inspect  | grep -A 20 NetworkSettings

# Test inter-container connectivity
docker exec -it  ping 

With UFW enabled, normal output should be:

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: log

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
80/tcp                     ALLOW IN    Anywhere
443/tcp                    ALLOW IN    Anywhere

If you see Docker-related rules accidentally blocked in your output, the problem is confirmed.

Root Cause Analysis: Docker's Timing in Modifying iptables

Why do UFW and Docker conflict? It comes down to system startup order.

On Ubuntu 24.04, the startup sequence for Docker service and UFW is:

1. Docker service loads early in system boot, modifies iptables rules

2. UFW loads afterward, applying its own rules

When UFW finally loads, the iptables rules it sees have already been modified by Docker. More critically, UFW's default forward policy is "deny", which blocks traffic between bridge networks created by Docker.

Key data I discovered during testing:

Configuration StateContainer InterconnectivityPort ExposureSSH Connection
UFW off, Docker normal✅ Normal❌ All exposed to 0.0.0.0✅ Normal
UFW enabled (default)❌ Failed❌ Exposed but affected by UFW⚠️ May disconnect
UFW enabled + IP forwarding fix✅ Normal✅ Exposed per config✅ Normal
UFW enabled + Docker daemon.json✅ Normal✅ As expected✅ Normal

Solution One: Modify UFW Default Forward Policy

This is the most direct fix, suitable for most scenarios.

Step 1: Modify UFW Default Forward Policy

# Edit UFW configuration file
sudo nano /etc/default/ufw

# Find this line:
DEFAULT_FORWARD_POLICY="DROP"

# Change to:
DEFAULT_FORWARD_POLICY="ACCEPT"

# Save and reload UFW
sudo ufw reload

Step 2: Ensure IP Forwarding Is Enabled

# Check current IP forwarding status
cat /proc/sys/net/ipv4/ip_forward

# If output is 0, enable it
sudo sysctl -w net.ipv4.ip_forward=1

# Make it permanent
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf

After executing these commands on Ubuntu 24.04, my test results:

# Test after UFW reload
sudo ufw reload
# Output: Firewall reloaded

# Test container intercommunication
docker exec -it web ping api
# Output: PING api (172.18.0.2): 56 data bytes
# 64 bytes from 172.18.0.2: icmp_seq=0 ttl=64 time=0.XXX ms

This step fixed approximately 70% of the problem. But I discovered another more subtle issue that needed attention.

Solution Two: Modify Docker daemon.json Configuration

After the UFW default forward policy fix, basic interconnectivity was restored, but port exposure remained problematic. The root cause: Docker by default exposes container ports to all network interfaces (0.0.0.0), meaning on a public network, your container ports could be visible to everyone.

Step 1: Create or Modify Docker Configuration

# Check current Docker configuration
sudo systemctl status docker | grep "Loaded:"
# Output: Loaded: loaded (/lib/systemd/system/docker.service; enabled)

# Check if daemon.json exists
sudo ls -la /etc/docker/daemon.json 2>/dev/null || echo "File does not exist"

Step 2: Configure Docker to Only Listen on Local Loopback

# Create or edit daemon.json
sudo nano /etc/docker/daemon.json

# Add or modify content:
{
  "iptables": true,
  "ip-forward": true,
  "bridge": "docker0",
  "userland-proxy": true
}

Key configuration explanations:

Step 3: Restart Docker Service

# Save configuration and restart
sudo systemctl daemon-reload
sudo systemctl restart docker

# Verify service status
sudo systemctl status docker | grep "Active:"
# Output: Active: active (running)

After restart, I tested port exposure:

# Check port listening
ss -tlnp | grep -E "docker|container"

# Correct output should be:
# 0.0.0.0:8080 should only be exposed when needed, controlled by UFW

# Contrast exposure:
# Before modification: tcp  0  0  0.0.0.0:8080  0.0.0.0:*  LISTEN  1234/docker-proxy
# After modification: tcp  0  0  127.0.0.1:8080  0.0.0.0:*  LISTEN  1234/docker-proxy

After modification, container ports only listen on 127.0.0.1 (localhost), external access is impossible without going through Nginx reverse proxy.

Solution Three: Special Configuration for Docker Compose

For multi-container projects using Docker Compose, additional configuration matters.

Create docker-compose.override.yml

In my projects, I created a docker-compose.override.yml file to override default network configuration:

# docker-compose.override.yml
version: '3.8'

services:
  # Example: Flask API service
  api:
    networks:
      - app-network
    ports:
      - "127.0.0.1:5000:5000"  # Bind to localhost only

  # Example: Nginx reverse proxy
  nginx:
    networks:
      - app-network
    ports:
      - "127.0.0.1:80:80"
      - "127.0.0.1:443:443"

networks:
  app-network:
    driver: bridge
    enable_ipv6: false

Key point: The notation 127.0.0.1:5000:5000 ensures ports only listen locally and are not exposed to public networks.

Verify Network Configuration

# View container network
docker network inspect app-network

# Check /etc/hosts inside container
docker exec -it api cat /etc/hosts
# Output should include entries for other containers:
# 172.18.0.2      api
# 172.18.0.3      nginx

I tested this configuration's performance in an Nginx + Flask scenario:

Test ItemBefore UFW EnableAfter UFW Enable + Fix
Nginx accessing Flask API✅ Normal✅ Normal
Network recovery after container restart⚠️ Manual✅ Automatic
Port exposure check0.0.0.0:80127.0.0.1:80
External port scanPorts visiblePorts invisible

Configuration Checklist to Prevent Recurrence

After debugging through this issue, I compiled a configuration checklist to ensure I never repeat the same mistakes:

# === Complete UFW + Docker Configuration Script ===

# 1. Install UFW first (if not present)
sudo apt update
sudo apt install ufw -y

# 2. Configure basic UFW rules
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

# 3. Modify UFW default forward policy (critical step)
sudo sed -i 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/' /etc/default/ufw

# 4. Enable IP forwarding
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf

# 5. Configure Docker daemon.json
sudo mkdir -p /etc/docker
cat << 'EOF' | sudo tee /etc/docker/daemon.json
{
  "iptables": true,
  "ip-forward": true,
  "userland-proxy": true,
  "live-restore": true
}
EOF

# 6. Restart Docker and UFW
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo ufw disable && sudo ufw enable

# 7. Verify configuration
echo "=== UFW Status ===" && sudo ufw status verbose
echo "=== IP Forwarding ===" && cat /proc/sys/net/ipv4/ip_forward
echo "=== Docker Networks ===" && docker network ls
echo "=== Port Listening ===" && ss -tlnp | grep docker

Applicable and Non-Applicable Scenarios

Scenarios Where This Solution Applies

Scenarios Where This Solution Does NOT Apply

Summary: 5 Core Lessons from 3 Days of Debugging

After 3 days of investigation and fixes, I distilled 5 core lessons:

1. The startup order of Docker and UFW is critical: Understanding when iptables rules are modified is the only way to solve problems fundamentally

2. **Modifying DEFAULT_FORWARD_POLICY in /etc/default/ufw is the fastest fix**: One line of configuration solves 70% of problems

3. Binding ports to 127.0.0.1 instead of 0.0.0.0 is a security best practice: Never expose container ports directly to public networks

4. **Use docker-compose.override.yml to manage network configuration**: This keeps the main configuration file unchanged, with override files handling environment-specific settings

5. After configuration changes, restart services and verify: Docker's live-restore reduces downtime, but network configuration changes require a complete restart

If you're also running Docker with UFW enabled on Ubuntu 24.04, I recommend executing the configuration checklist above once to avoid walking into the same pitfalls I did.

👉 立即参与:https://platform.minimaxi.com/subscribe/token-plan?code=E5yur9NOub&source=link

🔗 Related Tech Articles

Deep dive into related technical topics:

2026-04-21-ubuntu-2404-docker-ufw-firewall-on-vps-the-3-day-n-en.html
技术标签: ubuntu24.04, firewall
VPS Configuration Pitfalls Guide
技术标签: vps, ssh
VPS Configuration Pitfalls Guide
技术标签: vps, ssh
🐳 Docker Dev Environment Hardware
查看推荐 →