nftables自定义链实战:从iptables迁移到模块化防火墙规则

发布时间:2026/10/9 3:27:15
nftables自定义链实战:从iptables迁移到模块化防火墙规则 做Linux防火墙绕不开nftables而nftables和iptables最大的区别之一就是它把“自定义链”从一种鸡肋功能变成了组织整套防火墙规则的核心方式。很多人从iptables迁过来之后第一反应是“nftables命令怎么这么怪”第二反应是“自定义链到底怎么用能解决什么问题”。这篇文章不聊那些从man手册翻译过来的概念直接说清楚我在实际项目里怎么定义自定义链、怎么复用它们以及在这个过程中踩过的坑。这套内容适合正在用iptables、准备切到nftables的同学也适合已经把nftables跑起来、但是所有规则都堆在input/output链里、一张ruleset拉到几百行根本没法维护的运维。看完之后你会明白自定义链不是“把规则换个地方放”而是nftables里唯一好用的模块化手段配合jump/goto和命名集合可以让一套防火墙规则从“能跑”变成“能维护”。1. 理解自定义链之前先理清 nftables 的规则集骨架1.1 table一切规则的容器nftables的配置不是一堆平铺的iptables命令而是严格的三级嵌套结构table表→ chain链→ rule规则。table是最上层的容器它绑定一个地址族常见的有 ipIPv4、ip6IPv6、inetIPv4/IPv6双栈、arp、bridge。我在生产环境里几乎只用inet表。原因很简单一张表同时管v4和v6规则写法基本通用。比如 tcp dport 22 这样一条规则在inet表里会同时作用于IPv4和IPv6的22端口流量不需要像iptables那样v4写一遍、v6再写一遍。一开始用inet表还有个好处就是不会出现“明明配了防火墙IPv6的流量却在裸奔”这种低级事故。table本身没有太复杂的语义它就是一个命名空间把一组相关的链和规则装在一起。删除table会把里面的所有链和规则一起删掉所以做实验很痛快线上操作要谨慎。1.2 base chain 与普通链别再搞混“入口”和“子程序”这是理解自定义链的关键点。nftables里有两种链虽然都是 nft add chain 创建的但定位完全不同base chain有 type、hook、priority 等参数挂在内核netfilter钩子上是数据包进入防火墙的“入口”。相当于iptables里的INPUT、OUTPUT、FORWARD链。普通链regular chain没有任何hook参数不直接接收数据包只能被其他链通过 jump 或 goto 调用。这才是真正意义上的“自定义链”相当于一个子程序、一个函数。我见过不少人把普通链当成“再加一个INPUT链”来理解然后跑去给普通链加type filter hook input参数结果报错或者语义混乱。普通链不挂在钩子上它的执行起点一定是某条规则里的 jump 或 goto 语句。打个比方base chain是公司前台所有访客必须先到前台自定义链是各个部门某个访客要办的事需要跳转到财务部那就由前台的人转交过去办完事如果流程没有终止再回到前台接着走流程。1.3 为什么说自定义链是 nftables 的核心组织方式iptables时代规则一多就乱。一个典型的服务器可能有几十条规则堆在INPUT链里添加规则还要算插入位置因为 -I 和 -A 的语义经常把人绕晕。nftables把规则集结构化成 表→链→规则 之后自然就给了你一条模块化的路把同一类逻辑放到一个链里然后由入口链统一调度。我的一个真实感受是自定义链不是看着技术炫才用的而是当你第一次需要“改一条SSH防护规则但不想动整张规则表”的时候你才会发现它有多香。把SSH相关规则全部放在 ssh_protect 链里后面要调限速策略只改一个链其他部分完全不用碰。2. 定义自定义链命令语法与参数拆解2.1 创建表与链的基本命令先创建一张inet表nft add table inet security然后创建自定义链。不带hook参数的就是普通链nft add chain inet security ssh_protect这样就创建了一个名为 ssh_protect 的普通链。只不过现在它是空的还没有规则。要让它发挥作用还需要在入口链里加一条跳转规则。比如在input链里nft add chain inet security input { type filter hook input priority filter; policy accept; } nft add rule inet security input jump ssh_protect这里有个细节要单独说创建base chain时花括号后面、分号、policy关键字的写法都是有要求的少一个分号都可能报错。上面这个命令在zsh/bash里都能直接执行因为用了单引号包住参数。但如果你图省事把这段写进脚本文件反而更推荐用文件方式来管理。2.2 一个容易踩的坑bash 花括号展开第一次在命令行创建base chain的时候十有八九会遇到这种报错Error: syntax error, unexpected newline原因不是你的链名有问题而是bash把花括号当成展开语法处理了。nft add chain inet security input { type filter hook input priority filter; } 这行命令如果没有用单引号把花括号包起来bash会尝试把里面的内容拆成多个单词最后传给nft的就是一堆零散参数语法自然就错了。这个坑在写文章或复制网上的命令时特别容易踩因为有些教程为了排版把引号省略了。我的习惯是任何包含花括号、分号的nft命令一律用单引号包住整个参数如果是在脚本里用就提前用变量拼好再用。2.3 需要理解的关键参数type、hook、priority、policy创建一个base chain时四件套是必须理解的type链的类型常见 filter、nat、route。filter是过滤nat用于NAT规则route用于策略路由。hook挂载点prerouting、input、forward、output、postrouting。对应netfilter的五个钩子。priority优先级数值越小越先执行。系统给了几个默认值参考raw是-300mangle是-150filter是0nat的dstnat是-100、srcnat是100。自定义时可以直接用这些名字也可以写数字。policy链的默认策略accept或drop。当数据包走到链末尾还没有被任何规则匹配时就走这个策略。普通链完全没有这四件事它只需要一个名字。很多新手搞混的根源就在这里输入法不一样链的“性质”就不一样。普通链里的规则仍然可以写 drop、accept、reject 这类verdict这些verdict是规则的“返回动作”和链是base还是普通没有关系。2.4 给自定义链加计数器调试的第一件事自定义链一多最怕的就是“这个链到底有没有被调用到”。我强烈建议创建链的时候顺手加一个计数器nft add chain inet security ssh_protect { counter; }加了counter之后可以用 nft list chain inet security ssh_protect 查看链的计数器值。如果计数器一直没变说明jump语句可能没生效或者流量根本没走到这条路径。这比在规则里逐个猜要快得多。规则级别的计数同理可以在规则末尾加 counter 关键字nft add rule inet security input tcp dport 22 counter jump ssh_protect这样每次有发往22端口的流量经过计数器就会增长配合链级计数器可以统计“进入SSH保护链的包有多少、被拒绝的有多少”排障时信息量很大。3. 复用链的核心jump 与 goto 的语义差异3.1 jump调用子程序记得返回jump的语义和编程里的函数调用很像数据包进入自定义链按顺序匹配链里的规则如果某条规则执行了 accept 或 drop整个处理流程就结束了如果所有规则都匹配完还没有显式处理数据包会回到调用它的那条规则之后继续往后执行。举个例子nft add rule inet security input jump web_acl nft add rule inet security input log drop假设 web_acl 链里没有匹配的规则数据包从 web_acl 返回后还会继续执行 input 链里后面的 log drop。这样的流程在需要“先做检查没检查完继续走下一条”的场景下非常合适。CT状态相关的established规则经常放在入口链里提前放行然后把新连接送到子链里做细分处理这就是jump的典型用法。3.2 goto一锤子买卖不回头goto 和 jump 的区别只有一个goto 跳转到子链之后处理完不会再回到调用点而是直接从子链结束的位置继续整个规则集的后续流程。用专业一点的说法goto不会在链之间建立返回信息。还拿上面的例子nft add rule inet security input goto web_acl nft add rule inet security input log drop如果 web_acl 链里没有匹配的规则数据包不会回到第二行的 log drop而是直接跳出当前链的处理流程走向默认策略。换句话说goto 适合那种“转交出去不再收回”的逻辑比如遇到已知攻击IP直接转到一个专门drop的链或者转到一个专门做NAT的链。3.3 调用链之间的循环检测与限制在设计跳转关系时一定要避免出现A链jump到B链、B链又jump回A链的循环。nftables在加载规则集时会做静态检测检测到循环会直接报错Error: Could not process rule: File exists Error: recursive jump detected我第一次写嵌套跳转时也犯过这个错当时还以为是命令语法问题排查了半天才发现是调用关系设计反了。建议在纸上把链的调用关系画清楚确保是树状结构而不是图状结构树根就是base chain。3.4 同一套链的“流水线”组合设计自定义链的复用最舒服的场景是“一个入口链多个功能链按顺序调用”类似于流水线nft add rule inet security input jump anti_scan nft add rule inet security input jump apply_acl nft add rule inet security input jump rate_limit nft add rule inet security input jump log_drop每个链只干一件事anti_scan 做非法扫描防护apply_acl 做业务的黑白名单rate_limit 做限速log_drop 做日志记录和丢弃。后面对某个模块的修改完全不影响其他模块。团队成员各自维护一个链文件用git管理冲突概率也小很多。这套组织方式在生产环境跑了很久我认为它比任何“新功能”都更值钱因为防火墙这东西最难的不是配置而是维护的人看得懂。4. 规则复用更进一步include 文件与命名集合4.1 用 include 把规则集拆成可维护模块命令行创建规则适合快速验证但生产环境一定要用配置文件。nftables的配置文件天然支持include语法可以把全部规则集拆成多个文件。主配置 /etc/nftables.conf 内部可以这样写#!/usr/sbin/nft -f flush ruleset include /etc/nftables.d/security.nft include /etc/nftables.d/blacklist.nft注意第一行是flush ruleset这个动作很关键它先把内核里所有规则清空再加载新配置保证配置和当前状态严格一致。如果漏了这行旧规则会残留加载新配置可能出现“文件已存在”或规则叠加的问题。include后面的文件可以是通配符比如 include /etc/nftables.d/*.nft也可以用具体路径。文件里的语法和命令行一样可以创建表、链、规则也可以只定义集合。4.2 命名集合把“规则”和“名单”解耦说到复用很多人只想到链忽略了命名集合named set。事实上集合才是“数据复用”的主角。一个典型的场景封禁IP列表。传统做法是给每个IP写一条drop规则规则表会膨胀得很难看更好的做法是把IP放在集合里规则只写一条nft add set inet security blacklist { type ipv4_addr; flags interval; } nft add element inet security blacklist { 1.2.3.4, 5.6.7.8 } nft add rule inet security input ip saddr blacklist drop规则里用的是 blacklist 这个引用之后要加封禁IP只需要执行 add element不需要动规则。集合支持 interval 标志之后还能写CIDR和区间nft add element inet security blacklist { 10.0.0.0/24 }这个设计把“名单数据”和“执行逻辑”彻底分开了对写代码的人来说是直觉对运维也很友好。4.3 带 timeout 的动态集合自动解封与 fail2ban 联动命名集合还支持 timeout 属性元素可以设置存活时间时间一到自动从集合里消失nft add set inet security banned { type ipv4_addr; flags timeout; } nft add element inet security banned { 192.168.100.10 timeout 10m }10分钟之后这个IP会从集合里自动移除封禁逻辑自动解除。这个特性特别适合做“临时封禁”和fail2ban联动。实际上fail2ban新版本已经支持nftables后端它正是通过往这种带timeout的集合里加元素来实现动态封禁的。使用timeout集合时有一个细节如果你想在到期前手动解封某个IP用 delete element 指定IP即可。但要注意删除一个带timeout的元素和在集合里查找一个元素语法上需要完全匹配否则会报错。4.4 跨表、跨地址族的复用边界自定义链不是万能的一个很容易忽略的限制是不能跨表、跨地址族跳转。也就是说你不能在inet表的链里jump到一个ip表里的链因为两者处理的数据包上下文不同。如果你确实需要在多个表之间复用同一套规则常见的做法有两个用include文件生成多份配置在每个表里都创建同名同逻辑的链。把公共规则收敛到一个表里其他表只保留各自特有的少量规则。实践中我更倾向于第一种配合模板和sed批量生成维护成本其实不高。第二种方案限制较多适合规则规模不大的场景。5. 实战一套可复用的服务器安全基线配置5.1 配置目标与整体结构接下来用一个能直接改改就能用的例子把前面所有知识串起来。目标是一台典型的Linux服务器默认丢弃所有入站流量放行本机回环、已有连接SSH端口先过一个限速/封禁链正常的放行可疑的直接丢弃Web端口走一个单独的ACL链引用黑名单集合其余未匹配流量记录日志后丢弃配置通过 /etc/nftables.conf 加载全部用文件方式组织不依赖命令行临时规则。5.2 核心配置示例从表到链逐行读#!/usr/sbin/nft -f flush ruleset table inet security { set blacklist { type ipv4_addr flags interval } chain ssh_protect { tcp dport 22 ct state new limit rate 5/minute accept tcp dport 22 log prefix SSH attack blocked: drop } chain web_acl { ip saddr blacklist drop tcp dport 80,443 accept } chain log_drop { log prefix SECURITY DROP: counter drop } chain input { type filter hook input priority filter; policy drop; ct state established,related accept iifname lo accept jump ssh_protect jump web_acl jump log_drop } }逐块看第1块定义了一个 interval 类型的IPv4集合 blacklist。注意 ruleset 加载时集合默认是空的之后可以动态往里加IP。ssh_protect 链里对22端口的新连接做了频率限制每分钟超过5个新连接就直接丢。这个阈值在真实环境里要根据业务调整内网跳板机可能5都嫌少。web_acl 链先检查源IP是否在blacklist里在就丢弃然后对80和443的新连接放行。log_drop 链对所有走到这一步的流量做日志记录并drop。input链是入口policy设置成drop一开始就放行established/related和lo然后按顺序调用三个子链。整体结构非常清晰入口只负责“调度”不写具体业务规则业务规则分散在子链里名单数据集中在集合里。在文件里定义完执行nft -c -f /etc/nftables.conf-c 是check模式只做语法和依赖检查不会真正加载。确认无误后再执行nft -f /etc/nftables.conf5.3 如何在不闪断的情况下灰度加载配置很多人担心执行 nft -f 会把现有规则清空重来造成瞬时闪断。实际上 nft -f 加载一个包含 flush ruleset 的配置文件确实先清空、再加载但整个过程快得几乎不可感知。只有在极端高并发、规则极多的场景下才有明显影响。如果你要改的规则只是给某个集合加元素不需要重新加载配置文件nft add element inet security blacklist { 203.0.113.7 }这可以在运行中动态完成完全不中断现有流量。同样动态删除nft delete element inet security blacklist { 203.0.113.7 }每次使用 nft -f 加载配置前强烈建议先做一次备份nft list ruleset /tmp/ruleset-backup-$(date %F).nft别小看这一步。改防火墙配置把自己锁在门外的情况很可能就是靠这一份备份抢救回来的。6. 常用操作命令与常见问题排查实录6.1 日常管理命令速查整理一份我平时高频使用的命令覆盖查看、修改、删除三类操作# 查看完整规则集 nft list ruleset # 查看某张表的全部内容 nft list table inet security # 查看某个链含计数器和规则 nft list chain inet security input # 查看规则handle删除规则时需要 nft -a list ruleset # 按handle删除规则 nft delete rule inet security input handle 5 # 清空链里的规则 nft flush chain inet security ssh_protect # 清空一张表的所有规则 nft flush table inet security # 删除整个表 nft delete table inet security # 删除链需确保链为空 nft delete chain inet security ssh_protect关于handlenftables不像iptables那样支持按序号删除规则删除必须通过handle。想精确知道每条规则的handle可以用 nft -a list ruleset会多打印一列 handle 编号。6.2 常见报错与解决对照表报错信息原因解决办法Error: syntax error, unexpected newline命令里的花括号被shell展开用单引号包住包含花括号的参数Error: Could not process rule: File exists同名表、链或集合已存在先flush或delete旧对象或改用createError: No such file or directory引用了不存在的表或链检查表和链名、地址族是否匹配Error: Chain is busy链还被jump引用或链非空先flush链再删除引用它的规则Error: recursive jump detected链之间形成调用环调整跳转关系确保树状结构Error: conflicting protocols specified规则里同时出现了不兼容的匹配条件检查地址族和匹配关键字比如inet表里混用ip和ip6的特殊场景要分开写这里特别说一下“File exists”这个报错。nftables在加载规则集时如果配置里创建了同名的链而内存里已经存在就会报这个错。所以配置文件的flush ruleset不只是规范问题而是实际必需品。6.3 独家经验几次“翻车”后的总结这些年用nftables有几次印象深刻的故障都变成了现在的操作习惯。第一次是直接在命令行定义了带hook的链结果分号少打了一个整个规则集加载失败SSH直接断开。从那以后我所有规则都先写在文件里用 nft -c -f 检查语法再实际加载。第二次是给input链的policy改成drop之后忘记放行自己的管理IP远程重启防火墙服务时把自己关在机器外面。现在我的规则里管理网的放行规则永远写在policy drop之前而且会用一个一眼就能认出来的注释块标出来。第三次是inet表里写了 ip saddr blacklist 规则结果发现IPv6流量不受控制。原因是 blacklist 集合只定义了 ipv4_addr 类型IPv6地址根本装不进去。后面我把黑白名单集合都改成了 inet_service 或同时在ip6表里维护一套ipv6集合规则里分别对应 ip saddr 和 ip6 saddr。这些问题的共性是nftables本身语法很严谨逻辑绕带来的问题比语法问题多得多。设计规则集之前先在文档里把调用关系和数据流画清楚能省掉90%的现场排障时间。6.4 从 iptables 迁移时的思想转换最后说说iptables过来的朋友最容易卡住的点。iptables的自定义链其实也存在但使用的频率很低因为-A/-I/-D这套命令足够简单粗暴规则少的时候根本想不到拆链。nftables完全改变了这个平衡iptables的规则是一个大平面nftables是树状结构。iptables的自定义链不能指定hooknftables的普通链也不能指定hook但可以通过jump被base chain调用本质上都是子程序。iptables用 -j 跳转到自定义链nftables用 jump/goto 关键字。iptables里表是固定的filter/nat/manglenftables里表是自由创建的还可以跨协议用inet表。适应之后你会发现nftables的模型其实更接近程序员的心智模型表是模块链是函数规则是函数体集合是数据表include是代码拆分。把这个心智模型建立起来后面写多复杂的规则集都不会乱。我个人在实际操作中最喜欢的一个做法是每个业务模块一个.nft文件文件里只创建自己的链和规则然后在主配置里按顺序include。模块之间通过命名集合交互一个模块的集合另一个模块去引用。这样团队协作时各自改自己的文件用diff和git review都能看清楚改动范围。自定义链和集合用好了nftables不是比iptables更难用而是从“一笔糊涂账”变成了“一张清晰的结构图”。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询