为什么该从 iptables 迁到 nftables 了
如果你还在用 iptables 维护服务器防火墙,大概率有过这种体验:几十条规则散落在 INPUT、FORWARD、OUTPUT 三条链里,中间插错一条,重启后就再也进不去机器;用 iptables-save 导出的规则文件越滚越长,谁改的、为什么改只能靠注释猜;想按"来源 IP + 目的端口 + 连接状态"做一条复合规则,得先写一条再写一条,还要小心 -m state 与 -m conntrack 的区别。这些不是你的错,是 iptables 这套从上世纪 90 年代延续下来的设计本身的局限:规则是"线性表",每条链是一个有序列表,匹配是按顺序逐条试的,规则一多性能和维护成本都会陡增。
nftables 是 netfilter 项目的官方继任者,从 Linux 3.13 起就进入内核,Debian 10 / CentOS 8 之后的发行版早就默认用 nftables 后端来翻译你写的 iptables 规则了。它把两张表(filter 和 nat)合并成统一的"表-链-规则"三层结构,支持集合(set)、映射(map)、原子更新、以及一次提交多条规则的事务语义。对个人站长来说,最直观的收益有三个:规则组织更清晰、批量操作不会留下"改一半"的中间态、复杂匹配(比如一整个 IP 段加一批端口)用一行集合就写完了。
先看清:你的系统到底在跑哪套
迁移前第一步不是急着卸载 iptables,而是确认现状。很多发行版上 iptables 命令其实是个兼容层,底层仍然是 nftables。先跑这条:
iptables --version
# 输出含 (nf_tables) 说明走的是 nftables 兼容后端
# 输出含 (legacy) 说明是真·老 iptables再看当前实际生效的规则集。老系统要分开看 iptables 和 ip6tables,同时别忘了 iptables-save 只导出 IPv4:
nft list ruleset
iptables-save
ip6tables-save
iptables -t nat -L -n -v把这几份输出存成文件留档,是迁移的回滚依据。千万不要以为自己记得住现有规则——恰恰是这些"没记录的规则"最容易在迁移后把人锁在服务器外面。
迁移的核心手法:用 iptables-translate 生成 nft 语法
nftables 官方提供了一套翻译工具,能把现有 iptables 命令逐条转成 nft 语法,这是迁移最高效的起点。Debian/Ubuntu 装 nftables 包时一般会带上:
apt install nftables
iptables-translate -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
# 输出:
# nft add rule ip filter INPUT tcp dport 22 ct state new counter accept注意 iptables-translate 只做语法转换,不会帮你判断规则之间的逻辑关系。所以正确姿势是:把 iptables-save 里每一条 -A 规则喂给 iptables-translate,收集输出,再人工整理成一份完整的 nftables 配置。对于 -j LOG、-j REJECT 这类目标,翻译结果里会保留对应关键字,但要留意 REJECT 的 --reject-with 默认值在两套里不完全一致。
一份可用的 nftables.conf 骨架
nftables 的配置通常放在 /etc/nftables.conf,由 systemd 的 nftables.service 在开机时加载。下面这份是一个"默认拒绝、放行 SSH 与 Web、防住常见扫描"的实用骨架:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set ssh_whitelist {
type ipv4_addr
elements = { 203.0.113.10, 203.0.113.11 }
}
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
ct state invalid drop
iifname "lo" accept
tcp dport 22 ct state new limit rate 6/minute burst 10 packets accept
ip saddr @ssh_whitelist tcp dport 22 accept
tcp dport { 80, 443 } ct state new accept
udp dport 443 ct state new accept
meta l4proto icmp icmp type { echo-request } limit rate 10/second accept
ip6 nexthdr ipv6-icmp accept
counter log prefix "nft-drop-in: " level info
counter drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}几个要点值得单独拆开讲。第一,"table inet"是 nftables 的统一地址族,一份规则同时处理 IPv4 和 IPv6,不用再像过去那样同时维护 iptables 和 ip6tables 两套——这是迁移后最容易"顺手多拿"的收益。第二,限流用的是 limit rate 语句,直接内建在匹配里,不必再借助 hashlimit 或 recent 模块。第三,set 和 map 让白名单、端口列表变成命名集合,改的时候只动集合本身,不用重排规则顺序。第四,log 用 counter log prefix 组合,前缀加上后就很容易在 journalctl 里按关键字捞出来。
原子加载与灰度上线,别把自己锁在门外
nftables 最大的工程价值是"事务性":整个 ruleset 一次性加载,要么全生效要么全不生效。这意味着你可以先在内存里构建好,再原子替换,不会出现旧规则已删、新规则未加的裸奔窗口。但正因为它是原子的,一次写错就可能把你的 SSH 连接一起关掉。所以务必遵守下面的上线流程。
第一步,不要直接改 /etc/nftables.conf 就 flush。先把新规则写到一个临时文件,用 nft -c -f 做语法检查:
nft -c -f /etc/nftables.conf.new
# 无输出即语法通过第二步,真正加载前先给自己留一条"保命通道"。可以临时在 input 链顶部加一条允许你当前来源 IP 的规则,等一切验证完再删除。或者更稳妥的做法:用 at 或 systemd 定时任务设一个 5 分钟后自动恢复旧配置的任务,确认新规则没问题后再取消它:
# 后台挂一个 5 分钟后恢复旧规则的保险
echo "nft -f /root/nftables.backup" | at now + 5 minutes
nft -f /etc/nftables.conf.new
# 验证连接、端口都正常后:
atrm $(atq | awk '{print $1}')第三步,验证不是"能连上 SSH"就够了,要逐项确认:
nft list ruleset
nft list set inet filter ssh_whitelist
nft list ruleset | grep -c 'counter drop'
ss -tlnp | grep -E ':(80|443|22)'迁移中五个最常踩的坑
第一坑,FROM 与 SRC 的写法差异。iptables 里的 -s 0.0.0.0/0 在 nft 里直接省略即可,写成 ip saddr 0.0.0.0/0 语法虽对但没必要,反而容易在 ip6 场景下产生误判。
第二坑,RELATED 与 ESTABLISHED 的合并写法。老手习惯写 -m state --state RELATED,ESTABLISHED,nft 里对应 ct state established,related accept。但要注意 conntrack 模块必须存在,容器里如果没加载 nf_conntrack 模块,这条规则匹配会静默失效,日志里表现为所有新建连接都被 drop。
第三坑,NAT 里 masquerade 的位置。nftables 的 NAT 要写在 postrouting 链且 type nat hook postrouting priority srcnat,写错优先级会导致转发流量匹配不到,表现为"内网通、外网不通"。
第四坑,Docker 与 nftables 共存。老版本 Docker 会在 iptables 层面插入自己的规则;如果你在 nftables 里做了全量 flush,可能把 Docker 的 DOCKER 链一起清掉,容器网络随即瘫痪。稳妥做法是迁移期间保留 Docker 自己管理的那部分表,只接管 filter 中你手工维护的规则,或者升级到会主动适配 nftables 的 Docker 版本。
第五坑,持久化没做。手动 nft add 的规则重启即失,必须落到 /etc/nftables.conf 并 enable nftables.service。很多人迁移当天一切正常,机器一重启防火墙直接回退到默认接受,安全边界瞬间没了。
回滚方案与验收清单
上线前后都留一份对照,比事后拍脑袋强。回滚就是把之前 iptables-save 的规则重新加载:
# 备份
iptables-save > /root/iptables.backup.v4
ip6tables-save > /root/iptables.backup.v6
nft list ruleset > /root/nftables.backup
# 回滚(若还在用 iptables)
iptables-restore < /root/iptables.backup.v4验收清单建议固定成这么几条:默认策略是否为 drop;SSH 是否只对白名单或限流放行;80/443 是否正常;ICMP 是否按预期限速;日志里是否能按 prefix 捞到 drop 记录;机器重启后规则是否自动恢复;以及最容易被忽略的一条——IPv6 是否也被同一份 inet 表接管了。把这七条逐项打勾,这次迁移才算真正完成。
对个人站长来说,nftables 不是"更时髦的 iptables",而是在规则一旦变多、变复杂时能显著降低维护事故的基础设施。花一个下午迁移,换来的是以后每次调整防火墙都不用再提心吊胆。