网络技术

Linux 防火墙实战:iptables、nftables 与 firewalld 规则编写与持久化指南

✎ -- 字 🕐 -- 分钟
字号

Linux 防火墙实战:iptables、nftables 与 firewalld 规则编写与持久化指南

Linux 防火墙实战封面

服务器上线第一件事不是部署业务,而是先把门看好。防火墙就是那扇门。别看现在云厂商都提供安全组,很多场景下你依然要在操作系统层面写规则:容器网络、自定义 NAT、流量审计、精细到端口的访问控制。本文从 iptables 的四表五链出发,对比 nftablesfirewalld 的设计差异,带你写出一套可落地、可持久化、可审计的生产级防火墙规则,并分享我踩过的坑。

为什么防火墙依然是必修课

安全组虽然方便,但它位于 hypervisor 层,粒度粗、日志黑盒。操作系统防火墙能做的事情更细:

  • 按协议、端口、IP、连接状态放行或拒绝
  • 做 SNAT/DNAT、端口转发、负载均衡
  • 记录并审计每一条被拦截的连接
  • 与入侵检测、日志平台联动
  • 在主机内部对南北向和东西向流量做二次过滤

换句话说,安全组是小区大门,操作系统防火墙是你家门锁。两者不冲突,叠加使用才安全。尤其是在混合云、裸金属、边缘节点等场景,操作系统防火墙是最后一道可控防线。

防火墙技术全家福

iptables:Linux 防火墙的“老祖宗”

iptables 基于 netfilter 框架,规则组织成四表五链。理解表和链的关系,是写对规则的前提。

表(table)作用常见链
filter过滤数据包INPUT、FORWARD、OUTPUT
nat地址转换PREROUTING、POSTROUTING、OUTPUT
mangle修改包头(TTL、TOS 等)全部五条链
raw连接追踪豁免PREROUTING、OUTPUT

nftables:下一代统一框架

nftables 用一套引擎替代 iptables、ip6tables、arptables、ebtables。规则采用声明式语法,支持集合(set)、映射(map)、动态增删,性能更好。它不再区分 IPv4 和 IPv6,一个 inet 表就能同时管双栈。

firewalld:动态区域管理

firewalld 是 RHEL/CentOS/Fedora 默认的防火墙前端,核心概念是zoneservice。它把网络接口划分到不同区域,区域决定默认策略,服务定义端口和协议。改动无需刷新全部规则,运行时和永久配置分离,适合需要频繁变更的环境。

方案学习曲线粒度持久化推荐场景
iptables极细需手动保存自定义 NAT、复杂过滤
nftables中高规则即配置新系统、高性能场景
firewalld自动持久化服务器快速上线、动态变更

五链分工与数据包流向

netfilter 把数据包的生命周期拆成五条链,每条链承担不同职责:

  • PREROUTING:包刚进入网卡,还没做路由判断。常用于 DNAT、透明代理。
  • INPUT:路由判断目标为本机,进入本地进程前过滤。
  • FORWARD:目标不是本机,需要转发给其他主机时过滤。
  • OUTPUT:本地进程发出的包,在离开本机前处理。
  • POSTROUTING:包即将离开网卡,常用于 SNAT、MASQUERADE。

iptables 包处理流程图

记住一个原则:规则从上到下匹配,一旦命中就停止。所以更具体的规则必须放在更宽泛的规则前面,否则会被“短路”。

iptables 实战:从默认拒绝到精确放行

推荐策略:默认拒绝(DROP),显式放行(ACCEPT)。下面是 Web 服务器常用规则。

#!/bin/bash
# 清空旧规则
iptables -F
iptables -X
iptables -Z

# 默认策略
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# 允许本地回环
iptables -A INPUT -i lo -j ACCEPT

# 允许已建立连接
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# SSH(建议改非 22 端口)
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT

# HTTP/HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# ICMP 按需放行(建议保留,否则 ping 和 Path MTU 会受影响)
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 10/second -j ACCEPT

# 限速防暴力破解:每分钟最多 6 次 SSH 新连接
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 6 -j DROP

# 记录可疑流量
iptables -A INPUT -j LOG --log-prefix "IPTABLES-DROP: " --log-level 4

保存规则(Debian/Ubuntu):

iptables-save > /etc/iptables/rules.v4
# 开机自动加载
apt install iptables-persistent

NAT 实战:端口转发与源地址转换

nat 表是 iptables 的另一个高频场景。假设内网有一台 Web 服务器 192.168.10.20,网关要把外网 8080 端口转发给它:

# 开启转发
sysctl -w net.ipv4.ip_forward=1

# DNAT:把到达本机 8080 端口的流量转发到内网服务器
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.10.20:80

# SNAT:让内网服务器回包能正确经过网关
iptables -t nat -A POSTROUTING -p tcp -d 192.168.10.20 --dport 80 -j SNAT --to-source 10.0.0.1

# 或者直接用 MASQUERADE,适合出口 IP 会变的情况
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

做 NAT 时最容易忘的是开启内核转发参数 net.ipv4.ip_forward,也别忘了在 /etc/sysctl.conf 里持久化。

nftables 实战:声明式规则集

nftables 用 nft 命令或配置文件管理。下面是一份等价的规则。

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    set rate_limit {
        type ipv4_addr
        flags dynamic,timeout
        timeout 1m
    }

    chain input {
        type filter hook input priority 0; policy drop;

        iif "lo" accept
        ct state established,related accept
        tcp dport { 80, 443 } accept
        icmpv6 type { echo-request } limit rate 10/second accept
        icmp type echo-request limit rate 10/second accept

        # SSH 限速
        tcp dport 22 ct state new add @rate_limit { ip saddr limit rate over 6/minute } drop
        tcp dport 22 ct state new accept

        log prefix "NFTABLES-DROP: " drop
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

加载并持久化:

nft -f /etc/nftables.conf
systemctl enable nftables

firewalld 实战:区域 + 服务模型

如果你用 Rocky/Alma/CentOS Stream,firewalld 是默认选择。常用命令:

# 查看默认区域
firewall-cmd --get-default-zone

# 把 eth0 加入 dmz
firewall-cmd --permanent --zone=dmz --change-interface=eth0

# 放行 HTTPS
firewall-cmd --permanent --zone=public --add-service=https

# 放行自定义端口
firewall-cmd --permanent --zone=public --add-port=8080/tcp

# 富规则:只允许 192.168.1.0/24 访问 SSH
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4" source address="192.168.1.0/24" service name="ssh" accept'

# 重载生效
firewall-cmd --reload

firewalld 内置的区域策略如下:

区域默认信任度典型用途
drop不信任静默丢弃,不回复
block不信任明确拒绝,返回 ICMP 禁止
public公共网络、默认区域
external网关、NAT 场景
dmz隔离区服务
internal内网可信区域
trusted完全信任测试环境慎用

容器环境下的防火墙策略

Docker 默认会操作 iptables,插入自己的 DOCKER 链,常常导致你手工写的规则被“绕过”。三种应对方式:

  • 在 Docker daemon 配置里设置 "iptables": false,然后自己管理全部规则。
  • 把容器接到自定义 bridge,用显式的 DOCKER-USER 链补充过滤。
  • 升级到 nftables 或使用 Cilium 等 eBPF 方案,把过滤从 iptables 迁走。

对于 Kubernetes 环境,建议用 NetworkPolicy 做东西向隔离,节点级防火墙做南北向加固,两者互补。

持久化:别让重启把规则吃了

这是新手最容易踩的坑。三条规则对应三种方案:

  • iptables:用 iptables-save 导出,配合 iptables-persistent 或 systemd 服务加载。
  • nftables:配置文件本身就是规则源,systemctl enable nftables 即可。
  • firewalld--permanent 写入磁盘,--reload 生效,重启不丢。

我个人习惯:把规则写进 Git,每次变更先在测试机跑一遍,没问题再同步到生产,并用脚本做带时间戳的备份。

日志、审计与排错

规则没生效时,按下面顺序排查:

  1. iptables -L -v -n --line-numbers 看命中计数。
  2. iptables -S 查看当前规则序列,确认默认策略是否太严。
  3. 查看内核日志:journalctl -k | grep IPTABLES-DROP
  4. tcpdump -i any port 22 确认包是否到达网卡。
  5. 检查 Docker/kube-proxy 是否也加了自己的规则,导致冲突。

一个实用脚本:自动备份当前规则并带时间戳。

#!/bin/bash
BACKUP_DIR="/etc/iptables/backups"
mkdir -p "$BACKUP_DIR"
iptables-save > "$BACKUP_DIR/rules-$(date +%Y%m%d-%H%M%S).v4"
echo "备份完成:$(ls -1 $BACKUP_DIR | tail -n 1)"

常见陷阱

现象解决办法
默认策略 DROP 但没放行 SSH远程直接失联改规则前用 nohup 或 console 会话操作;先 ACCEPT 再切 DROP
规则顺序错误明明放行了端口还是被拒iptables 从上到下匹配,把更具体的规则放前面
Docker 与 iptables 冲突容器端口外网可访问,iptables 规则失效调整 Docker daemon 的 iptables 选项,或改用自定义链
IPv6 没设防IPv4 封了,IPv6 裸奔同步配置 ip6tables 或直接用 nftables/inet 表
忘记持久化重启后规则全丢写入配置文件并设置开机加载
NAT 忘记开转发端口转发不通设置 net.ipv4.ip_forward=1 并持久化

iptables 自定义链:让规则更清晰

规则多了之后全堆在 INPUT 链里很难维护。iptables 支持自定义链,可以把 Web、SSH、日志分组管理,结构更清晰。

# 创建自定义链
iptables -N WEB
iptables -N SSH_BRUTEFORCE
iptables -N LOG_DROP

# 将流量引导到自定义链
iptables -A INPUT -p tcp --dport 80 -j WEB
iptables -A INPUT -p tcp --dport 443 -j WEB
iptables -A INPUT -p tcp --dport 22 -j SSH_BRUTEFORCE

# 在自定义链里写具体规则
iptables -A WEB -m conntrack --ctstate NEW -j ACCEPT
iptables -A SSH_BRUTEFORCE -m recent --set
iptables -A SSH_BRUTEFORCE -m recent --update --seconds 60 --hitcount 6 -j LOG_DROP
iptables -A SSH_BRUTEFORCE -j ACCEPT
iptables -A LOG_DROP -j LOG --log-prefix "DROP: "
iptables -A LOG_DROP -j DROP

自定义链还有一个好处:清空规则时只刷新 INPUT 和自己的链即可,不会误伤 Docker 等组件插入的规则。

nftables 动态黑名单:基于集合的自动封禁

用 nftables 的 set 和 timeout,可以配合日志自动封禁扫描 IP,而不用重启规则。

table inet filter {
    set blacklist {
        type ipv4_addr
        flags timeout
        timeout 1h
    }

    chain input {
        type filter hook input priority 0; policy drop;

        ip saddr @blacklist drop

        tcp dport 22 ct state new limit rate 3/minute accept
        tcp dport 22 ct state new add @blacklist { ip saddr }

        tcp dport { 80, 443 } accept
        ct state established,related accept
        iif "lo" accept
    }
}

上面的规则意思是:SSH 新连接如果超过每分钟 3 次,就把源 IP 加入黑名单 1 小时。这种动态集合是传统 iptables 很难优雅实现的。

firewalld 富规则进阶

富规则(rich rule)让 firewalld 也能做比较精细的控制,比如限时放行、按用户或按日志标记处理。

# 仅在工作时间放行某端口
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4" source address="10.0.0.0/8"
       port protocol="tcp" port="3306"
       time from="09:00" to="18:00" accept'

# 拒绝某个 IP 访问所有服务
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4" source address="192.168.100.50" reject'

# 记录匹配到某条规则的日志
firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4" source address="10.0.0.0/8" service name="ssh" log prefix="SSH-ADMIN: " accept'

把日志接入审计体系

iptables 的 LOG 目标默认输出到内核日志,可以通过 rsyslog 或 journald 做进一步处理。下面是一个把 DROP 日志转发到独立文件的示例:

# /etc/rsyslog.d/10-iptables.conf
:msg,contains,"IPTABLES-DROP" /var/log/iptables-drop.log
& stop

然后再用 Fail2ban 或自定义脚本扫描该日志,自动把恶意 IP 加入黑名单。对于安全运营来说, DROP 日志是最直接的攻击面观测数据,建议至少保留 30 天。

性能考量:规则越多越慢吗

iptables 的规则是线性匹配的,规则数量增加会直接影响吞吐量。而 nftables 使用内部虚拟机,支持集合和映射,可以把几百条 IP 白名单合并成一次哈希查找,性能提升明显。

在高并发场景下,我建议:

  • 尽量用 ipset 或 nftables set 做批量 IP 匹配,不要用几百条独立的 iptables 规则。
  • 把最常被命中的规则放在链的前面。
  • 用 conntrack 状态匹配,让已建立连接快速通过,不走后续规则。
  • 对日志规则加 rate limit,避免日志风暴拖垮系统。

普通 Web 服务器几十条规则不会有明显性能问题,但 CDN 节点、网关、负载均衡器这种流量大户,必须从设计阶段就考虑规则效率。

变更演练:如何安全地修改远程防火墙

远程改防火墙最怕一条规则写错把自己锁在门外。我的标准流程如下:

  1. 先用 screentmux 建立一个可断线恢复的会话。
  2. 设置一个定时任务,在 5 分钟后自动清空所有规则并恢复默认 ACCEPT,防止失联:echo 'iptables -P INPUT ACCEPT; iptables -F' | at now + 5 minutes
  3. 逐条添加新规则,每加一条用 iptables -L -v -n --line-numbers 确认。
  4. 另开一个终端测试业务端口和管理端口是否可达。
  5. 确认无误后保存规则,并取消刚才的定时恢复任务:atrm

这个“保险丝”做法我已经救过自己很多次。生产环境永远不要裸奔改 INPUT 链,尤其是在默认策略为 DROP 的情况下。

生产最佳实践 Checklist

  • 默认拒绝所有入站,出站按需限制。
  • 只开放业务必需的端口,SSH 建议改端口 + 密钥登录 + fail2ban。
  • 关键管理端口限定来源 IP。
  • 定期备份规则,变更前先在测试机验证。
  • 结合日志平台对 DROP 日志做告警。
  • IPv4 与 IPv6 同步防护。
  • 把防火墙规则纳入版本控制,变更走 Code Review。
  • 复杂规则用自定义链或 nftables 集合拆分,避免单链臃肿。

总结

iptables 依然是理解 Linux 网络过滤的必修课,nftables 是未来方向,firewalld 则更适合想“开箱即用”的场景。无论选哪一套,核心都是:默认拒绝、最小权限、持久化、可审计。把防火墙当成代码来管理,写进 Git、做 Code Review、定期演练,才是生产环境该有的样子。