nftables 入门:一条规则同时管住 IPv4 和 IPv6
新买的服务器绑上公网 IP 后,扫描器几分钟内就会摸到 22 端口。很多教程这时会让你敲 ufw allow ssh 就完事,但 Ubuntu 24.04 底层的防火墙引擎早已换成 nftables,连 iptables 命令本身都在通过兼容层转发给 nftables 执行。绕过这层转译直接学 nftables,规则写一次,IPv4 和 IPv6 一起管住。
内核里跑的早已换人:从 iptables-nft 说起
nftables 从 Linux 4.x 起就是内核默认的包过滤框架,2019 年发布的 Debian 10 开始成为系统默认。你今天在 Ubuntu 24.04 上敲的每一条 iptables 命令,实际走的是 iptables-nft 兼容层,最终仍然由 nftables 的内核子系统执行。
这带来一个容易踩的坑:iptables -L 看到的规则和 nft list ruleset 看到的规则可能对不上号。Docker、fail2ban 这类工具也在往同一套内核规则里插自己的条目。排查端口不通时,用 nft list ruleset 看全量规则集,比只盯着一张 iptables 表可靠得多。
本站此前讲过的 UFW 是 nftables 之上的简化前端(见 UFW 防火墙入门),适合两三条规则的场景。规则一多——比如要按国家段、端口集合、限速分别控制——nftables 的原生语法反而更省事。
三层结构:table 装链,链挂钩子,链里放规则
nftables 的规则集只有三层。最外层是 table,一个 table 必须属于一个地址族:ip(仅 IPv4)、ip6(仅 IPv6)、inet(双栈通用)。日常建站防护直接选 inet,一条规则同时匹配两个协议栈,这是旧框架做不到的。
第二层是 chain。nftables 没有预定义的 INPUT/OUTPUT 链,链完全由你创建,通过 hook 声明挂到内核的哪个处理点上:input 看发往本机的包,output 看本机发出的包,forward 看途经转发的包。链还带一个优先级数字,同一 hook 上数字小的先处理。
第三层是 rule,由若干匹配表达式加动作组成。表达式从左到右依次判断,全中才执行动作;一条规则里可以串联多个动作,比如同时计数、记日志再丢弃。
建一张最小可用的双栈过滤表:
sudo nft add table inet filter
sudo nft add chain inet filter input '{ type filter hook input priority 0; policy accept; }'
注意花括号里那段配置:type filter 声明链类型,hook input 声明挂载点,policy accept 声明没被任何规则匹配到的包默认放行。漏掉花括号创建的只是一个普通链,不会看到任何数据包,这是新手最常犯的错误。
建站防护规则集:可直接粘贴
日常用命令逐条加规则容易乱,推荐写成配置文件统一管理。下面这份规则集覆盖了建站服务器的典型需求:SSH 换端口、Web 双栈放行、ICMP 限速防 flood、放行已建立连接:
sudo tee /etc/nftables.conf > /dev/null <<'EOF'
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set abuse {
type ipv4_addr
flags interval
elements = { 203.0.113.0/24 }
}
chain input {
type filter hook input priority 0; policy drop;
# 回环流量放行
iif "lo" accept
# 已建立或相关的连接放行
ct state established,related accept
# 无效包直接丢弃
ct state invalid drop
# SSH(示例端口 2222,按实际修改)
tcp dport 2222 ct state new accept
# Web 双栈放行
tcp dport { 80, 443 } accept
# ICMP 限速:每秒最多 4 个 ping 包,超出丢弃
icmp type echo-request limit rate 4/second accept
icmpv6 type echo-request limit rate 4/second accept
# 拦截已知滥用网段
ip saddr @abuse drop
}
}
EOF
sudo nft -f /etc/nftables.conf
几个写法值得单独说明。tcp dport { 80, 443 } 是内联集合语法,旧框架要装 ipset 才能做到;@abuse 引用的是文件顶部定义的命名集合,往集合里加网段不用改规则本身;ct state 读的是连接跟踪状态,放行 established 是保证出站请求能收到回应的关键——policy 已经是 drop,忘了这条,你的服务器自己发出的 curl 会全部超时。
应用前先验证,锁死自己之前给自己留后门
防火墙操作最大的风险是把 SSH 规则写错,然后把自己锁在服务器外面。nftables 提供了检查模式,-c 参数让命令只做语法和依赖校验,不真正生效:
sudo nft -c -f /etc/nftables.conf
没有任何输出说明规则集合法。确认无误再正式应用,然后立刻开第二个终端测试 SSH 还能不能连上——别关掉当前这个已经连上的会话,那是你最后的退路。
应用后核对规则集和计数器:
sudo nft list ruleset
table inet filter {
set abuse {
type ipv4_addr
flags interval
elements = { 203.0.113.0/24 }
}
chain input {
type filter hook input priority 0; policy drop;
iifname "lo" accept packets 0 bytes 0
ct state established,related accept packets 842 bytes 91660
ct state invalid drop packets 0 bytes 0
tcp dport 2222 ct state new accept packets 3 bytes 180
tcp dport { 80, 443 } accept packets 57 bytes 4120
icmp type echo-request limit rate 4/second accept packets 12 bytes 1008
icmpv6 type echo-request limit rate 4/second accept packets 0 bytes 0
ip saddr @abuse drop packets 0 bytes 0
}
}
packets 列是每条规则自带的可选计数器。网页打不开时先看 80/443 那行的计数有没有增长:数字在涨说明流量到了防火墙这一层,问题在服务本身;一直为零说明包根本没到,问题在网络路由。
开机自动生效与备份回滚
手动 nft -f 加载的规则重启就丢。持久化需要两步:
sudo systemctl enable nftables.service
enable 之后,系统启动时会自动加载 /etc/nftables.conf。刚才那份配置文件顶部第一行的 flush ruleset 是刻意保留的——每次加载前先清空旧规则集,保证文件里的内容就是内核里的全部现状,避免旧规则残留叠加。
日常改动走这个流程:改配置文件 → nft -c -f 校验 → nft -f 应用 → 开新终端验证 → 确认无误后提交一份到版本库或备份盘。需要临时排障时,sudo nft list ruleset | sudo tee ~/nft-backup.nft 导出当前全量规则,sudo nft flush ruleset 可以一键清空所有规则回到无防火墙状态——排障完记得重新加载配置文件。
和 fail2ban 配合时注意:fail2ban 默认通过 banaction 往防火墙里插拒绝规则,较新版本已经支持直接写 nftables。两者共存没问题,但如果你在 nftables.conf 里写死了 policy drop,fail2ban 的动作链仍然会正常工作,因为 drop 策略只影响到达链尾的包,不影响 fail2ban 插入的显式 drop 规则。
常见问题
加了规则之后 Docker 容器的端口映射全乱了?
Docker 自己维护一张独立的 nft/iptables 规则表来处理容器端口转发,和你手工写的表是并行关系,互不覆盖。容器端口不通时先跑 nft list ruleset 全量排查,最常见的冲突是你的 input 链 policy drop 挡住了转发流量,这种情况需要在 forward 链上给 Docker 网段显式放行。
nftables 和 ufw 能一起用吗?
不建议同时管。ufw 在 Ubuntu 上本身就是 nftables 的前端,两套工具各自改写规则集,删掉对方的规则只是时间问题。选 ufw 就全走 ufw,想用 nftables 原生语法就先 ufw disable 再上。
规则顺序重要吗?
重要,从上到下第一条命中即生效。把 ct state established,related accept 放在链首是通用最佳实践——绝大多数数据包是已建立连接的后续包,提前放行能省掉后面所有规则的匹配开销。
怎么临时封掉一个正在攻击的 IP?
用命名集合一条命令搞定:sudo nft add element inet filter abuse { 198.51.100.7 },立即生效且重启后仍在(因为集合定义在持久化配置文件里)。解封用 delete element 替换 add 即可。
改坏了进不去系统怎么办?
云服务商的控制台 VNC 是最后防线,它不经过 SSH。登录后 sudo nft flush ruleset 清空全部规则,恢复访问再慢慢排查配置文件。
nftables 的门槛集中在第一次建表那十分钟:理解 hook、优先级和 policy 这三个概念之后,剩下就是照抄语法。先把上面那份规则集原样跑通,再按自己的端口和网段改,比从零理解全部概念快得多——防火墙这种东西,跑通一次比读十遍文档有用。
参考链接:
- nftables 官方 Wiki
- nftables Wiki《Configuring chains》
- nftables Wiki《Simple rule management》
- Debian Wiki《nftables》