firewalld 与 iptables 常用规则实战:从放行端口到防爆破
给一台 Linux 服务器配防火墙,常见翻车方式有三种:规则加了但重启就没了、规则写了但顺序不对永远不命中、人在外网把自己的 SSH 也挡在门外。本文把 firewalld 和 iptables 两套常用工具的实战要点一次讲清:firewalld 的 zone 与运行时/永久规则、端口与服务放行、富规则限源 IP;iptables 的表与链复习、状态跟踪与限速防爆破的规则组;再加上持久化、验证与排障方法,最后说明两套工具怎么取舍。
适用环境:firewalld 部分以 Rocky Linux 9 / CentOS 系为例,iptables 部分命令在各发行版通用(底层可能是 nftables 的兼容层,不影响用法)。示例地址均为文档段:服务器公网地址 192.0.2.10,管理来源网段 192.0.2.0/24。
前置条件
- root 权限,且当前有一条已建立的 SSH 会话——改防火墙全程不要断开这条会话,规则写错时它是唯一的回头路。
- 确认在用哪套工具:
systemctl status firewalld,以及发行版是否默认 nftables 后端。 - 先列清要放行的端口清单和来源范围,再动手写规则,不要边想边加。
- 如果是云服务器,先确认云厂商安全组与本机防火墙是两道独立关卡,两边都要放行。
第一步:firewalld 的 zone 与规则的两种状态
firewalld 用 zone(区域)组织规则:每块网卡或每个来源网段归属一个 zone,zone 里定义允许的服务与端口以及默认动作。常用 zone 有 public(默认,不信任网络)、trusted(全放行)、drop(全丢弃)等。先看当前状态:
systemctl enable --now firewalld
firewall-cmd --get-default-zone
firewall-cmd --get-active-zones
firewall-cmd --zone=public --list-all
最要命的概念是规则的两种状态:运行时规则(不加 --permanent)立即生效,但 firewalld 重载或机器重启后消失;永久规则(加 --permanent)写入配置文件,但不会立即生效,需要 firewall-cmd --reload 才加载。日常操作的正确姿势是:所有正式变更都写永久规则再 reload;运行时规则只用于临时测试,并清楚它会在 reload 时被永久配置覆盖。
第二步:firewalld 常用放行写法
# 放行单个端口(永久)
firewall-cmd --permanent --zone=public --add-port=8080/tcp
# 放行预定义服务(一组端口的集合,如 https=443/tcp)
firewall-cmd --permanent --zone=public --add-service=https
# 查看服务都包含哪些端口
firewall-cmd --info-service=https
# 统一生效并复核
firewall-cmd --reload
firewall-cmd --zone=public --list-ports
firewall-cmd --zone=public --list-services
删除把 --add-* 换成 --remove-* 即可。端口段用 --add-port=8000-8010/udp。需要只允许某个来源访问时(如 SSH、数据库端口),不要用 --add-port 全网放行,改用富规则(rich rule)限定来源:
# 只允许 192.0.2.0/24 访问 22 端口
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.0.2.0/24" port port="22" protocol="tcp" accept'
# 变更来源网段时,把某个 zone 直接绑定来源(该来源的所有流量按此 zone 策略走)
firewall-cmd --permanent --zone=trusted --add-source=192.0.2.0/24
firewall-cmd --reload
firewall-cmd --zone=public --list-rich-rules
第三步:iptables 的表与链,一段复习清楚
iptables 按功能分表:filter 表管过滤(默认表),nat 表管地址转换,mangle 表管报文标记与修改,raw 表管连接跟踪豁免。每个表里有链,filter 表的三个内置链是运维最常用的:INPUT(发给本机进程的包)、OUTPUT(本机发出的包)、FORWARD(经本机转发的包,网关和容器宿主机才重点用)。包按链中规则自上而下逐条匹配,第一条命中且带终结目标(ACCEPT/DROP)的规则说了算,都不命中才走链的默认策略(policy)。记住这三点——表决定能干什么、链决定在哪个位置干、顺序决定谁说了算——后面排障全靠它。
第四步:iptables 典型规则组
一组能直接用的基础规则:先放行已建立连接和回环,再放行指定服务,SSH 用 recent 模块限速防爆破,最后默认拒绝并对被丢弃的包限量记日志:
# 1)状态跟踪:已建立/相关连接直接放行(务必放在靠前位置)
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
# 2)常规服务放行
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# 3)SSH 限速防爆破:同一来源 60 秒内新建连接超过 4 次即丢弃
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set --name SSHBRUTE
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 --name SSHBRUTE -j DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# 4)被拒绝的包限速记一条日志,便于排障(每分钟最多 5 条)
iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables-drop: " --log-level 4
# 5)默认策略收紧为 DROP(执行前确认上面的放行规则齐了!)
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -L INPUT -n -v --line-numbers
说明两点:recent 规则必须成对出现,先 --set 记录,再 --update 判断,两条顺序不能反;比起网上传抄的“端口敲门”(knocking),限速加来源限制加密钥登录才是务实组合——敲门实现复杂、客户端支持差,而爆破的本质是尝试次数,限速直接掐住它。ICMP 建议只放行必要的类型或加限速,不要一刀切全禁,否则路径 MTU 发现等机制会出问题。
这组规则的组织顺序也值得留意,它体现了一个通用原则:先放行“回包”性质的流量(状态跟踪、回环),再放行对外服务,然后是对高危端口的精细化处理(SSH 限速),最后才是日志与默认拒绝。按这个顺序写,规则的意图自上而下就是从宽到严、从通用到特殊,后来的人接手时能顺着读懂;反过来如果把默认 DROP 写成第一条规则去逐条“捞”放行,后续每加一个服务都要在链里找插入位置,迟早插错。iptables 没有注释区,规则顺序和分组本身就是文档的一部分,再配合 iptables-save 导出文件纳入配置备份,变更才有据可查。
第五步:规则持久化与重启验证
iptables 命令写的是内存中的规则,重启即失。持久化按发行版选机制:Debian/Ubuntu 装 iptables-persistent(保存到 /etc/iptables/rules.v4),RHEL 系可用 iptables-services 或干脆统一交给 firewalld/nftables 管理。手工保存与恢复:
iptables-save > /etc/iptables/rules.v4
iptables-restore < /etc/iptables/rules.v4
# firewalld 侧的等价检查:永久配置就是持久化配置
firewall-cmd --zone=public --list-all
firewall-cmd --permanent --zone=public --list-all
重启验证别偷懒:重启后重新 SSH 登录,从另一台机器逐个验证放行端口可达、未放行端口不通,并在服务器上用 iptables -L -n -v 看计数器——有流量的规则 pkts 列会增长,计数器为 0 的放行规则大概率写了但没命中。
排障:规则不生效时按这个顺序查
- 顺序问题:用
iptables -L --line-numbers核对,一条宽泛的 DROP/REJECT 排在放行规则前面,后面的规则永远轮不到。插入规则用iptables -I INPUT 3 ...指定位置。 - 状态问题(firewalld):永久规则忘了 reload,或临时规则被 reload 冲掉。对比运行时与永久两份 --list-all 输出即可定位。
- 云安全组问题:判断方法是看包到没到机器。在服务器上
tcpdump -i eth0 port 8080,同时从外部发请求:一个包都抓不到,是安全组或上游网络拦的,与本机规则无关;包到了但被回 RST 或无响应,才查本机防火墙。 - 网卡绑错 zone:多网卡机器上流量走的网卡不在预期 zone 里,用
firewall-cmd --get-active-zones核对接口归属。 - 把自己锁在外面:预防胜于补救。改默认策略前,先加一条允许自己当前来源 IP 的规则;大改动时预先挂一个定时回滚:
echo 'iptables-restore < /etc/iptables/rules.bak' | at now + 10 minutes,验证通过再删掉这个 at 任务。
常见问题
firewalld 和 iptables 命令能混着用吗?不要。firewalld 运行时会管理底层规则集,你手工用 iptables 插的规则可能被它的 reload 覆盖,反之亦然。一台机器选定一套作为唯一入口;firewalld 内部需要写原生规则时,用它的 --direct 或改走 nftables 的 inet 家庭统一管理。
加了放行还是连不上,计数器也不涨。包根本没进这条链:可能走的是 FORWARD(容器、网关场景)而不是 INPUT,或流量被 DNAT 改道。先用 tcpdump 确认包从哪块网卡、到哪个端口进来,再顺着链找。
Docker 发布端口绕过了我的 INPUT 规则。Docker 在 nat 和 FORWARD 链上写自己的规则,容器端口的流量不经过 INPUT 链的默认路径。要限制容器端口来源,应在 DOCKER-USER 链上加规则,这是 Docker 官方预留的入口。
firewalld 与直接 iptables/nftables 的取舍
单机或少量服务器、规则以“放行哪些端口和来源”为主:用 firewalld,zone 和 service 的抽象够用且不易写错。多网卡、多网段按来源分策略:firewalld 的 zone 模型同样占优。规则里需要大量地址集、复杂匹配或做网关 NAT:直接上 nftables(集合 set 一次定义批量引用,性能与可读性都比逐条 iptables 好),或 iptables 加 ipset。已经全量容器化的宿主机:以 DOCKER-USER 等框架预留链为界管理,不要和编排工具抢规则所有权。终极原则只有一条:一台机器、一个规则所有者、一套持久化机制。
小结
firewalld 的要害是分清运行时与永久规则、会用富规则限源;iptables 的要害是表链概念和匹配顺序,再配上状态跟踪与 recent 限速,SSH 爆破这类日常威胁就有解。所有规则改动遵循同一节奏:留一条不断的会话、写规则、看计数器验证命中、做持久化、重启再验一遍。流程固定下来,防火墙就从“玄学配置”变成可审计的例行操作。







