Overview
Symptoms:
- docker run can’t reach the internet or other containers
- Errors like: "iptables: not found", or Docker logs show "failed to program iptables rules"
- DNS fails inside containers; publishing ports has no effect
Root causes:
- iptables package missing or wrong backend (legacy vs nft)
- Forwarding/NAT blocked by UFW, firewalld, or a custom nftables ruleset
- Required sysctls or kernel modules disabled (bridge netfilter, ip_forward)
This guide is Linux-focused (host networking). Containers do not need iptables; the host does.
Quickstart (most cases)
- Install iptables userspace
- Debian/Ubuntu:
sudo apt-get update && sudo apt-get install -y iptables - RHEL/CentOS/Rocky/Alma:
sudo dnf install -y iptables iptables-services - Fedora:
sudo dnf install -y iptables-nft
- Ensure Docker can manage iptables
- Check
/etc/docker/daemon.jsoncontains{ "iptables": true }(default). Restart Docker if you change it.
- Enable forwarding and bridge netfilter
sudo modprobe br_netfilter
sudo sysctl -w net.ipv4.ip_forward=1
sudo sysctl -w net.bridge.bridge-nf-call-iptables=1
# Persist
printf "net.ipv4.ip_forward=1\nnet.bridge.bridge-nf-call-iptables=1\n" | sudo tee /etc/sysctl.d/99-docker.conf
sudo sysctl --system
- Relax host firewall for Docker bridges
- UFW:
echo 'DEFAULT_FORWARD_POLICY="ACCEPT"' | sudo tee /etc/default/ufw
sudo ufw allow in on docker0
sudo ufw route allow in on docker0 out on any
sudo ufw reload
- firewalld:
sudo firewall-cmd --permanent --add-masquerade
sudo firewall-cmd --permanent --zone=trusted --add-interface=docker0
sudo firewall-cmd --reload
- Restart Docker and test
sudo systemctl restart docker
Minimal working example (connectivity test)
# Should print an HTTP status line (e.g., HTTP/2 200)
docker run --rm alpine:3.19 sh -lc "apk add -q curl && curl -sI https://example.com | head -n1"
If this fails, proceed to detailed steps below.
Step-by-step fixes
1) Verify kernel and modules
uname -r
lsmod | grep br_netfilter || sudo modprobe br_netfilter
cat /proc/sys/net/ipv4/ip_forward # expect 1
If ip_forward is 0, enable it via sysctl as shown in Quickstart.
2) Ensure iptables is installed and consistent
- Check availability and backend:
iptables -V || echo "iptables missing"
# On nft-based distros, you may see: iptables v1.8.x (nf_tables)
- Install/choose backend:
- Debian/Ubuntu:
sudo apt-get install -y iptables(wrapper supports nft by default). To force legacy:
- Debian/Ubuntu:
sudo apt-get install -y iptables iptables-legacy
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
- Fedora: prefers
iptables-nft. Keep nft unless a vendor requires legacy.
Warning: Mixing legacy and nft backends breaks Docker. Ensure iptables, ip6tables, arptables, and ebtables all point to the same family (legacy or nft).
3) Confirm Docker is allowed to program iptables
sudo jq . /etc/docker/daemon.json 2>/dev/null || echo "{ }"
# Ensure either absent (defaults to true) or explicitly:
# { "iptables": true }
Restart if changed: sudo systemctl restart docker.
4) Make forwarding/NAT work with common firewalls
- UFW specifics:
- UFW defaults to DROP on FORWARD. Set
DEFAULT_FORWARD_POLICY="ACCEPT"and allowdocker0as shown earlier. - Optionally, allow everything going through Docker’s chain first:
- UFW defaults to DROP on FORWARD. Set
sudo iptables -C DOCKER-USER -j ACCEPT 2>/dev/null || sudo iptables -I DOCKER-USER -j ACCEPT
If publishing ports fails, ensure UFW isn’t overriding nat PREROUTING; prefer using DOCKER-USER instead of editing Docker’s own chains.
firewalld specifics:
- Enable masquerade and place Docker bridges in a permissive zone (trusted) or create rules in your active zone.
- If using custom zones, add forward allowance:
sudo firewall-cmd --permanent --zone=public --add-forward
sudo firewall-cmd --reload
- nftables-only setups (no firewalld/UFW):
# Minimal rules: allow forward to/from docker0 and enable NAT masquerade out of your uplink (e.g., eth0)
sudo nft -f - <<'EOF'
add table ip nat
add chain ip nat postrouting { type nat hook postrouting priority 100; }
add rule ip nat postrouting oifname "eth0" masquerade
add table ip filter
add chain ip filter forward { type filter hook forward priority 0; }
add rule ip filter forward iifname "docker0" accept
add rule ip filter forward oifname "docker0" accept
EOF
Replace eth0 with your egress interface.
5) Diagnose conflicting rules
ip addr show docker0
iptables -S | sed -n '1,120p'
iptables -t nat -S | sed -n '1,120p'
nft list ruleset | sed -n '1,200p' | grep -i docker -n || true
journalctl -u docker -b --no-pager | tail -n 100
Look for:
- Missing DOCKER and DOCKER-USER chains
- Policies DROP on FORWARD without Docker’s ACCEPT rules
- nftables rules that shadow Docker’s iptables-nft rules
Performance notes
- Prefer iptables-nft on modern kernels (5.x+); it scales better with large rule sets.
- Keep firewall rules concise. Thousands of unrelated rules slow container start (Docker updates iptables per container/network).
- Avoid verbose logging on FORWARD; it adds CPU overhead under traffic.
- Prune unused networks and containers to reduce rule churn:
docker network prune,docker system prune. - If you manage firewall externally, consider a static ruleset and keep Docker’s
"iptables": true; disabling it (false) breaks bridge/NAT networking.
Common pitfalls
- "iptables not found" inside a container is normal; Docker programs iptables on the host.
- Mixed backends:
iptables -Vsays "legacy" whileip6tables -Vsays "nf_tables" → align them. - Boot races: early firewalls or VPN clients can flush/override Docker rules. Ensure their services start before Docker or restore your rules after they start.
- Cloud SGs/VPC ACLs can block egress/ingress; verify outside the host first.
Tiny FAQ
Q: Can Docker networking work without iptables? A: Not for bridge/NAT. You can use host network or MACVLAN, but typical bridge requires iptables/nftables.
Q: Should I use legacy or nft? A: Use nft unless a specific dependency requires legacy. Keep all tools on the same backend.
Q: How do I reset Docker’s iptables rules? A: sudo systemctl restart docker recreates its chains. Avoid flushing all tables on a remote system unless you have out-of-band access.
Q: Rootless Docker? A: Rootless uses slirp4netns/pasta and does not modify host iptables by default; firewall interactions differ and are usually simpler on the host.