修复Docker绕过UFW防火墙:4种经过测试的VPS解决方案
Docker直接操作iptables并忽略UFW规则。即使启用了ufw deny,你的容器端口仍然暴露在互联网上。这里提供四种解决方案及其权衡,每种都通过外部主机扫描验证。
你的UFW防火墙在骗你。如果你在启用了UFW的VPS上运行Docker,每个发布的容器端口都对互联网完全开放。运行ufw deny 8080没有任何效果。Docker完全绕过了UFW。
本教程先展示问题,然后介绍四种解决方案及其不同的权衡。每种方案都包含验证步骤:从外部主机扫描端口,确认它确实被阻止了。不只是检查ufw status。
本指南适用于Ubuntu 24.04和Debian 12。两者默认使用iptables兼容层,因此相同的修复方法都适用。Debian 12默认使用nftables作为后端,但UFW和Docker都通过iptables接口交互,该接口透明地映射到nftables。
**前提条件:**一台安装了Docker、启用了UFW并有SSH访问权限的VPS。一台第二台机器(你的笔记本电脑或另一台服务器)用于外部端口扫描。
为什么Docker会绕过UFW防火墙规则?
Docker在nat和filter表中写入iptables规则来将流量路由到容器。UFW只管理INPUT链。当数据包到达容器发布的端口时,Docker的NAT规则通过FORWARD链重定向它,在UFW的INPUT规则看到之前就已经处理了。数据包永远不会到达UFW。这意味着ufw deny对Docker发布的端口完全无效。
以下是请求到达VPS端口8080时的数据包流程,其中容器通过-p 8080:80发布:
- 数据包到达网络接口
- 进入
nat表的PREROUTING链 - Docker的DNAT规则将目标重写为容器IP(例如
172.17.0.2:80) - 数据包进入
filter表的FORWARD链(不是INPUT) - Docker的
DOCKER链接受转发的数据包 - 数据包到达容器
UFW永远看不到它,因为UFW只监控INPUT链。数据包走的是FORWARD路径。
这不是bug。Docker需要iptables控制来使容器网络正常工作。但如果你以为UFW在保护你的服务器,这就会造成严重的安全漏洞。
如何验证Docker正在绕过我的UFW防火墙?
在应用任何修复之前,先亲自看看问题。启动一个在8080端口提供HTTP服务的测试容器:
docker run -d --name ufw-test -p 8080:80 nginx:alpine
检查UFW是否处于活动状态并默认拒绝传入流量:
sudo ufw status verbose
你应该在输出中看到Default: deny (incoming)。现在显式拒绝8080端口:
sudo ufw deny 8080
检查UFW是否显示端口已被阻止:
sudo ufw status | grep 8080
8080 DENY Anywhere
8080 (v6) DENY Anywhere (v6)
UFW报告端口已被拒绝。现在从你的本地机器(不是服务器)测试:
curl -s -o /dev/null -w "%{http_code}" http://YOUR_SERVER_IP:8080
200
响应是200。尽管UFW拒绝了该端口,容器仍然可以从互联网完全访问。如果你的本地机器安装了nmap:
nmap -p 8080 YOUR_SERVER_IP
PORT STATE SERVICE
8080/tcp open http-proxy
端口是开放的。UFW没有保护它。
删除deny规则(接下来你将应用真正的修复):
sudo ufw delete deny 8080
保持测试容器运行。你将用它来验证每种解决方案。
应该使用哪种Docker UFW修复方案?
有四种解决方案。每种都有不同的权衡。选择适合你设置的方案。
| 解决方案 | 网络影响 | 重启后保留 | 兼容Compose | 复杂度 | 适用场景 |
|---|---|---|---|---|---|
| DOCKER-USER链 | 无 | 是 | 是 | 中等 | 完全控制,多个公开端口 |
| ufw-docker工具 | 无 | 是 | 是 | 低 | 自动化管理,动态容器 |
| iptables=false | 破坏容器间网络,容器无法访问互联网 | 是 | 是 | 低 | 隔离的单个容器(少见) |
| 绑定到127.0.0.1 | 无 | 是 | 是 | 低 | 反向代理后面的服务 |
快速决策:
- 所有容器都在反向代理(Nginx、Traefik、Caddy)后面?使用127.0.0.1绑定。这是最简单的方法。
- 需要某些容器在特定端口上公开访问?使用DOCKER-USER链进行手动控制,或使用ufw-docker进行自动化管理。
- 运行的容器绝不能访问网络?使用iptables=false,但要理解其权衡。
如何使用DOCKER-USER链修复Docker绕过UFW的问题?
DOCKER-USER链是Docker为管理员预留的空占位符。你在这里添加的规则会在Docker自己的转发规则之前被评估。通过在/etc/ufw/after.rules中插入UFW集成规则,你可以让Docker流量经过UFW。
这种方法给你完全的控制权。它同时适用于docker run和Docker Compose。它在重启后仍然有效,因为规则在UFW启动时加载。
打开/etc/ufw/after.rules:
sudo cp /etc/ufw/after.rules /etc/ufw/after.rules.bak
sudo nano /etc/ufw/after.rules
在文件末尾、现有COMMIT行之后添加以下内容:
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j RETURN
-A DOCKER-USER -m conntrack --ctstate INVALID -j DROP
-A DOCKER-USER -i docker0 -o docker0 -j ACCEPT
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -m conntrack --ctstate NEW -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -m conntrack --ctstate NEW -d 172.16.0.0/12
-A DOCKER-USER -j ufw-docker-logging-deny -m conntrack --ctstate NEW -d 192.168.0.0/16
-A DOCKER-USER -j RETURN
-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP
COMMIT
# END UFW AND DOCKER
这个配置块的作用:
-A DOCKER-USER -j ufw-user-forward:先将Docker流量发送到UFW的转发规则conntrack --ctstate RELATED,ESTABLISHED:允许已建立连接的回程流量(在规则更改后保持现有连接)conntrack --ctstate INVALID:丢弃畸形数据包-i docker0 -o docker0:允许同一bridge网络上的容器间通信-j RETURN -s行允许来自私有子网的流量- 从外部到Docker子网的新连接(
ctstate NEW)被记录并丢弃 - 日志规则限制为每分钟3条消息,避免日志泛滥
重新加载UFW以应用新规则:
sudo ufw reload
验证规则已加载:
sudo iptables -L DOCKER-USER -n -v
你应该在链输出中看到你的新规则。如果链只显示默认的RETURN规则,说明after.rules块没有正确加载。检查文件语法。
通过UFW允许特定容器端口
DOCKER-USER链激活后,所有容器端口默认被阻止外部访问。要允许特定端口,你需要引用容器IP和容器端口,而不是主机端口。原因是Docker在PREROUTING链中的DNAT规则在数据包到达FORWARD链之前就重写了目标。当你的规则被评估时,目标已经是容器的内部地址。
首先,找到容器IP:
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ufw-test
172.17.0.2
然后允许流量到达该容器:
sudo ufw route allow proto tcp from any to 172.17.0.2 port 80
验证UFW显示路由规则:
sudo ufw status | grep "ALLOW FWD"
172.17.0.2 80/tcp ALLOW FWD Anywhere
注意:由于规则针对的容器IP可能在重启时更改,请使用ufw-docker工具(下一节)进行自动IP跟踪。手动DOCKER-USER方法更适合你自己管理容器IP的情况(Docker Compose中的静态IP或当你编写脚本更新规则时)。
从外部主机验证
从你的本地机器测试允许的端口:
curl -s -o /dev/null -w "%{http_code}" http://YOUR_SERVER_IP:8080
200
端口可以访问,因为你显式允许了到容器的转发。现在测试一个未允许的端口(例如,如果你有一个运行在9090端口的容器):
nmap -p 9090 YOUR_SERVER_IP
PORT STATE SERVICE
9090/tcp filtered unknown
filtered表示防火墙正在静默丢弃数据包。DOCKER-USER链正在工作。
如何使用ufw-docker工具管理容器防火墙规则?
chaifeng/ufw-docker工具自动化了DOCKER-USER链的设置,并提供命令来允许或拒绝容器端口。它为你修改/etc/ufw/after.rules并添加CLI来管理每个容器的规则。
安装:
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
执行前先检查脚本。这是一个shell脚本,你可以用cat /usr/local/bin/ufw-docker查看。
运行安装程序来修补/etc/ufw/after.rules:
sudo ufw-docker install
这会添加类似上一节的DOCKER-USER链规则,包括基于conntrack的过滤和日志记录。它还会修补after6.rules以支持IPv6。重新加载UFW:
sudo ufw reload
验证安装:
sudo ufw-docker check
管理容器端口
允许外部访问名为web的容器的80端口:
sudo ufw-docker allow web 80/tcp
列出容器的规则:
sudo ufw-docker list web
删除规则:
sudo ufw-docker delete allow web 80/tcp
显示所有Docker防火墙规则:
sudo ufw-docker status
使用ufw-docker的Docker Compose示例
services:
web:
image: nginx:alpine
container_name: web
ports:
- "8080:80"
restart: unless-stopped
启动stack,然后允许端口:
docker compose up -d
sudo ufw-docker allow web 80/tcp
注意:ufw-docker命令引用的是容器端口(80),不是主机端口(8080)。工具会自动解析映射。
替代方案:ufw-docker-automated
shinebayar-g/ufw-docker-automated项目采用不同的方法。它本身作为Docker容器运行,监听Docker API事件,并在容器启动或停止时自动创建UFW规则。这解决了ufw-docker工具的一个限制:当容器重启并获得新IP时,旧规则变得无效。
使用ufw-docker-automated,你可以给容器添加标签:
services:
web:
image: nginx:alpine
ports:
- "8080:80"
labels:
- "UFW_MANAGED=TRUE"
restart: unless-stopped
该工具监控带有UFW_MANAGED=TRUE标签的容器,并自动创建匹配的UFW规则。它还支持UFW_ALLOW_FROM来限制特定IP或CIDR范围的访问。
从外部主机验证
使用ufw-docker允许端口后,从你的本地机器确认:
curl -s -o /dev/null -w "%{http_code}" http://YOUR_SERVER_IP:8080
200
测试一个未列出的端口以确认它被阻止:
nmap -p 9090 YOUR_SERVER_IP
PORT STATE SERVICE
9090/tcp filtered unknown
如果禁用Docker的iptables管理会怎样?
在Docker的守护进程配置中设置"iptables": false会阻止Docker创建任何iptables规则。这是最简单的修复,但副作用也最严重。
会出现的问题:
- 容器无法访问互联网(没有masquerading规则)
- 不同网络之间的容器间通信停止工作
- 端口发布(
-p)完全停止工作。你必须自己管理所有转发规则。
这种方法仅适用于运行不需要互联网访问的隔离容器,并且你手动处理所有网络配置的情况。
编辑或创建Docker守护进程配置:
sudo nano /etc/docker/daemon.json
{
"iptables": false
}
如果文件已有内容,将"iptables": false键添加到现有JSON对象中。不要创建第二个JSON对象。
重启Docker:
sudo systemctl restart docker
验证Docker没有创建iptables规则:
sudo iptables -L DOCKER -n 2>/dev/null
在Docker 28.x中,DOCKER链可能仍然存在,但应该只包含一条DROP规则,而不是通常的转发规则。没有ACCEPT规则意味着Docker没有将流量路由到容器。
从外部主机验证
重新启动测试容器(Docker重启时它已停止):
docker start ufw-test
从你的本地机器:
nmap -p 8080 YOUR_SERVER_IP
PORT STATE SERVICE
8080/tcp closed http-proxy
closed(不是filtered),因为Docker不再创建NAT规则来转发流量。从外部角度看,端口没有在监听。
要恢复,从daemon.json中删除"iptables": false并重启Docker:
sudo systemctl restart docker
如何将Docker Compose端口仅绑定到localhost?
对于反向代理(Nginx、Traefik、Caddy)后面的容器,最简单的修复是一开始就不要将端口发布到0.0.0.0。改为绑定到127.0.0.1。反向代理通过localhost连接到容器。外部流量通过80/443端口到达反向代理,UFW正常控制这些端口。
这不需要修改iptables,不需要额外工具,适用于所有操作系统。
在你的docker-compose.yml中,更改端口绑定:
services:
app:
image: your-app:latest
ports:
- "127.0.0.1:8080:80"
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
restart: unless-stopped
app服务仅绑定到127.0.0.1:8080。它无法从服务器外部访问。nginx服务绑定到0.0.0.0:80和0.0.0.0:443(未指定IP时的默认值),你用UFW控制这些端口。
对于docker run,等效语法:
docker run -d --name app -p 127.0.0.1:8080:80 your-app:latest
验证绑定
在服务器上,确认端口仅绑定到localhost:
ss -tlnp | grep 8080
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("docker-proxy",pid=12345,fd=4))
注意看:监听地址是127.0.0.1:8080,不是0.0.0.0:8080。这意味着只接受本地连接。
从外部主机验证
从你的本地机器:
nmap -p 8080 YOUR_SERVER_IP
PORT STATE SERVICE
8080/tcp closed http-proxy
端口对外部流量关闭。你的反向代理在80和443端口处理公共访问,UFW正常保护这些端口。
这是大多数VPS设置的推荐方法,适用于在反向代理后面运行Web应用的场景。
故障排除
Docker重启后规则消失
如果你使用了DOCKER-USER链方法但规则在Docker重启后消失,检查你的规则是否在/etc/ufw/after.rules中,而不是用iptables命令手动添加的。手动iptables规则不会在服务重启后保留。after.rules文件在UFW每次启动时加载。
sudo ufw reload
sudo iptables -L DOCKER-USER -n -v
应用DOCKER-USER规则后容器无法解析DNS
after.rules块包含一条conntrack --ctstate RELATED,ESTABLISHED规则,允许DNS响应流量返回容器。如果容器内DNS解析失败,验证conntrack规则是否已加载:
sudo iptables -L DOCKER-USER -n -v | grep "RELATED,ESTABLISHED"
如果缺失,检查after.rules块的语法并重新加载UFW。
ufw-docker命令失败并显示"container not found"
ufw-docker allow命令需要容器正在运行。它将容器名称解析为IP地址。如果容器已停止,命令会失败。先启动容器,然后添加规则。
Debian 12 nftables兼容性
Debian 12使用nftables作为防火墙后端,但UFW和Docker都使用iptables兼容层(iptables-nft)。本指南中的解决方案在Debian 12和Ubuntu 24.04上完全相同。验证你是否在使用nft后端:
sudo iptables --version
iptables v1.8.10 (nf_tables)
(nf_tables)后缀确认iptables-nft兼容层处于活动状态。所有DOCKER-USER链规则都通过此层工作。
检查被阻止流量的日志
如果在应用DOCKER-USER链修复后连接意外被阻止,检查UFW Docker日志:
sudo journalctl -k | grep "UFW DOCKER BLOCK"
日志条目显示被阻止数据包的源IP和目标端口。
清理测试资源
完成后删除测试容器:
docker rm -f ufw-test
如果在测试期间添加了UFW deny规则,删除它:
sudo ufw delete deny 8080
Copyright 2026 Virtua.Cloud. All rights reserved.