iptables性能优化核心是用ipset替代万条-s规则:创建带maxelem预留的hash:ip集,通过restore批量导入,iptables用-m set引用并确保规则置顶;动态更新必须swap而非flush,避免防护真空。

直接用 iptables -s 加万条规则会把匹配性能拖垮——每次包进来都要线性比对几千次,CPU吃紧、吞吐骤降、iptables-save 输出文件动辄上百 MB,重载卡顿还可能触发 conntrack 表抖动。真正可行的方案是用 ipset 把 IP 列表转成内核级哈希集合,让 iptables 只做一次 O(1) 查表。
创建 hash:ip 集合时必须显式设 maxelem
默认 maxelem 是 65536,万级 IP 看似够用,但实际容易溢出,尤其你后续还要批量导入或自动更新。一旦超出,ipset add 或 ipset restore 会直接报错:
ipset v7.6: Error in line 1: set is full
所以创建时务必按预期上限预留空间,比如预估最多 12 万 IP:
ipset create blacklist hash:ip maxelem 120000- 别用
hash:net存单个 IPv4 地址——它专为 CIDR 设计,存纯 IP 会浪费空间且查表略慢 - IPv6 黑名单必须用
hash:ip6单独建集,和 IPv4 不兼容
批量导入优先用 ipset restore,别循环调用 ipset add
写脚本逐行 ipset add blacklist 1.2.3.4 看似直观,但每条命令都启动新进程、进内核态,万次调用光 shell 开销就几秒,还可能被系统限频。正确做法是生成标准 restore 格式文件再一把导入:
- 准备文本
blacklist.restore,内容形如:create blacklist hash:ip maxelem 120000 add blacklist 1.1.1.1 add blacklist 2.2.2.2 add blacklist 192.168.0.100
- 执行
ipset restore -f blacklist.restore,毫秒级完成万条加载 - 导入前确保集合已存在且类型一致,否则报
Set cannot be swapped: different types
iptables 引用必须带 -m set 模块,且规则位置不能靠后
iptables -I INPUT -m set --match-set blacklist src -j DROP 这条命令里,漏掉 -m set 就会报错:
iptables: No chain/target/match by that name
更隐蔽的问题是规则顺序:如果这条规则写在 -j ACCEPT 之后,所有流量早被放行了,根本不会走到黑名单判断。务必保证:
- 用
iptables -I INPUT 1 ...插入最前面,或至少在所有ACCEPT规则之前 - 检查是否加载了内核模块:
lsmod | grep ip_set应输出ip_set和ip_set_hash_ip,缺一不可 - IPv4 集合只能匹配 IPv4 流量;若应用层有 IPv4-mapped IPv6 地址(如
::ffff:192.168.1.1),需额外处理或改用双栈逻辑
动态更新黑名单必须用 swap,禁止 flush + re-add
直接 ipset flush blacklist 再重加新 IP,会在清空瞬间放行所有黑名单 IP,形成数秒到数十秒的防护真空。生产环境零停机更新唯一可靠方式是 swap:
- 后台构建新集合:
ipset create blacklist_new hash:ip maxelem 120000,再用restore导入新名单 - 原子切换:
ipset swap blacklist blacklist_new,毫秒完成,旧数据自动转入blacklist_new - 立刻清理残留:
ipset destroy blacklist_new,否则空集合仍占内核内存 - swap 前必须确认两集合类型、family(inet/inet6)完全一致,否则报错
真正难的不是加一万条 IP,而是让这一万条在更新时不丢包、不放行、不卡 reload、不和现有规则打架——swap 机制和严格的位置/模块检查,才是万级黑名单能落地的关键。


















