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

服务器上线第一件事不是部署业务,而是先把门看好。防火墙就是那扇门。别看现在云厂商都提供安全组,很多场景下你依然要在操作系统层面写规则:容器网络、自定义 NAT、流量审计、精细到端口的访问控制。本文从 iptables 的四表五链出发,对比 nftables、firewalld 的设计差异,带你写出一套可落地、可持久化、可审计的生产级防火墙规则,并分享我踩过的坑。
为什么防火墙依然是必修课
安全组虽然方便,但它位于 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 默认的防火墙前端,核心概念是zone 和 service。它把网络接口划分到不同区域,区域决定默认策略,服务定义端口和协议。改动无需刷新全部规则,运行时和永久配置分离,适合需要频繁变更的环境。
| 方案 | 学习曲线 | 粒度 | 持久化 | 推荐场景 |
|---|---|---|---|---|
| iptables | 高 | 极细 | 需手动保存 | 自定义 NAT、复杂过滤 |
| nftables | 中高 | 细 | 规则即配置 | 新系统、高性能场景 |
| firewalld | 低 | 中 | 自动持久化 | 服务器快速上线、动态变更 |
五链分工与数据包流向
netfilter 把数据包的生命周期拆成五条链,每条链承担不同职责:
- PREROUTING:包刚进入网卡,还没做路由判断。常用于 DNAT、透明代理。
- INPUT:路由判断目标为本机,进入本地进程前过滤。
- FORWARD:目标不是本机,需要转发给其他主机时过滤。
- OUTPUT:本地进程发出的包,在离开本机前处理。
- POSTROUTING:包即将离开网卡,常用于 SNAT、MASQUERADE。
记住一个原则:规则从上到下匹配,一旦命中就停止。所以更具体的规则必须放在更宽泛的规则前面,否则会被“短路”。
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,每次变更先在测试机跑一遍,没问题再同步到生产,并用脚本做带时间戳的备份。
日志、审计与排错
规则没生效时,按下面顺序排查:
iptables -L -v -n --line-numbers看命中计数。iptables -S查看当前规则序列,确认默认策略是否太严。- 查看内核日志:
journalctl -k | grep IPTABLES-DROP。 - 用
tcpdump -i any port 22确认包是否到达网卡。 - 检查 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 节点、网关、负载均衡器这种流量大户,必须从设计阶段就考虑规则效率。
变更演练:如何安全地修改远程防火墙
远程改防火墙最怕一条规则写错把自己锁在门外。我的标准流程如下:
- 先用
screen或tmux建立一个可断线恢复的会话。 - 设置一个定时任务,在 5 分钟后自动清空所有规则并恢复默认 ACCEPT,防止失联:
echo 'iptables -P INPUT ACCEPT; iptables -F' | at now + 5 minutes。 - 逐条添加新规则,每加一条用
iptables -L -v -n --line-numbers确认。 - 另开一个终端测试业务端口和管理端口是否可达。
- 确认无误后保存规则,并取消刚才的定时恢复任务:
atrm。
这个“保险丝”做法我已经救过自己很多次。生产环境永远不要裸奔改 INPUT 链,尤其是在默认策略为 DROP 的情况下。
生产最佳实践 Checklist
- 默认拒绝所有入站,出站按需限制。
- 只开放业务必需的端口,SSH 建议改端口 + 密钥登录 + fail2ban。
- 关键管理端口限定来源 IP。
- 定期备份规则,变更前先在测试机验证。
- 结合日志平台对 DROP 日志做告警。
- IPv4 与 IPv6 同步防护。
- 把防火墙规则纳入版本控制,变更走 Code Review。
- 复杂规则用自定义链或 nftables 集合拆分,避免单链臃肿。
总结
iptables 依然是理解 Linux 网络过滤的必修课,nftables 是未来方向,firewalld 则更适合想“开箱即用”的场景。无论选哪一套,核心都是:默认拒绝、最小权限、持久化、可审计。把防火墙当成代码来管理,写进 Git、做 Code Review、定期演练,才是生产环境该有的样子。