Secure Docker Host: iptables Rules for MySQL and SSH/SFTP While Maintaining Container Internet Access


2 views

When securing a Docker host running MySQL and SSH/SFTP services, we face a fundamental network security dilemma: Docker automatically manages iptables rules, which can conflict with manual firewall configurations. The core requirements are:

  • Allow inbound traffic only on ports 22 (SSH/SFTP) and 3306 (MySQL)
  • Maintain outbound internet access for containers
  • Prevent Docker from bypassing our firewall rules

Docker creates several chains in the nat and filter tables by default:

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

The critical chains are DOCKER, DOCKER-USER (filter table), and DOCKER (nat table). Docker inserts rules in these chains to manage container networking.

Here's a working solution that preserves container internet access while restricting inbound traffic:

# Flush existing rules (careful with production systems!)
iptables -F
iptables -t nat -F

# Set default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Allow established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow loopback
iptables -A INPUT -i lo -j ACCEPT

# Allow SSH and MySQL
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 3306 -j ACCEPT

# Docker-specific rules
iptables -N DOCKER-USER
iptables -A DOCKER-USER -i eth0 -j RETURN

# Allow container outbound traffic
iptables -A FORWARD -i docker0 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o docker0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Persist rules (Ubuntu)
iptables-save | sudo tee /etc/iptables/rules.v4

Ensure your Docker daemon isn't fighting with these rules by adding this to /etc/docker/daemon.json:

{
  "iptables": true,
  "userland-proxy": false
}

After applying these rules, test both requirements:

  1. External access to MySQL and SSH/SFTP should work
  2. Containers should maintain internet access

Test container internet access:

docker run --rm alpine ping -c 4 google.com

For a more Docker-friendly approach that works with dynamic ports:

# Reset DOCKER-USER chain
iptables -F DOCKER-USER

# Allow SSH and MySQL through docker-proxy
iptables -A DOCKER-USER -p tcp --dport 22 -j ACCEPT
iptables -A DOCKER-USER -p tcp --dport 3306 -j ACCEPT

# Block all other inbound traffic to Docker
iptables -A DOCKER-USER -j DROP

# Save rules
iptables-save | sudo tee /etc/iptables/rules.v4

For your specific docker-compose.yml, ensure the network configuration doesn't interfere:

networks:
  database:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.enable_ip_masquerade: "true"
  • Consider adding rate limiting for SSH (--limit 30/min)
  • For production, implement fail2ban for SSH
  • Regularly audit your iptables rules (iptables-save)
  • Monitor logs for unauthorized access attempts

Docker manipulates iptables rules automatically to facilitate container networking. When you expose ports like 3306 and 22 through docker-compose, Docker creates NAT rules in the NAT table that bypass traditional INPUT chain rules. This explains why you might see open ports despite your iptables configuration.

Instead of fighting Docker's networking, work with it by setting up these rules after Docker's initialization:

# Flush existing rules
iptables -F
iptables -X

# Set default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Allow SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Allow MySQL
iptables -A INPUT -p tcp --dport 3306 -j ACCEPT

# Allow ICMP (ping)
iptables -A INPUT -p icmp -j ACCEPT

# Docker specific rules
iptables -A FORWARD -i docker0 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o docker0 -j ACCEPT

# Allow containers to access internet
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

To make these rules persistent:

apt-get install iptables-persistent
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6

For more granular control over Docker's port exposure, consider these additional measures:

# Create a new chain for Docker filtering
iptables -N DOCKER-USER
iptables -I DOCKER-USER -j RETURN

# Block all incoming traffic to Docker containers by default
iptables -I DOCKER-USER -i eth0 ! -s 127.0.0.1 -j DROP

# Explicitly allow MySQL and SSH traffic
iptables -I DOCKER-USER -i eth0 -p tcp --dport 22 -j ACCEPT
iptables -I DOCKER-USER -i eth0 -p tcp --dport 3306 -j ACCEPT

Use these commands to check your rules:

iptables -L -n -v
iptables -t nat -L -n -v
conntrack -L

For simpler setups, you can leverage Docker's built-in capabilities:

# In your docker-compose.yml
services:
  mysql:
    ports:
      - "127.0.0.1:3306:3306"  # Only binds to localhost