iptables 太老?用 nftables 给 VPS 做一套现代防火墙
2026-08-15 · DevCraft Studio
从零写 nftables 规则集:只开 22/80/443、限 SSH 暴破、防 ping 洪、Docker 兼容(别再把容器端口暴露出去),附一键备份/回滚。
延伸阅读
更多相关攻略推荐:Ollama AI系列(2):VPS上的AI推理与API应用、Ollama AI系列(2):VPS上的AI推理与API应用、【CPU选型 01】搭载 AMD Ryzen 9950X 的 VPS、ARM / Ampere 席卷 VPS:性价比真香还是兼容陷阱?、Ollama AI系列(2):VPS上的AI推理与API应用。
为什么 iptables 该退休了
先说个扎心的事实:你现在服务器上跑的 iptables,骨子里是 2001 年的老古董。它当年确实立过大功,但二十多年过去,网络环境早变天了。iptables 本身不是一套工具,而是四套各干各的兄弟——iptables 管 IPv4,ip6tables 管 IPv6,arptables 管 ARP,ebtables 管桥接。每套都有自己的命令行、自己的规则表,内核里还各自维护一份独立的匹配代码。结果就是规则越多,内核开销越大,重复代码一堆,出了问题你还得在四个工具之间来回切换着查。
nftables 是 2014 年合并进 Linux 内核的"正统接班人",由同一个 Netfilter 团队开发。它的核心思路特别清爽:一套引擎、一套语法、一份规则集。不管你是要过滤 IPv4、IPv6 还是做桥接,通通交给内核里同一个 nftables 虚拟机去跑。对用户来说,最直观的好处有三个。
第一,inet 地址族一次管俩。过去最经典的翻车现场就是:你辛辛苦苦给 iptables 写了严严实实的 IPv4 规则,却忘了 ip6tables 根本没动,结果服务器开了一堆 IPv6 监听端口,等于后门全敞着。nftables 的 inet 表天生同时匹配 IPv4 和 IPv6,写一条规则两头都生效,从根上消灭这种"一半防火墙"的尴尬。
第二,原子替换。iptables 改规则是一条一条往里插的,中途如果命令报错或者你网断了,规则集就处于"半成品"状态,轻则漏洞,重则把自己锁门外。nftables 用 nft -f 文件 的方式一次性把整张规则集替换掉,要么全成功要么全不动,干净利落。
第三,性能和新特性。集合(set)、映射(map)、计量器(meter)都是内置原语,做"按源 IP 限速""批量封 IP"这类活儿不用再装 ipset。主流发行版早就站队了:Debian 10、Ubuntu 20.04、RHEL 8、Fedora 全都把 nftables 设为默认后端。所以与其继续给老框架打补丁,不如直接拥抱现代方案。
先搞懂三个核心概念:表、链、规则
很多人一看 nftables 的配置就头大,其实拆开就三样东西,比 iptables 的表链结构清楚多了。
表(table)是容器,里面装链和集合。建表时要选地址族,常用的是 inet(同时管 IPv4/IPv6)、ip(仅 IPv4)、ip6(仅 IPv6),还有 bridge、arp、netdev。普通服务器一把 inet 梭哈就够。
链(chain)挂在 Netfilter 的钩子(hook)上,决定流量在网络的哪个关口被检查。基础链能挂的钩子有:ingress(网卡刚收包)、prerouting(路由判断前)、input(送往本机)、forward(转发)、output(本机发出)、postrouting(路由判断后)。每个基础链有个默认策略(policy),通常是 drop 或 accept。
规则(rule)就是"匹配条件 + 动作"。匹配条件可以是端口、协议、源地址、连接状态等等;动作最常见的是 accept(放行)、drop(悄悄丢弃)、reject(拒绝并回包)、log(记录)。还有个关键概念叫连接追踪(conntrack),它给每个连接打状态标签:new(新连接第一个包)、established(已建立)、related(相关联,比如 FTP 数据通道)、invalid(不认识的脏包)。靠状态我们就能实现"出去的流量能回来,别人主动敲门一律挡"的效果。
下面这张图记住就够用:进来的包先过 input 链,要转发的过 forward 链,出去的过 output 链。我们的防火墙主攻 input 链,顺手把 forward 链管住,别让 Docker 偷偷放行人进来。
从零写一份"只开 22/80/443"的规则集
目标很明确:SSH(22)、网站(80/443)外面能访问,其他所有入站流量全部丢。先装好工具,大多数新系统自带,没有就补一下。
sudo apt update
sudo apt install nftables -y
sudo systemctl enable nftables然后新建一个配置文件 /etc/nftables.conf。注意文件第一行是 shebang 指明用 nft 解释,第二行 flush ruleset 会先清空旧规则,避免新旧叠加。
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# 本机自己发的流量全放行
iif lo accept comment "localhost"
# 已经建立的连接、相关联的回包放行
ct state established,related accept
# 脏包直接扔
ct state invalid drop
# 放行 ICMP(ping),后面会单独限流
meta l4proto { icmp, ipv6-icmp } accept
# 只开三个端口
tcp dport { ssh, http, https } accept comment "SSH and web"
# 其余全部记录后丢弃
counter log prefix "drop-input: " drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}来解释几个容易踩的点。policy drop 是兜底哲学:默认一律拒绝,只显式放行你点过名的。这比 policy accept 再一条条堵要安全得多。iif lo accept 放行回环,不然本机服务之间互相调用(比如 nginx 连本机 redis)会断。ct state established,related accept 这条必须放在端口规则前面,它保证你 ssh 连上之后后续的包不会被 policy drop 误杀。
写好之后别急着重启,先用 dry-run 检查语法:
sudo nft -c -f /etc/nftables.conf确认无误再正式加载:
sudo nft -f /etc/nftables.conf
sudo nft list ruleset第二条命令能把当前生效的规则打印出来,方便你核对。看到三个端口的 accept 规则和最后的 counter drop,说明成了。
给 SSH 上把锁:按源 IP 限速防暴破
22 端口常年被全世界的脚本小子扫,放个弱密码分分钟被攻破。除了强制密钥登录,防火墙层面还能再垫一道:每个源 IP 每分钟最多几个新连接,超了就丢。这招用 nftables 的"动态集合 + 计量器"实现,比 fail2ban 反应更快、零依赖。
思路是建一个会自己过期的集合,把"最近尝试过 SSH 的源 IP"记进去,并对每个 IP 单独限速。改一下 input 链里的 SSH 规则:
table inet filter {
set ssh_ratelimit {
type ipv4_addr;
flags dynamic;
timeout 10m;
}
chain input {
type filter hook input priority 0; policy drop;
iif lo accept
ct state established,related accept
ct state invalid drop
meta l4proto { icmp, ipv6-icmp } accept
# 每个源 IP 每分钟最多 4 个新 SSH 连接,超出静默丢弃
tcp dport ssh ct state new add @ssh_ratelimit { ip saddr limit rate 4/minute } accept comment "SSH per-IP throttle"
tcp dport ssh ct state new drop
tcp dport { http, https } accept
counter log prefix "drop-input: " drop
}
}这里有个微妙的顺序:先有一条"限速后 accept"的规则,再紧跟一条"无条件 drop"。某个 IP 这一分钟连第 5 次时,第一条匹配不上(已超限),于是落到第二条被丢掉——这就是按源 IP 限流的关键。timeout 10m 表示这个 IP 的限速记录在十分钟没活动后自动清除,不会把集合撑爆。
要顺手把 IPv6 也管上,加一个 ip6 版本的集合即可,逻辑完全一样。光靠限速还不够,强烈建议同时做三件事:禁用密码登录(/etc/ssh/sshd_config 里设 PasswordAuthentication no)、改个非常规端口、再叠加 fail2ban 读认证日志封真正撞库的 IP。限速治标,密钥治本,两样都上才稳。
别让 ping 把你打瘫:ICMP 限流
很多人喜欢直接 drop 掉所有 ping,觉得"看不见我就安全"。其实完全禁 ping 会破坏网络诊断(比如 MTU 路径发现依赖 ICMP),而且正常运维也靠它探活。更聪明的做法是放行 ping 但限流,既能用又能扛小规模的 echo-request 洪水。
在 input 链里把原来的 ICMP 放行那行换成带 rate 的版本:
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept
ct state established,related accept
ct state invalid drop
# 每秒超过 10 个 echo-request 就丢,burst 4 允许瞬时突发
meta l4proto icmp icmp type echo-request limit rate 10/second burst 4 packets accept
meta l4proto ipv6-icmp icmpv6 type echo-request limit rate 10/second burst 4 packets accept
tcp dport ssh ct state new add @ssh_ratelimit { ip saddr limit rate 4/minute } accept
tcp dport ssh ct state new drop
tcp dport { http, https } accept
counter log prefix "drop-input: " drop
}
}limit rate 10/second burst 4 的意思是:平均每秒放 10 个 ping 包,但允许短暂突发到 4 个。超过这个节奏的洪水包直接被后面的 policy drop 吞掉。注意 IPv4 用 icmp type echo-request,IPv6 对应 icmpv6 type echo-request,两者都得写,否则 v6 的 ping 会被卡。
如果你确实想彻底隐身,把这两行改成 drop 也行,但记得保留 ipv6-icmp 里其他类型(比如邻居发现 ND)的放行,否则 IPv6 网络会出问题。稳妥起见,新手还是用限流别用全禁。
Docker 用户必看:别把容器端口暴露到公网
这一节是重灾区。很多人兴致冲冲配好 nftables,结果一装 Docker,发现 -p 8080:80 把容器里的 80 端口直接绑到了服务器的公网 IP 上,防火墙形同虚设。原因得从 Docker 的网络模型说起。
Docker 出生那会儿只认 iptables,它启动时会往 iptables 里插一堆 NAT 和 FORWARD 规则来实现端口映射。问题来了:现代系统上的 iptables 其实是 iptables-nft 兼容层,它把 iptables 命令翻译成 nftables 规则。所以 Docker 插的规则最终也进了 nftables,但它是挂在 Docker 自己建的链(DOCKER、DOCKER-USER)里,绕过了你 input 链的 policy drop。换句话说,容器发布的端口不走 input 链,自然不受你"只开 22/80/443"的限制。
先确认系统用的是 nft 后端:
iptables --version
# 应显示 iptables v1.8.x (nf_tables)如果不是,切过去并重启 Docker:
sudo update-alternatives --set iptables /usr/sbin/iptables-nft
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-nft
sudo systemctl restart docker然后有两个路线。路线一,用 Docker 的 DOCKER-USER 链(通过 nftables 语法操作):
# 只允许内网访问容器发布的端口,其余外部来源丢弃
sudo nft add rule ip filter DOCKER-USER iifname "eth0" ip saddr != 192.168.1.0/24 drop路线二,原生 nftables 下自己建一张 forward 过滤表,优先级和 Docker 的链错开:
table ip docker-fw {
chain forward-filter {
type filter hook forward priority filter; policy accept;
iifname "eth0" ip saddr != 192.0.2.0/24 counter drop
}
}把 eth0 换成你服务器的公网网卡名(用 ip -br addr 查)。这条规则的意思是:从公网卡进来的、目标不是白名单网段的转发流量(也就是去往容器的)一律丢掉,等于给容器加了一道"仅白名单可达"的闸门。反过来,如果你 forward 链是 policy drop,记得手动放行 Docker 网桥,否则容器自己都出不去网:
iifname "docker0" accept
iifname "br-" accept还有个更激进但更干净的选择:Docker 29.0.0 起实验性支持原生 nftables 后端。在 /etc/docker/daemon.json 写 {"firewall-backend": "nftables"} 再重启,Docker 会自己建 ip docker-bridges 表,彻底不用 iptables 兼容层。不过这模式不支持 Swarm,且不会生成 DOCKER-USER 链,适合单机折腾。
一键备份与回滚:别把自己锁在门外
配防火墙最怕手一抖把自己踢下线。好消息是 nftables 的备份回滚比其他方案简单得多,因为整张规则集就是一个文本文件。
改之前先备份当前生效规则:
sudo nft list ruleset > /etc/nftables.backup.$(date +%F).conf注意这里用的是命令替换取日期,是个普通的 shell 写法,不是什么模板变量。每次大改前都存一份,文件名带日期,事后回滚一目了然。
给错误留个后路。改规则前,开个定时任务,十分钟后自动清空规则集。这样万一你把自己锁了,十分钟后规则自动消失,你又能连回来改:
echo "nft flush ruleset" | at now + 10 minutes确认新规则没问题了,再取消这个定时任务(用 atq 看编号,atrm 删掉)。
回滚就是一句话:
sudo nft -f /etc/nftables.backup.2026-08-15.conf日常保存当前规则到开机加载的文件:
sudo nft list ruleset | sudo tee /etc/nftables.conf
sudo systemctl enable --now nftables我一般把"备份→改文件→语法检查→加载→观察十分钟→保存"做成肌肉记忆。另外建议把 /etc/nftables.conf 放进 git 或者同步到异地备份(LowEndBox 上常有 2 到 4 美元一个月的小鸡,正好拿来存这种关键配置),毕竟规则文件丢了可比 ruleset 没了更难复原。
开机自启与日常运维
规则写进 /etc/nftables.conf 还不够,得让它在开机时自动加载。大多数发行版装好 nftables 包就会带一个 nftables.service,它启动时会读这个文件:
sudo systemctl enable --now nftables
sudo systemctl status nftables几个高频运维命令记一下:
nft list ruleset # 看全部生效规则
nft list table inet filter # 只看某张表
nft -s list ruleset # 带句柄,方便按编号删规则
nft delete rule inet filter input handle 5 # 按句柄删某条
nft monitor # 实时看规则变更,排错神器想临时加一条测试规则又不想改文件,可以直接命令行敲,生效立刻看到;确定要留,再写回配置文件并重新保存。计数器(counter)配合 nft 能看到每个规则的命中数,帮你判断哪条规则在干活、哪条纯摆设。
最后提醒一句:nftables 的规则是即时生效的,但不保存就等于没写。每次满意的改动都记得 nft list ruleset 落盘,别等重启后发现防火墙"穿越"回了出厂设置。