ufw 与 nftables 防火墙实战:默认拒绝策略、Docker 绕过 ufw 的坑与远程配置保命三招

把服务器锁起来的第一次尝试,往往是把 SSH 也一起锁了

每个站长都听人说过「服务器要开防火墙」。于是很多人 SSH 上去,敲一句 ufw enable,看到提示 Command may disrupt existing ssh connections. Proceed with operation (y|n)? 随手点了 y,然后——当前这个 SSH 会话还活着(因为 ufw 默认不会立刻切断已建立的连接),但下次再连就 Connection timed out 了。更糟的是如果服务器在云上、你又没开 VNC 控制台,那就只能重装了。

防火墙这件事,本质上是「默认拒绝 + 显式放行」的思维。但真正让人踩坑的,是规则生效的顺序、已建立连接和新连接的区别、以及 Docker 会在你背后偷偷改 iptables 这件事。这篇把 ufw 和 nftables 两条路子都讲清楚,并给出一个「顺序正确、可回滚、不会把自己锁在外面」的安全配置流程。

先理解 ufw 和 nftables 是什么关系

很多人以为 ufw、nftables、iptables 是三个并列的防火墙,其实不是。

内核层面只有一套东西:netfilter。它是 Linux 内核里的包过滤框架。我们平时说的「防火墙工具」都只是操作 netfilter 规则的用户态程序:

· iptables:老的用户态工具,操作 ip_tables / ip6_tables 内核模块。规则是「表(table)→ 链(chain)→ 规则(rule)」三层结构,有 filter、nat、mangle 等表。
· nftables:新一代工具,从 Linux 3.13 开始进内核,替代 iptables。规则语法更简洁,支持集合(set)和映射(map),性能更好,而且可以用一个命令原子性地替换整套规则。
· ufw(Uncomplicated Firewall):不是独立防火墙,只是 iptables/nftables 的封装。Ubuntu/Debian 上默认用它,`ufw` 命令背后实际是生成 iptables 规则(新版系统背后是 nftables)。

所以「ufw 和 nftables 冲突」这个说法不太准确,准确的说法是:ufw 管理的是它自己的一套规则,你手动用 nft 加的规则和它各管各的,容易互相覆盖或产生意料之外的放行。社区最稳的做法是二选一:要么全用 ufw,要么全用 nftables,别混着来。

方案一:ufw,适合个人站长的 90% 场景

ufw 最大的价值是「简单不容易错」。它默认的管理哲学是:默认拒绝入站、默认允许出站、默认放行已建立连接。这正好满足绝大多数服务器的需求。

第一步:先放行 SSH,再启用。顺序绝对不能反。永远先放行,再 enable:

# 1. 先装(Debian/Ubuntu 通常自带)
apt install -y ufw

# 2. 关键:先放行 SSH。改成你真实的 SSH 端口,不是 22 就写真实值
ufw allow 22/tcp

# 3. 再放行 Web 端口
ufw allow 80/tcp
ufw allow 443/tcp

# 4. 现在才启用
ufw enable

# 5. 查看状态
ufw status verbose

第 5 步的 status verbose 输出里必须看到这么一行,否则说明默认策略被改过:

Default: deny (incoming), allow (outgoing), disabled (routed)

第二步:改过 SSH 端口的话,别忘了同步。这是第二高频的「把自己锁在外面」的原因。如果你在 /etc/ssh/sshd_config 里把 Port 改成了 2222,但防火墙里放的还是 22,那你重启 sshd 之后当前会话还活着,下一次重连就进不来了。改端口时两张表都要动:

# sshd_config 里改端口后
ufw allow 2222/tcp
# 先不要删 22,等新端口连通性验证通过了再删
systemctl restart sshd
# 新开一个终端验证 2222 能连上
ufw delete allow 22/tcp

第三步:按来源 IP 放行(比如只允许自己访问后台)。

# 只允许某 IP 访问 8080
ufw allow from 203.0.113.7 to any port 8080 proto tcp

# 允许一个网段
ufw allow from 192.168.1.0/24 to any port 3306 proto tcp

# 限制 SSH 暴力破解:同一 IP 60 秒内超过 6 次连接就临时封禁
ufw limit 22/tcp

ufw limit 是 ufw 内建的一个轻量防爆破机制,比 fail2ban 简单,配置一行就行,代价是它不是持久封禁(fail2ban 可以封更久)。两者可以叠加,不冲突。

第四步:给 Docker 用户的一个大坑警告。如果你服务器上跑了 Docker,ufw 对 Docker 发布的端口几乎不起作用。原因:Docker 启动容器时会往 iptables 的 nat 表 DOCKER 链里插规则,这些规则在 ufw 的规则之前生效。也就是说,你用 docker run -p 3306:3306 暴露的 MySQL,哪怕 ufw deny 3306,外部照样能连进来。

验证方法:在外部机器上直接 nc -zv 你的IP 3306,如果通了但 ufw status 里没有 3306 的允许规则,那就是这个问题。解决方式有三:

# 方式 1(推荐):不要去发布端口,用 Docker 内部网络通信
#           容器之间通过 docker network 互访,不映射到宿主机

# 方式 2:只绑定到本地回环
docker run -p 127.0.0.1:3306:3306 mysql:8

# 方式 3:在 DOCKER-USER 链里加规则(ufw 管不到这段)
iptables -I DOCKER-USER -i eth0 -p tcp --dport 3306 -j DROP

最省心的还是方式 1 和 2:能不给宿主机映射端口就不映射。

方案二:nftables,规则更清晰、性能更好

如果你的系统比较新(Debian 10+ / Ubuntu 20.04+ / CentOS 8+),nftables 已经默认可用。它的配置文件是 /etc/nftables.conf,语法自成一派,但只要理解「表 → 链 → 规则」这个骨架就能上手。

一个完整的、可以直接用的服务器防火墙规则集:

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    # 已建立连接的集合,用于放行回包
    set ssh_ratelimit {
        type ipv4_addr
        flags dynamic, timeout
        timeout 60s
    }

    chain input {
        type filter hook input priority 0; policy drop;

        # 1. 放行本地回环
        iif lo accept

        # 2. 放行已建立/相关的连接(关键:否则回包被丢,表现为"能连上但没响应")
        ct state established,related accept

        # 3. 丢弃无效包
        ct state invalid drop

        # 4. 放行 ICMP(至少放行 ping 和 path MTU 发现,否则网络会莫名卡)
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept

        # 5. SSH:限制每 IP 每分钟最多 6 个新连接
        tcp dport 22 ct state new \
            add @ssh_ratelimit { ip saddr limit rate 6/minute } accept

        # 6. Web 端口
        tcp dport { 80, 443 } accept

        # 7. 其余全部丢弃(policy drop 已经兜底)
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

几个必须理解的要点:

1. ct state established,related accept 必须放在前面。这一条解决的是「出站请求的回包要不要放行」。如果没有它,你的服务器能发出 DNS 查询、能发出 API 请求,但收不到任何回包——表现是「网络能 ping 通但什么都加载不出来」,极难排查。

2. priority 0 和 policy drop 是 base chain 的属性。hook input 表示这个链挂在「入站」这个钩子上,priority 0 是执行优先级(数字小的先跑),policy drop 表示没有规则匹配时默认丢弃。很多教程漏掉 policy drop,结果规则写了等于没写。

3. 放行 ICMP 不是可选项。完全禁 ping 看起来安全,但会破坏 路径 MTU 发现(PMTUD)。表现是:小页面能打开,大页面(或者大文件上传)卡死。所以至少要放行 icmp 和 icmpv6,其中 icmpv6 在 IPv6 环境下是必需的(邻居发现协议依赖它)。

4. 加载规则前先 nft -c -f /etc/nftables.conf 检查语法。-c 是 check 模式,只解析不生效。这一步能帮你避免「规则写错导致整套配置没加载、防火墙处于全开状态」的尴尬。

# 语法检查(不会真的加载)
nft -c -f /etc/nftables.conf

# 确认无误后加载
nft -f /etc/nftables.conf

# 查看当前生效的完整规则集
nft list ruleset

# 设置开机自启
systemctl enable --now nftables

远程配置防火墙的安全方法论:给自己留三张保命牌

不管你用 ufw 还是 nftables,只要是远程 SSH 操作,就必须遵守下面三条。这不是「建议」,是血泪教训。

保命牌一:先放行,后启用,永远不要在同一个命令里做完。ufw 的 enable 之后加一句 sleep 30; ufw disable 作为「自动回滚」,是远程改防火墙的经典技巧:

# 在一条命令里:先配好规则,启用,然后 5 分钟后自动关闭
# 如果这 5 分钟内你连不上了,防火墙会自己退回到关闭状态
ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp \
  && ufw --force enable \
  && (sleep 300 && ufw disable) &

这个「定时自救」的模式,比任何备份都实用——因为防火墙把你锁在外面时,你连上去恢复配置的通道都没有了。

保命牌二:改配置前开两个 SSH 会话。一个用来改,另一个保持不动(ssh -o ServerAliveInterval=30 user@host)。如果改坏了,当前连接可能因超时被切,但第二个会话如果一直有心跳,通常能活到你把配置改回去。当然,如果是规则层面的 drop,已建立的连接不受影响(因为 established 是放行的),这正好是最可靠的一张保命牌。

保命牌三:云服务商的安全组是独立的一层,不要只改一台。很多云主机的防火墙有两层:一层是云控制台的「安全组」,一层是系统内的 ufw/nftables。流量要两层都放行才能通。两层都拒的时候,你在系统里怎么调 ufw 都没用,问题出在云控制台那边。反过来,如果只想临时开放一个端口,先在云安全组里放行试试,往往比改系统防火墙快。

排查顺序建议这样:先从本机 curl 127.0.0.1:端口 → 如果本机能通、外网不通,问题在防火墙或安全组;再从本机 curl 内网IP:端口 → 通了说明监听没问题,纯是入站规则;最后看云控制台安全组。这个「由内到外」的顺序能省掉大量瞎猜。

怎么验证防火墙规则真的生效了

配置完不算完,要能证明它按预期工作。三个验证动作:

1. 在外部机器上扫描你的端口。从一台不同网络的机器(手机 4G、或者另一台 VPS)上扫:

# 扫常见端口
nmap -Pn -p 22,80,443,3306,6379 你的服务器IP

# 快速看某个端口是否可达
nc -zv 你的服务器IP 3306

期望结果是:22/80/443 open,3306/6379 filtered(被丢弃表现为 filtered,被拒绝表现为 closed,两者语义不同)。

2. 看计数器有没有在动。nftables 可以给规则加计数:

# 在规则里加 counter
tcp dport { 80, 443 } counter accept

# 查看计数
nft -a list ruleset | grep -A1 counter

如果某条 drop 规则的计数一直涨,说明有人(或者某个扫描器)一直在敲那个端口,规则正在干活。

3. 查日志确认丢包原因。给 drop 规则加 log 前缀(注意日志量,别把磁盘写满):

chain input {
    ...
    tcp dport 22 log prefix "FW-DROP-SSH: " drop
}

然后在 journalctl -k -f 里就能看到被丢弃的包来自哪个 IP。这对判断「是不是自己在被扫描」很有用。

小结:记住三条主线

防火墙不复杂,复杂的是它和其他系统的交互。三条主线记住,基本就不会翻车:

第一条:默认拒绝入站,显式放行需要的,永远先放行 SSH 再启用。任何时候远程改防火墙,都要给自己留一条能进去的路(定时回滚、keepalive 会话、云控制台 VNC)。

第二条:别忘了「已建立连接」和「ICMP」这两类流量。前者不能放行会导致「能发不能收」的诡异现象,后者完全禁止会破坏路径 MTU 发现,表现为大文件上传卡死。

第三条:Docker 和云安全组是两个独立的、会覆盖你规则的东西。Docker 会在 iptables 里插到 ufw 前面,云安全组在网络层比系统防火墙更靠前。出了「端口明明没放行却能访问」或者「规则明明放行了却不通」的问题,先往这两个方向查。

最后,如果非要在 ufw 和 nftables 之间选:个人站长用 ufw 就够了,一行一条规则,出错概率最低;只有当你要做复杂的按连接数限速、按集合匹配、或者需要精细的规则组织和原子替换时,nftables 的优势才体现出来。工具栏不是越复杂越好,能稳定保护服务器的才是好工具。

Last modification:September 30th, 2026 at 12:24 pm

Leave a Comment