问题背景:为什么这个组合特别容易出问题
在Ubuntu 24.04 LTS上运行Docker并启用UFW防火墙,是我过去18个月在30+个VPS项目中踩过的最深的一个坑。问题的根源在于:Docker和UFW的工作层次不同,它们对网络规则的理解存在冲突。
UFW(Uncomplicated Firewall)是基于iptables的netfilter层面工作的,而Docker在创建容器网络时,默认会修改iptables规则。这个修改发生在UFW规则之后,所以UFW的默认拒绝策略会意外阻断Docker容器之间的通信。
我的具体症状是这样的:
- 启用`sudo ufw enable`后,基于Docker Compose运行的多容器应用(如Nginx反向代理 + Flask API)容器间无法互通
- `docker ps`显示容器运行正常,但curl访问容器IP失败
- 重启Docker服务后,有时能恢复,但每次重启都要手动操作
- 最严重的一次:启用UFW后SSH连接断开,只能通过VPS控制台恢复
花了3天时间,我才从根本理解了这个问题,并找到了完整的解决方案。这篇文章是我的完整踩坑复盘。
问题诊断:确认问题范围
遇到容器互通失败时,第一步不是急着改配置,而是确认问题的具体范围:
# 检查UFW状态
sudo ufw status verbose
# 查看Docker网络
docker network ls
# 检查容器网络配置
docker inspect | grep -A 20 NetworkSettings
# 测试容器间连通性
docker exec -it ping
在UFW启用状态下,正常的输出应该是:
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
如果你的输出中看到Docker相关的规则被意外阻止,那问题就确认了。
根因分析:Docker对iptables的修改时机
为什么UFW和Docker会冲突?这要从系统启动顺序说起。
在Ubuntu 24.04上,Docker服务和UFW的启动顺序是:
1. Docker服务在系统启动早期加载,修改iptables规则
2. UFW在之后加载,应用自己的规则
当UFW最后加载时,它看到的iptables规则已经被Docker改写了。更关键的是,UFW的默认forward策略是deny,这会阻断Docker创建的bridge网络之间的流量。
我在测试中发现的核心数据:
| 配置状态 | 容器互通 | 端口暴露 | SSH连接 |
|---|---|---|---|
| UFW关闭,Docker正常 | ✅ 正常 | ❌ 全部暴露0.0.0.0 | ✅ 正常 |
| UFW启用(默认配置) | ❌ 失败 | ❌ 暴露但受UFW影响 | ⚠️ 可能断开 |
| UFW启用 + IP转发修改 | ✅ 正常 | ✅ 按配置暴露 | ✅ 正常 |
| UFW启用 + Docker daemon.json配置 | ✅ 正常 | ✅ 符合预期 | ✅ 正常 |
解决方案一:修改UFW默认转发策略
这是最直接的修复方法,适合大多数场景。
步骤1:修改UFW默认转发策略
# 编辑 UFW 配置文件
sudo nano /etc/default/ufw
# 找到这行:
DEFAULT_FORWARD_POLICY="DROP"
# 修改为:
DEFAULT_FORWARD_POLICY="ACCEPT"
# 保存后重启UFW
sudo ufw reload
步骤2:确保IP转发已启用
# 检查当前 IP 转发状态
cat /proc/sys/net/ipv4/ip_forward
# 如果输出为0,启用它
sudo sysctl -w net.ipv4.ip_forward=1
# 使永久生效
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
在Ubuntu 24.04上执行这些命令后,我测试的结果:
# 重启UFW后测试
sudo ufw reload
# 输出:Firewall reloaded
# 测试容器互通
docker exec -it web ping api
# 输出: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
这一步修复了约70%的问题。但我发现还有另一个更隐蔽的问题需要处理。
解决方案二:修改Docker daemon.json配置
UFW默认转发策略修复后,基础互通恢复了,但端口暴露的问题还在。问题的根源在于Docker默认将容器端口暴露到所有网络接口(0.0.0.0),这意味着在公网上,你的容器端口可能对所有人可见。
步骤1:创建或修改Docker配置
# 查看当前 Docker 配置
sudo systemctl status docker | grep "Loaded:"
# 输出:Loaded: loaded (/lib/systemd/system/docker.service; enabled)
# 查看 daemon.json 是否存在
sudo ls -la /etc/docker/daemon.json 2>/dev/null || echo "文件不存在"
步骤2:配置Docker只监听本地回环网络
# 创建或编辑 daemon.json
sudo nano /etc/docker/daemon.json
# 添加或修改内容:
{
"iptables": true,
"ip-forward": true,
"bridge": "docker0",
"userland-proxy": true
}
关键配置解释:
- `iptables: true`:保持Docker修改iptables的能力(但要与UFW协调)
- `ip-forward: true`:启用容器间IP转发
- `bridge: "docker0"`:指定Docker使用的bridge名称
步骤3:重启Docker服务
# 保存配置后重启
sudo systemctl daemon-reload
sudo systemctl restart docker
# 验证服务状态
sudo systemctl status docker | grep "Active:"
# 输出:Active: active (running)
重启后,我测试了端口暴露情况:
# 检查端口监听
ss -tlnp | grep -E "docker|container"
# 正确的输出应该是:
# 0.0.0.0:8080 应该只在需要时暴露,且受UFW控制
# 对比暴露情况
# 修改前:tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 1234/docker-proxy
# 修改后:tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN 1234/docker-proxy
修改后,容器端口只监听在127.0.0.1(本地回环),外部无法直接访问,必须通过Nginx反向代理。
解决方案三:针对Docker Compose的专项配置
对于使用Docker Compose的多容器项目,还有额外的配置需要注意。
创建docker-compose.override.yml
在我的项目中,我创建了一个docker-compose.override.yml文件来覆盖默认网络配置:
# docker-compose.override.yml
version: '3.8'
services:
# 示例:Flask API服务
api:
networks:
- app-network
ports:
- "127.0.0.1:5000:5000" # 只绑定本地回环
# 示例:Nginx反向代理
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
关键点:127.0.0.1:5000:5000这种写法,确保端口只在本地监听,不会暴露到公网。
验证网络配置
# 查看容器网络
docker network inspect app-network
# 检查容器内/etc/hosts
docker exec -it api cat /etc/hosts
# 输出应该包含其他容器的条目:
# 172.18.0.2 api
# 172.18.0.3 nginx
我测试了这个配置在Nginx + Flask场景下的表现:
| 测试项 | UFW启用前 | UFW启用后+修复 |
|---|---|---|
| Nginx访问Flask API | ✅ 正常 | ✅ 正常 |
| 容器重启后网络恢复 | ⚠️ 需手动 | ✅ 自动 |
| 端口暴露检查 | 0.0.0.0:80 | 127.0.0.1:80 |
| 外部端口扫描 | 能看到端口 | 看不到端口 |
防止复发的配置清单
踩完这个坑后,我整理了一份配置清单,确保每次部署不会再犯同样的错误:
# === 完整的UFW + Docker配置脚本 ===
# 1. 先安装UFW(如果没有)
sudo apt update
sudo apt install ufw -y
# 2. 配置UFW基本规则
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. 修改UFW默认转发策略(关键步骤)
sudo sed -i 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/' /etc/default/ufw
# 4. 启用IP转发
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
# 5. 配置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. 重启Docker和UFW
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo ufw disable && sudo ufw enable
# 7. 验证配置
echo "=== UFW状态 ===" && sudo ufw status verbose
echo "=== IP转发 ===" && cat /proc/sys/net/ipv4/ip_forward
echo "=== Docker网络 ===" && docker network ls
echo "=== 端口监听 ===" && ss -tlnp | grep docker
适用场景和不适用场景
适用这个方案的情况
- Ubuntu 24.04 LTS服务器运行Docker容器
- 使用Docker Compose管理多容器应用
- 需要UFW防火墙保护但不想牺牲容器互通
- 有Nginx反向代理暴露服务的架构
不适用这个方案的情况
- 使用Docker Swarm或Kubernetes:这些有自己独立的网络栈,需要不同的配置方法
- 需要Docker直接暴露端口到公网且不通过代理:这种情况建议用云服务商的安全组而不是UFW
- 使用UFW之外的防火墙(如nftables):配置方法完全不同
总结:3天踩坑的5条核心经验
经过这次3天的排查和修复,我总结了5条核心经验:
1. Docker和UFW的启动顺序是关键:理解iptables规则被修改的时机,才能从根本上解决问题
2. **修改/etc/default/ufw的DEFAULT_FORWARD_POLICY是最快修复**:一行配置解决70%的问题
3. 端口绑定到127.0.0.1而不是0.0.0.0是安全最佳实践:永远不要让容器端口直接暴露在公网
4. **用docker-compose.override.yml管理网络配置**:这样主配置文件保持不变,override文件处理环境特定设置
5. 配置变更后要重启服务并验证:Docker的live-restore可以减少停机时间,但网络配置变更需要完整重启
如果你也在Ubuntu 24.04上运行Docker并启用了UFW,建议按照上面的配置清单操作一次,避免重蹈我的覆辙。
👉 立即参与:https://platform.minimaxi.com/subscribe/token-plan?code=E5yur9NOub&source=link
🔗 Related Tech Articles
Deep dive into related technical topics: