Linux 防火墙完全指南:从 iptables 到 firewalld

目录

理解 netfilter 内核框架、掌握 iptables 命令与四表五链、 firewalld 的 zone/service/rich-rule 机制。

一、为什么需要防火墙

安全不是"装上去"的,是"配出来"的

Linux 系统安全有三道基础防线:

  1. 身份认证(PAM、SSH Key、sudo)—— 谁能进来
  2. 访问控制(文件权限、SELinux/AppArmor)—— 进来后能碰什么
  3. 网络防火墙(netfilter/iptables/firewalld)—— 谁能从网络访问什么端口

防火墙位于第一道防线之前——它在网络数据包到达应用程序之前就做出放行或拒绝的决定。没有防火墙,你的服务器就像一栋没有围墙的房子,每扇窗户(端口)都可能被试探。

iptables 和 firewalld 的本质

一个重要认知:iptables 和 firewalld 都不是防火墙本身,它们只是管理工具。 真正的防火墙是 Linux 内核中的 netfilter 框架。

用户空间(你敲命令的地方)
  ┌─────────────┐     ┌──────────────┐
  │  iptables   │     │  firewalld   │  ← 管理工具
  │  (传统 CLI)  │     │  (动态守护进程) │
  └──────┬──────┘     └──────┬───────┘
         │                   │
         ▼                   ▼
    ┌─────────────────────────────┐
    │         netfilter            │  ← 内核态包过滤框架
    │   (hooks in kernel stack)    │
    └─────────────────────────────┘

一句话总结:无论你用 iptables 命令行还是 firewall-cmd 图形界面,最终都是往 netfilter 里写入过滤规则。


二、地基:netfilter 框架

理解 netfilter 是整个防火墙知识体系的地基。跳过这块直接背命令,大概率会出问题还不知道原因。

2.1 先用生活类比理解"数据包的旅程"

在接触任何命令之前,先建立一个画面感。想象你是一份快递,要从 A 城送到 B 城某栋楼里的一个人手上:

  • 快递车进入大楼分拣区 → 这是 PREROUTING(刚到,还没决定给谁)
  • 分拣员看了一眼地址:“哦,这栋楼 3 楼的” → 这是路由决策(判断是不是自己人)
  • 如果是给本楼的人,快递上 3 楼交给收件人 → 这是 INPUT
  • 如果是寄给隔壁楼的,从后门转发出去 → 这是 FORWARD
  • 楼里的人自己寄出快递,从 3 楼拿下楼 → 这是 OUTPUT
  • 快递最终装车离开大楼 → 这是 POSTROUTING

防火墙规则就相当于在分拣区、楼梯口、后门这些位置安排了安检员。你写 iptables 规则,就是在告诉每个位置的安检员:“看到来自 XX 的放行,其他一律拦下。”

有了这个画面,我们再来看技术细节。

netfilter 在 Linux 内核网络协议栈中注册了 5 个钩子点——你可以把它们理解为快递大楼里的 5 个安检关卡,每个经过的网络数据包都会依次经过这些位置:

                     ┌──────────────┐
                     │   进入本机    │
                     │  (Local In)  │
                     └──────▲───────┘
                            │
                    ┌───────┴────────┐
                    │  INPUT 链      │  ← 钩子点3:发往本地进程
                    └───────┬────────┘
                            │
              ┌─────────────┴──────────────┐
              │       路由决策(第二次)     │
              │    "这个包是给我的吗?"      │
              └─────────────┬──────────────┘
                            │
  数据包到达网卡             │
       │                    │
       ▼                    │
┌──────────────┐    ┌───────┴────────┐    ┌──────────────┐
│ PREROUTING   │    │  FORWARD 链    │    │  OUTPUT 链   │
│ 链            │───▶│  (转发)         │───▶│  (本地发出)   │
│ 钩子点1       │    │  钩子点4        │    │  钩子点5      │
└──────────────┘    └───────┬────────┘    └──────▲───────┘
                            │                    │
                   ┌────────┴────────┐  ┌────────┴────────┐
                   │  POSTROUTING    │  │   本地进程       │
                   │  链              │  │   发出数据包      │
                   │  钩子点2         │  └─────────────────┘
                   └────────┬────────┘
                            │
                            ▼
                     ┌──────────────┐
                     │   从网卡出去  │
                     │  (Local Out) │
                     └──────────────┘

五个钩子点的职责:

钩子点快递类比触发时机你什么时候用
PREROUTING快递刚进分拣区包刚到达,路由前做端口映射(DNAT)
INPUT快递上楼梯交给收件人确定是发给本机的过滤进站流量(最常用)
FORWARD快递从中转通道去隔壁楼确定是转发给他人的路由器/NAT 网关
OUTPUT楼里的人下楼寄快递本机发出数据包限制本机出站
POSTROUTING快递装车准备离开包即将离开网卡内网上网(SNAT/MASQUERADE)

2.2 表与链的关系

这里有一个新手特别容易迷糊的点:"表"和"链"是什么关系?

  • 链(Chain):对应一个安检关卡,是排好顺序的"规则列表"。数据包到了这个关卡,就从头到尾逐条匹配。
  • 表(Table):按"工种"对规则分组。同一种工种(比如 NAT 工种)可以在不同的关卡(PREROUTING、OUTPUT、POSTROUTING)干活。

一句话记忆:链 = 在哪查;表 = 查什么类型的事。比如"在大楼入口(PREROUTING 链)做地址转换(nat 表)“,或者"在楼道口(INPUT 链)做放行/拦截(filter 表)”。


三、iptables 深度讲解

iptables 是 netfilter 最经典的用户态管理工具,1998 年诞生,至今仍是 Linux 防火墙的基石。

3.0 先跑起来:你的第一条 iptables 规则

在深入理论之前,先动手感受一下。在你的测试机器上执行:

# 看一眼当前有没有防火墙规则
iptables -L -n

# 加一条最简单的规则:允许所有人访问本机的 80 端口(HTTP)
iptables -A INPUT -p tcp --dport 80 -j ACCEPT

# 再看一眼,刚才的规则已经在列表里了
iptables -L -n

逐词拆解这条命令(这是你理解 iptables 语法的起点):

片段大白话含义
iptables“我要写防火墙规则”
-A INPUT“追加到 INPUT 链”(INPUT 链 = 检查所有发给我自己的流量)
-p tcp“只针对 TCP 协议”
--dport 80“目标端口是 80”(也就是 HTTP 服务的端口)
-j ACCEPT“匹配了就放行”(j 是 jump 的缩写,跳转到 ACCEPT 动作)

整条命令翻译成人话:“在检查进站流量的规则列表末尾,追加一条规则——凡是 TCP 协议、目标是 80 端口的包,通通放行。”

感受完这一条后,下面我们系统学习背后的理论。放心,你不需要一次记住所有表名和链名——先有一个印象,实践中反复查、反复用,自然就熟了。

3.1 四张表与五条链

四张表(按优先级从高到低):

表名功能一句话大白话你什么时候需要用到
raw连接追踪豁免“这个包别跟踪了,直接过”基本用不到,高性能场景才考虑
mangle数据包修改“给这个包做点手脚”极少用,改了包头的 TTL/MARK 等字段
nat网络地址转换“把包的地址换一下”做端口映射、内网上网时用
filter包过滤“这个包放行还是拦下?”这就是你 90% 时间在操作的表

新手先记住-t filter 是默认值,所以你日常写的 iptables 命令 90% 都是在操作 filter 表。nat 表偶尔用(端口转发、NAT 网关场景)。raw 和 mangle 你大概率永远不会直接碰。

五条内置链PREROUTINGINPUTFORWARDOUTPUTPOSTROUTING

3.2 表-链映射关系

并非每张表都能在每条链上注册规则——想想也对,你不可能让"NAT 换地址"这件事发生在数据包已经进了本机之后。实际映射如下(只看 filter 和 nat 就够了,其他不用记):

表 ↓ / 链 →PREROUTINGINPUTFORWARDOUTPUTPOSTROUTING
raw
mangle
nat
filter

记忆技巧:filter 表只管"进出本机的"和"经本机转发的",不管路由前后的地址转换。nat 表恰好相反,只管地址转换发生的点(PREROUTING 做 DNAT、POSTROUTING/OUTPUT 做 SNAT)。

3.3 规则结构与命令语法

一条 iptables 规则的结构:

iptables [-t 表名] <操作> <链名> <匹配条件> -j <动作>

常用操作:

操作缩写含义
--append-A在链末尾追加规则
--insert-I在链开头(或指定位置)插入规则
--delete-D删除指定规则
--replace-R替换指定位置的规则
--flush-F清空链中所有规则
--list-L列出链中规则
--policy-P设置链的默认策略

基本命令格式:

# 查看规则(推荐加 -v -n 看详细信息,不加 -n 会反向 DNS 查询,很慢)
iptables -L -n -v --line-numbers

# 查看 nat 表
iptables -t nat -L -n -v

# 添加规则到 filter 表的 INPUT 链(-t filter 可省略,默认就是 filter)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# 指定位置插入(第 3 条之前)
iptables -I INPUT 3 -p tcp --dport 80 -j ACCEPT

# 按编号删除(先用 --line-numbers 查看编号)
iptables -D INPUT 3

# 按内容删除
iptables -D INPUT -p tcp --dport 80 -j ACCEPT

# 设置链的默认策略
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

3.4 匹配条件详解

iptables 的匹配条件分为基本匹配扩展匹配两类。

3.4.1 基本匹配(不需要 -m 加载模块)
-p tcp|udp|icmp       # 协议
-s 192.168.1.0/24     # 源地址
-d 10.0.0.1           # 目标地址
-i eth0               # 入站网卡(用于 INPUT/PREROUTING)
-o eth1               # 出站网卡(用于 OUTPUT/POSTROUTING)
3.4.2 扩展匹配(需要 -m 加载模块)
# TCP/UDP 端口匹配
-p tcp -m tcp --dport 80          # 目标端口
-p tcp -m tcp --sport 1024:65535  # 源端口范围
-p tcp -m multiport --dports 80,443,8080  # 多端口

# 连接状态匹配(极其重要!)
-m conntrack --ctstate NEW,ESTABLISHED,RELATED
# 或者用传统写法(仍然有效)
-m state --state NEW,ESTABLISHED,RELATED

# IP 范围
-m iprange --src-range 192.168.1.100-192.168.1.200

# MAC 地址
-m mac --mac-source 00:11:22:33:44:55

# 限制速率(防暴力破解的关键模块)
-m limit --limit 5/minute --limit-burst 10

# 最近连接记录
-m recent --name ssh_attack --rcheck --seconds 60 --hitcount 5

# 注释(给自己看的备注)
-m comment --comment "Allow SSH from office"
3.4.3 连接状态详解

先看比喻:打电话的过程——你拨号给朋友(发起连接),朋友接起来说"喂"(连接建立),你们开始聊(数据来往),最后挂断(连接结束)。netfilter 的 conntrack 模块就是"电话总机",记录每一通电话进行到哪一步了。

这是 iptables 中最重要的概念之一。理解了它,防火墙规则可以从"一条一条写"变成"两三条搞定"。

状态含义电话比喻实际例子
NEW新连接的第一个包你刚拨出号码,对方还没接客户端发起 TCP SYN
ESTABLISHED已建立的连接电话接通了,正在通话中TCP 三次握手完成后的数据包
RELATED与已有连接相关的你叫朋友顺带传个话给旁边的人FTP 数据通道、ICMP 差错报文
INVALID无法识别的包线路噪音,不知道谁打来的损坏的包、状态不匹配的包

这个概念的实战价值:利用状态匹配,你的防火墙规则可以大幅简化:

# 就这两条,覆盖了 90% 的回包逻辑:
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT  # 放行所有"回话"
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP                # 丢弃垃圾包

第一条放行所有"已经建立的对话的后续回复"——比如你访问了一个网站,网站回复你的 HTML 页面就自动被放行。第二条丢弃来路不明的包。

从此你只需要为真正对外开放的服务(如 SSH 的 22 端口、HTTP 的 80 端口)添加 NEW 状态的放行规则,其他的回包和关联流量这两条都帮你搞定了。

3.5 常用 Target(动作)

Target含义说明
ACCEPT放行数据包通过,继续协议栈处理
DROP静默丢弃不给发送方任何回应,对方超时才知道
REJECT拒绝并通知丢弃同时回复 ICMP 错误(默认 icmp-port-unreachable
LOG记录日志不终止匹配,记录后继续匹配下一条规则
SNAT源地址转换修改源 IP(用于内网上网)
DNAT目标地址转换修改目标 IP(用于端口映射/负载均衡)
MASQUERADE动态 SNAT自动使用出口网卡 IP 做源地址(适用于动态 IP)
RETURN返回从当前链返回到调用链
REDIRECT端口重定向将包重定向到本机另一个端口(透明代理用)

DROP vs REJECT 的选择(用人话比喻):

  • REJECT = 门卫说"你不能进",你立刻知道被拒绝了
  • DROP = 门卫装没看见你,你站在门口等半天也不知道是被拒了还是门卫不在

实际选择:

  • 对外服务端口:用 REJECT,让合法客户端快速知道端口不可达
  • 扫描防御/边界防护:用 DROP,让攻击者搞不清是端口没开还是 IP 不存在
  • 内网环境:优先 REJECT,方便排错

3.6 实战示例(由浅入深)

示例 1:最简 Web 服务器
#!/bin/bash
# 清空所有规则(安全起点)
iptables -F
iptables -X
iptables -t nat -F

# 默认策略:入站和转发 DROP,出站 ACCEPT
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)和 HTTP/HTTPS(80/443)
iptables -A INPUT -p tcp -m multiport --dports 22,80,443 -m conntrack --ctstate NEW -j ACCEPT

# 允许 ping(可选的)
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
示例 2:防 SSH 暴力破解
# 60 秒内新连接超过 5 次就拉黑 300 秒
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent \
    --update --seconds 60 --hitcount 6 -j DROP
示例 3:端口转发(DNAT)
# 将访问本机 8080 端口的流量转发到内网 192.168.1.100:80
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80

# 关键:必须开启转发并允许 FORWARD 链
sysctl -w net.ipv4.ip_forward=1
iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 80 -j ACCEPT
示例 4:内网共享上网(SNAT/MASQUERADE)
# 开启转发
sysctl -w net.ipv4.ip_forward=1

# 固定公网 IP 用 SNAT
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10

# 动态公网 IP(如 PPPoE)用 MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

3.7 规则持久化

iptables 规则存储在内存中,重启丢失。持久化有多种方式:

方法一:iptables-save/restore(最通用)

# 保存当前规则
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6

# 恢复规则
iptables-restore < /etc/iptables/rules.v4

方法二:使用 netfilter-persistent 服务(Debian/Ubuntu)

apt install iptables-persistent
# 安装过程中会提示保存当前规则
# 之后手动更新:
netfilter-persistent save

方法三:使用 iptables-services(RHEL/CentOS)

yum install iptables-services
systemctl enable iptables
systemctl start iptables
# 规则保存到 /etc/sysconfig/iptables
service iptables save

四、firewalld 深度讲解

4.1 为什么需要 firewalld

iptables 有一个架构性痛点:每次修改规则都要刷新整条链。 这意味着:

  • 添加一条临时测试规则也很"重"
  • 修改规则时,极短的时间窗口内规则不完整
  • 没有"网络环境"的概念(你在咖啡厅和办公室需要用不同的规则)

firewalld 在 2011 年由 Red Hat 引入,从 RHEL 7 / CentOS 7 开始成为默认防火墙。

通俗理解 firewalld 做了什么:如果把 iptables 比作直接操作机器指令,firewalld 就是给这套指令加了一个"图形界面"——它把 iptables 那些让人头晕的 -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT 封装成了你一看就懂的"放行 HTTP 服务"。

它的核心设计哲学是:

规则应该是动态的、可感知网络环境的、易于管理的。

4.2 架构概览:firewalld 怎么"坐在 iptables 上面"

┌───────────────────────────────────────┐
│            firewall-config             │  ← GUI 工具(可选)
│            firewall-cmd                │  ← CLI 工具
│            firewall-offline-cmd        │  ← 离线配置工具
└────────────────┬──────────────────────┘
                 │ (D-Bus)
         ┌───────▼────────┐
         │  firewalld      │  ← 守护进程(持有内存中的规则)
         │  (daemon)       │
         └───────┬────────┘
                 │
         ┌───────▼────────┐
         │   iptables      │  ← firewalld 底层仍通过 iptables 操作 netfilter
         │   (backend)     │
         └───────┬────────┘
                 │
         ┌───────▼────────┐
         │   netfilter     │
         └────────────────┘

关键认知:firewalld 底层还是调用 iptables,你可以用 iptables -L -n 看到 firewalld 生成的规则。firewalld 的价值在于它对规则进行了更高级的抽象和组织。

4.3 核心概念:Zone(区域)

Zone 是 firewalld 最核心的创新——它把"我在哪"和"放行什么"绑定在一起。

预定义 Zone 一览(按信任度从低到高):

一句话理解 zone:zone = 给网卡贴标签。一张网卡只能贴一个标签,标签决定了"默认有多信任连到这个网卡的流量"。

Zone信任度默认行为你什么时候用
drop0%所有入站丢包,无任何回复极致隐身,外面完全感觉不到这台机器的存在
block10%入站被拒,返回 ICMP 禁止比 drop 温和,对方至少知道被拒了
public20%不信任,仅放行你指定的端口☕ 公共 WiFi、咖啡厅、云服务器默认选这个
external30%NAT 模式,开启伪装路由器外网口
dmz40%仅允许特定入站隔离区的服务器(前后都有防火墙)
work60%放行多数内网服务公司办公网
home70%放行多数内网服务家里网络
internal80%放行更多内网服务全是你自己机器的内部网络
trusted100%全部放行绝对信任(⚠️ 慎用)

新手只需关注 public:云服务器 99% 的场景用 public 就够了。其他 zone 主要面向多网卡或频繁切换网络环境的笔记本用户。

zone 的核心行为由两个要素决定:

  1. target:对不匹配任何规则的包的最终处理

    • default:按 zone 的默认行为
    • ACCEPT:放行
    • DROP:静默丢弃
    • %%REJECT%%:拒绝并通知
  2. services/ports/rules:在 target 之前匹配的放行清单

# 查看所有 zone
firewall-cmd --get-zones

# 查看当前活动 zone(及绑定的网卡)
firewall-cmd --get-active-zones

# 查看某个 zone 的详细配置
firewall-cmd --zone=public --list-all

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

# 修改默认 zone
firewall-cmd --set-default-zone=home

# 将网卡绑定到 zone(网卡只能属于一个 zone)
firewall-cmd --zone=internal --add-interface=eth1 --permanent

4.4 核心概念:Service(服务)

Service 是 firewalld 的第二大抽象——它将"开放什么端口"封装为"放行什么服务"。

预定义 service 文件位置/usr/lib/firewalld/services/

# 查看所有预定义 service
firewall-cmd --get-services

# 查看某个 service 的定义(本质是 XML 文件)
cat /usr/lib/firewalld/services/ssh.xml

一个典型的 service XML 定义:

<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>SSH</short>
  <description>Secure Shell (SSH) 远程登录协议</description>
  <port protocol="tcp" port="22"/>
</service>

自定义 service(以常见 Web 应用端口为例):

# 创建自定义 service 文件
cat > /etc/firewalld/services/myapp.xml << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>MyApp</short>
  <description>My Application - Web + API ports</description>
  <port protocol="tcp" port="8080"/>
  <port protocol="tcp" port="8443"/>
</service>
EOF

# 重载让 firewalld 识别新 service
firewall-cmd --reload

# 现在可以像使用预定义服务一样使用
firewall-cmd --add-service=myapp --permanent

4.5 Runtime vs Permanent

这是 firewalld 最容易让新手踩坑的设计。Firewalld 有两套配置:

模式生效时机生命周期
Runtime(默认)立即生效重启后丢失
Permanent--reload 后生效永久保存

三种操作模式:

# 模式1:仅修改 runtime(立即生效,重启丢失)—— 适合临时测试
firewall-cmd --add-service=http

# 模式2:仅修改 permanent(不立即生效,reload 后生效)—— 适合批量配置
firewall-cmd --add-service=http --permanent

# 模式3:同时修改 runtime 和 permanent(立即生效 + 持久化)—— 最常用
firewall-cmd --add-service=http --permanent && firewall-cmd --add-service=http
# 或者反过来,先 runtime 测试,确认无误再加 permanent
firewall-cmd --add-service=http                          # 立即测试
firewall-cmd --add-service=http --permanent               # 确认无误后持久化

常见坑:你 --add-service=http 没加 --permanent,重启后配置丢了。或者加了 --permanent 忘了 --reload,一直等到重启才生效。建议先用 runtime 模式测试,确认无误后再加上 --permanent 持久化。

reload 的两种方式:

firewall-cmd --reload          # 重载配置,不中断现有连接(推荐)
firewall-cmd --complete-reload # 完全重载,会短暂中断连接

4.6 Rich Rules(富规则)

当 service 和 port 的粒度不够时,rich rule 提供了更精细的控制。它的语法直观、接近自然语言。

# 格式:
firewall-cmd --add-rich-rule='rule [family="ipv4|ipv6"] <匹配条件> <动作>'

# 示例1:仅允许特定 IP 访问 SSH
firewall-cmd --add-rich-rule='rule family="ipv4" source address="203.0.113.10" service name="ssh" accept' --permanent

# 示例2:允许某网段访问多个端口
firewall-cmd --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port port="3306" protocol="tcp" accept' --permanent

# 示例3:速率限制(防止暴力破解)
firewall-cmd --add-rich-rule='rule service name="ssh" limit value="5/m" accept' --permanent

# 示例4:记录日志后丢弃
firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" log prefix="DROPPED: " level="warning" limit value="10/m" drop' --permanent

# 示例5:端口转发(DNAT)
firewall-cmd --add-rich-rule='rule family="ipv4" forward-port port="8080" protocol="tcp" to-port="80" to-addr="192.168.1.100"' --permanent

# 查看 rich rules
firewall-cmd --list-rich-rules

4.7 Direct Rules(直通规则)

Direct rules 是 firewalld 的"逃生舱"——当你需要的功能 firewalld 无法抽象时,可以直接写原始 iptables 语法。

# 语法:
firewall-cmd --direct --add-rule <ipv4|ipv6> <table> <chain> <priority> <args>

# 示例:在 filter 表的 INPUT 链最前面添加一条 DROP 规则
firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 23 -j DROP

# 查看 direct rules
firewall-cmd --direct --get-all-rules

使用建议:能用 rich rule 解决的不要用 direct rule。Direct rule 绕过了 firewalld 的 zone/service 抽象,相当于从高级语言掉回汇编——失去了 firewalld 的主要优势。只有在需要精细控制 conntrack、mangle 表等场景下才使用。

4.8 实战示例

示例 1:将 Web 服务器从零配置到安全上线
# 第一步:确认当前状态
firewall-cmd --state
firewall-cmd --get-default-zone
firewall-cmd --list-all

# 第二步:设置默认 zone 为 public
firewall-cmd --set-default-zone=public

# 第三步:开放必要服务
firewall-cmd --add-service=ssh --permanent
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent

# 第四步:SSH 加 IP 白名单
firewall-cmd --remove-service=ssh --permanent  # 先移除服务级放行
firewall-cmd --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" accept' --permanent

# 第五步:应用并验证
firewall-cmd --reload
firewall-cmd --list-all
示例 2:工作笔记本——根据网络环境切换 zone
# 办公室(信任网络)
firewall-cmd --change-zone=work
# → 自动开放更多服务

# 咖啡厅(公共网络)
firewall-cmd --change-zone=public
# → 严格限制入站,只保留基本出站

# 查看当前 zone
firewall-cmd --get-active-zones

五、firewalld vs iptables 全面对比

维度iptablesfirewalld
配置方式命令行直接操作规则通过 zone/service/rich-rule 抽象
规则生效立即修改内核规则表运行时生效,支持不中断重载
持久化需借助外部工具(iptables-save 或服务)内置 --permanent 机制
动态修改每次修改需刷新整条链(原子操作)支持单条规则的增删,不影响现有连接
网络环境感知Zone 机制,可按网络环境切换策略
学习曲线陡峭,需理解表/链/匹配/目标平缓,概念更接近人类思维
精细控制度极致高层抽象,精细场景需 rich/direct rule
性能轻量,无守护进程开销有守护进程和 D-Bus 通信开销(通常可忽略)
生产稳定性20+ 年验证10+ 年验证,RHEL/CentOS 7+ 默认

什么时候用什么

优先使用 firewalld 的场景:

  • RHEL/CentOS/Fedora 7+ 系统(它是默认防火墙)
  • 标准 Web 服务器、数据库服务器(service 抽象足够)
  • 需要在不同网络环境切换策略(笔记本)
  • 团队中有初级运维,降低学习成本

仍然需要使用 iptables 的场景:

  • 复杂的 NAT/路由场景(多出口、策略路由)
  • Docker/K8s 环境(它们直接操作 iptables 规则,与 firewalld 可能冲突)
  • 嵌入式 Linux / 容器(不想引入额外的守护进程依赖)
  • 遗留系统维护(RHEL 6 / CentOS 6 及更早)
  • 对性能极度敏感的场景(高频包处理)

共存警告:firewalld 和 iptables 服务不能同时运行。在 RHEL 7+ 上,如果你 systemctl start iptables,它和 firewalld 是互斥的——两个服务会争抢 netfilter 规则的控制权。


六、实战场景

6.1 Web 服务器防火墙

假设你在云上有一台 Web 服务器,需要开放 80/443,SSH 只允许公司 IP 网段访问,还要防止简单 DDoS。

firewalld 方案:

#!/bin/bash
# 设置默认 zone
firewall-cmd --set-default-zone=public

# 开放 Web
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent

# SSH 仅允许公司 IP 网段
firewall-cmd --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" accept' --permanent

# HTTP 连接速率限制(防简单 CC 攻击)
firewall-cmd --add-rich-rule='rule service name="http" limit value="100/s" accept' --permanent

# 丢弃无效包(通过 direct rule)
firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -m conntrack --ctstate INVALID -j DROP

# 应用
firewall-cmd --reload

iptables 等价方案:

#!/bin/bash
iptables -F; iptables -X
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
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# SSH 白名单
iptables -A INPUT -p tcp -s 203.0.113.0/24 --dport 22 -m conntrack --ctstate NEW -j ACCEPT

# Web 速率限制
iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -m limit --limit 100/s --limit-burst 200 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -m limit --limit 100/s --limit-burst 200 -j ACCEPT

6.2 NAT 网关(iptables)

这是 iptables 当仁不让的主场。假设一台双网卡服务器做网关:

  • eth0:外网(203.0.113.10)
  • eth1:内网(192.168.1.0/24)
#!/bin/bash
# 开启 IP 转发
sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf

# 默认过滤策略
iptables -P FORWARD DROP

# 允许内网访问外网
iptables -A FORWARD -i eth1 -o eth0 -s 192.168.1.0/24 -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# SNAT(内网上网)
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

# 端口映射(将外网 8080 映射到内网 192.168.1.100:80)
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80
iptables -A FORWARD -i eth0 -o eth1 -p tcp -d 192.168.1.100 --dport 80 -m conntrack --ctstate NEW -j ACCEPT

为什么这里不用 firewalld? firewalld 的 NAT 支持主要在 rich rule 的 forward-port 中,对于多网卡、多网段、策略路由等复杂场景,表达能力不足。直接写 iptables 更清晰、更可控。

6.3 Docker 与防火墙的兼容问题

这是一个高频踩坑点。Docker 直接操作 iptables 规则来实现容器网络,它会:

  1. DOCKERDOCKER-USER 自定义链中插入规则
  2. 默认绕过 INPUT 链的控制(因为 Docker 的转发走 FORWARD 链)

常见问题: 你明明用 firewalld 禁止了某个端口的外部访问,但 Docker 容器在那个端口上仍然可以被访问。

原因:Docker 在 PREROUTING 做 DNAT,在 FORWARD 放行,而 firewalld 主要管 INPUT 链。两个机制在不同的网络栈层面运行,互不感知。

解决方案:

# 方案1:在 DOCKER-USER 链中加规则(Docker 保证这条链不会被自己覆盖)
iptables -I DOCKER-USER -p tcp --dport 8080 -j DROP

# 方案2:Docker daemon 配置中禁用 iptables 操作(不推荐,会破坏容器网络)
# /etc/docker/daemon.json
{
  "iptables": false
}

# 方案3(推荐):firewalld + docker 共存最佳实践
# 将 docker0 网桥加入 trusted zone(让容器间通信正常)
firewall-cmd --zone=trusted --add-interface=docker0 --permanent
firewall-cmd --reload

# 容器对外暴露的端口用 firewalld 的 direct rule 控制 FORWARD 链
firewall-cmd --direct --add-rule ipv4 filter FORWARD 0 \
    -i eth0 -o docker0 -p tcp --dport 8080 -m conntrack --ctstate NEW -j ACCEPT

七、进阶话题

7.1 调试技巧

排查"为什么这个包被丢弃了":

# 方法1:在规则链末尾加 LOG 规则,捕获不匹配任何规则的包
iptables -A INPUT -j LOG --log-prefix "IPTABLES-DROPPED: " --log-level 4

# 然后查看日志
tail -f /var/log/kern.log | grep IPTABLES-DROPPED
# 或者
dmesg -w | grep IPTABLES-DROPPED

firewalld 调试:

# 开启 firewalld 调试日志
firewall-cmd --set-log-denied=all

# 查看被拒绝的包
dmesg | grep "FINAL_REJECT"

# 查看 firewalld 底层生成的 iptables 规则
iptables -L -n -v
iptables -t nat -L -n -v

查看规则计数器(判断规则是否被命中):

# 重置计数器后观察
iptables -Z
# 一段时间后:
iptables -L -n -v  # pkts 和 bytes 列显示每条规则匹配的流量

7.2 fail2ban 联动

fail2ban 是一个经典的入侵防御工具——它监控日志(如 SSH 登录失败),然后动态调用防火墙封禁攻击者 IP。

# 安装
apt install fail2ban  # Debian/Ubuntu
yum install fail2ban  # RHEL/CentOS

# 基础配置 /etc/fail2ban/jail.local
[DEFAULT]
bantime = 600
findtime = 600
maxretry = 5
banaction = firewallcmd-rich-rules     # 使用 firewalld
# banaction = iptables-multiport       # 使用 iptables

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s

# 启动
systemctl enable fail2ban
systemctl start fail2ban

# 查看封禁状态
fail2ban-client status sshd

fail2ban 会自动往 firewalld 中插入 rich rule 或在 iptables 中插入 REJECT 规则来封禁攻击者 IP。封禁到期后自动解除。


八、常见问题排查

问题 1:加了规则为什么还是不通?

排查清单:

  1. 规则顺序:iptables 规则是从上到下匹配,命中即停止。检查是不是前面的规则匹配了

    iptables -L -n -v --line-numbers
    
  2. 链的默认策略iptables -L -n 看链的 policy 是不是 DROP

    iptables -P INPUT ACCEPT  # 临时放行所有,确认是防火墙问题
    
  3. firewalld 的 zone 不对:网卡可能绑定到了其他 zone

    firewall-cmd --get-active-zones
    
  4. firewalld runtime vs permanent

    # runtime 有但 permanent 没有 → 重启后丢失
    firewall-cmd --list-services          # 当前生效
    firewall-cmd --permanent --list-services  # 持久化配置
    
  5. Docker 绕过了防火墙:见 6.3 节

问题 2:firewalld 和 iptables 服务冲突

# 症状:两个服务同时 running,规则混乱
systemctl status firewalld
systemctl status iptables

# 解决:禁用 iptables 服务,统一用 firewalld
systemctl stop iptables
systemctl disable iptables
systemctl restart firewalld

问题 3:iptables 规则重启后丢失

# Debian/Ubuntu
apt install iptables-persistent
netfilter-persistent save

# RHEL/CentOS 7(如果直接使用 iptables 而非 firewalld)
yum install iptables-services
service iptables save
systemctl enable iptables

问题 4:firewalld reload 后 SSH 断了

这通常是因为你在 permanent 中移除了 SSH 服务或 zone 配置有误,reload 时应用了新规则。

# 修复:通过控制台(非 SSH)登录
firewall-cmd --add-service=ssh --permanent
firewall-cmd --reload

# 预防:reload 前先在 runtime 测试
firewall-cmd --add-service=ssh
# 确认 OK 后再加 --permanent

九、总结与最佳实践

核心理念

  1. 最小权限原则:默认 DROP 一切,只开放必需的端口和服务
  2. 白名单优于黑名单:指定"谁可以"而不是"谁不可以"
  3. 分层防御:防火墙只是安全链中的一环,配合 SSH Key、SELinux、fail2ban、定期审计使用

操作 Checklist

部署新服务器时,按以下顺序配置防火墙:

  • 确认防火墙状态:firewall-cmd --statesystemctl status firewalld
  • 确认当前规则:firewall-cmd --list-alliptables -L -n -v
  • 设置默认策略:入站 DROP / 转发 DROP / 出站 ACCEPT
  • 放行回环接口(lo
  • 放行 ESTABLISHED,RELATED 状态
  • 丢弃 INVALID 状态
  • 开放 SSH(最好加 IP 白名单或速率限制)
  • 开放业务端口(HTTP/HTTPS/DB 等)
  • 持久化配置
  • 从另一个终端验证 SSH 还能登录(重要!不要关掉当前会话)
  • 配置 fail2ban(可选但强烈推荐)

推荐学习路径

小白起步:
  1. 理解 netfilter 五个钩子点的数据包流转(本文第二章)
  2. 能用 iptables 写出"允许 SSH + 默认拒绝"的基本规则
  3. 转到 firewalld,掌握 zone / service / runtime-vs-permanent
  4. 在测试虚拟机中实践所有本文示例

中级进阶:
  5. 理解 conntrack 状态机(NEW/ESTABLISHED/RELATED/INVALID)
  6. 能用 iptables 配置 NAT 网关(SNAT/DNAT/MASQUERADE)
  7. 会排查防火墙与 Docker/K8s 的交互问题

高级深入:
  8. 自定义 iptables 链实现模块化规则管理
  9. mangle 表与 QoS(流量整形)
  10. 大规模规则集的性能优化(ipset)
  11. 阅读 netfilter 内核源码(nf_hook_ops 结构)

最后的提醒:防火墙的配置是"差一点都不行"的。一条顺序错误或遗漏的规则,可能让你从互联网上裸奔,也可能把你自己锁在服务器外面。在测试环境充分验证,在生产环境谨慎操作,永远保留一个独立于防火墙的控制台通道(如云厂商的 VNC 控制台)。


本文基于 Linux 内核 5.x/6.x 系列、iptables 1.8.x、firewalld 1.x 版本编写,适用于 RHEL 7~9 / CentOS 7~9 / Fedora 35+ / Debian 10~12 / Ubuntu 20.04~24.04 等主流发行版。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐