Linux 防火墙完全指南:从 iptables 到 firewalld
目录
- [Linux 防火墙完全指南:从 iptables 到 firewalld](#Linux 防火墙完全指南:从 iptables 到 firewalld)
-
- 一、为什么需要防火墙
-
- 安全不是"装上去"的,是"配出来"的
- [iptables 和 firewalld 的本质](#iptables 和 firewalld 的本质)
- [二、地基:netfilter 框架](#二、地基:netfilter 框架)
-
- [2.1 先用生活类比理解"数据包的旅程"](#2.1 先用生活类比理解"数据包的旅程")
- [2.2 表与链的关系](#2.2 表与链的关系)
- [三、iptables 深度讲解](#三、iptables 深度讲解)
-
- [3.0 先跑起来:你的第一条 iptables 规则](#3.0 先跑起来:你的第一条 iptables 规则)
- [3.1 四张表与五条链](#3.1 四张表与五条链)
- [3.2 表-链映射关系](#3.2 表-链映射关系)
- [3.3 规则结构与命令语法](#3.3 规则结构与命令语法)
- [3.4 匹配条件详解](#3.4 匹配条件详解)
-
- [3.4.1 基本匹配(不需要 -m 加载模块)](#3.4.1 基本匹配(不需要 -m 加载模块))
- [3.4.2 扩展匹配(需要 -m 加载模块)](#3.4.2 扩展匹配(需要 -m 加载模块))
- [3.4.3 连接状态详解](#3.4.3 连接状态详解)
- [3.5 常用 Target(动作)](#3.5 常用 Target(动作))
- [3.6 实战示例(由浅入深)](#3.6 实战示例(由浅入深))
-
- [示例 1:最简 Web 服务器](#示例 1:最简 Web 服务器)
- [示例 2:防 SSH 暴力破解](#示例 2:防 SSH 暴力破解)
- [示例 3:端口转发(DNAT)](#示例 3:端口转发(DNAT))
- [示例 4:内网共享上网(SNAT/MASQUERADE)](#示例 4:内网共享上网(SNAT/MASQUERADE))
- [3.7 规则持久化](#3.7 规则持久化)
- [四、firewalld 深度讲解](#四、firewalld 深度讲解)
-
- [4.1 为什么需要 firewalld](#4.1 为什么需要 firewalld)
- [4.2 架构概览:firewalld 怎么"坐在 iptables 上面"](#4.2 架构概览:firewalld 怎么"坐在 iptables 上面")
- [4.3 核心概念:Zone(区域)](#4.3 核心概念:Zone(区域))
- [4.4 核心概念:Service(服务)](#4.4 核心概念:Service(服务))
- [4.5 Runtime vs Permanent](#4.5 Runtime vs Permanent)
- [4.6 Rich Rules(富规则)](#4.6 Rich Rules(富规则))
- [4.7 Direct Rules(直通规则)](#4.7 Direct Rules(直通规则))
- [4.8 实战示例](#4.8 实战示例)
-
- [示例 1:将 Web 服务器从零配置到安全上线](#示例 1:将 Web 服务器从零配置到安全上线)
- [示例 2:工作笔记本------根据网络环境切换 zone](#示例 2:工作笔记本——根据网络环境切换 zone)
- [五、firewalld vs iptables 全面对比](#五、firewalld vs iptables 全面对比)
- 六、实战场景
-
- [6.1 Web 服务器防火墙](#6.1 Web 服务器防火墙)
- [6.2 NAT 网关(iptables)](#6.2 NAT 网关(iptables))
- [6.3 Docker 与防火墙的兼容问题](#6.3 Docker 与防火墙的兼容问题)
- 七、进阶话题
-
- [7.1 调试技巧](#7.1 调试技巧)
- [7.2 fail2ban 联动](#7.2 fail2ban 联动)
- 八、常见问题排查
-
- [问题 1:加了规则为什么还是不通?](#问题 1:加了规则为什么还是不通?)
- [问题 2:firewalld 和 iptables 服务冲突](#问题 2:firewalld 和 iptables 服务冲突)
- [问题 3:iptables 规则重启后丢失](#问题 3:iptables 规则重启后丢失)
- [问题 4:firewalld reload 后 SSH 断了](#问题 4:firewalld reload 后 SSH 断了)
- 九、总结与最佳实践
理解 netfilter 内核框架、掌握 iptables 命令与四表五链、 firewalld 的 zone/service/rich-rule 机制。
一、为什么需要防火墙
安全不是"装上去"的,是"配出来"的
Linux 系统安全有三道基础防线:
- 身份认证(PAM、SSH Key、sudo)------ 谁能进来
- 访问控制(文件权限、SELinux/AppArmor)------ 进来后能碰什么
- 网络防火墙(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 规则
在深入理论之前,先动手感受一下。在你的测试机器上执行:
bash
# 看一眼当前有没有防火墙规则
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 你大概率永远不会直接碰。
五条内置链 :PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING
3.2 表-链映射关系
并非每张表都能在每条链上注册规则------想想也对,你不可能让"NAT 换地址"这件事发生在数据包已经进了本机之后。实际映射如下(只看 filter 和 nat 就够了,其他不用记):
| 表 ↓ / 链 → | PREROUTING | INPUT | FORWARD | OUTPUT | POSTROUTING |
|---|---|---|---|---|---|
| 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 |
设置链的默认策略 |
基本命令格式:
bash
# 查看规则(推荐加 -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 加载模块)
bash
-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 加载模块)
bash
# 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 | 无法识别的包 | 线路噪音,不知道谁打来的 | 损坏的包、状态不匹配的包 |
这个概念的实战价值:利用状态匹配,你的防火墙规则可以大幅简化:
bash
# 就这两条,覆盖了 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 服务器
bash
#!/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 暴力破解
bash
# 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)
bash
# 将访问本机 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)
bash
# 开启转发
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(最通用)
bash
# 保存当前规则
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6
# 恢复规则
iptables-restore < /etc/iptables/rules.v4
方法二:使用 netfilter-persistent 服务(Debian/Ubuntu)
bash
apt install iptables-persistent
# 安装过程中会提示保存当前规则
# 之后手动更新:
netfilter-persistent save
方法三:使用 iptables-services(RHEL/CentOS)
bash
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 | 信任度 | 默认行为 | 你什么时候用 |
|---|---|---|---|
| drop | 0% | 所有入站丢包,无任何回复 | 极致隐身,外面完全感觉不到这台机器的存在 |
| block | 10% | 入站被拒,返回 ICMP 禁止 | 比 drop 温和,对方至少知道被拒了 |
| public | 20% | 不信任,仅放行你指定的端口 | ☕ 公共 WiFi、咖啡厅、云服务器默认选这个 |
| external | 30% | NAT 模式,开启伪装 | 路由器外网口 |
| dmz | 40% | 仅允许特定入站 | 隔离区的服务器(前后都有防火墙) |
| work | 60% | 放行多数内网服务 | 公司办公网 |
| home | 70% | 放行多数内网服务 | 家里网络 |
| internal | 80% | 放行更多内网服务 | 全是你自己机器的内部网络 |
| trusted | 100% | 全部放行 | 绝对信任(⚠️ 慎用) |
新手只需关注 public :云服务器 99% 的场景用
public就够了。其他 zone 主要面向多网卡或频繁切换网络环境的笔记本用户。
zone 的核心行为由两个要素决定:
-
target:对不匹配任何规则的包的最终处理
default:按 zone 的默认行为ACCEPT:放行DROP:静默丢弃%%REJECT%%:拒绝并通知
-
services/ports/rules:在 target 之前匹配的放行清单
bash
# 查看所有 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/
bash
# 查看所有预定义 service
firewall-cmd --get-services
# 查看某个 service 的定义(本质是 XML 文件)
cat /usr/lib/firewalld/services/ssh.xml
一个典型的 service XML 定义:
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 应用端口为例):
bash
# 创建自定义 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 后生效 |
永久保存 |
三种操作模式:
bash
# 模式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 的两种方式:
bash
firewall-cmd --reload # 重载配置,不中断现有连接(推荐)
firewall-cmd --complete-reload # 完全重载,会短暂中断连接
4.6 Rich Rules(富规则)
当 service 和 port 的粒度不够时,rich rule 提供了更精细的控制。它的语法直观、接近自然语言。
bash
# 格式:
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 语法。
bash
# 语法:
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 服务器从零配置到安全上线
bash
# 第一步:确认当前状态
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
bash
# 办公室(信任网络)
firewall-cmd --change-zone=work
# → 自动开放更多服务
# 咖啡厅(公共网络)
firewall-cmd --change-zone=public
# → 严格限制入站,只保留基本出站
# 查看当前 zone
firewall-cmd --get-active-zones
五、firewalld vs iptables 全面对比
| 维度 | iptables | firewalld |
|---|---|---|
| 配置方式 | 命令行直接操作规则 | 通过 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 方案:
bash
#!/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 等价方案:
bash
#!/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)
bash
#!/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 规则来实现容器网络,它会:
- 在
DOCKER和DOCKER-USER自定义链中插入规则 - 默认绕过
INPUT链的控制(因为 Docker 的转发走FORWARD链)
常见问题: 你明明用 firewalld 禁止了某个端口的外部访问,但 Docker 容器在那个端口上仍然可以被访问。
原因 :Docker 在 PREROUTING 做 DNAT,在 FORWARD 放行,而 firewalld 主要管 INPUT 链。两个机制在不同的网络栈层面运行,互不感知。
解决方案:
bash
# 方案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 调试技巧
排查"为什么这个包被丢弃了":
bash
# 方法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 调试:
bash
# 开启 firewalld 调试日志
firewall-cmd --set-log-denied=all
# 查看被拒绝的包
dmesg | grep "FINAL_REJECT"
# 查看 firewalld 底层生成的 iptables 规则
iptables -L -n -v
iptables -t nat -L -n -v
查看规则计数器(判断规则是否被命中):
bash
# 重置计数器后观察
iptables -Z
# 一段时间后:
iptables -L -n -v # pkts 和 bytes 列显示每条规则匹配的流量
7.2 fail2ban 联动
fail2ban 是一个经典的入侵防御工具------它监控日志(如 SSH 登录失败),然后动态调用防火墙封禁攻击者 IP。
bash
# 安装
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:加了规则为什么还是不通?
排查清单:
-
规则顺序 :iptables 规则是从上到下匹配,命中即停止。检查是不是前面的规则匹配了
bashiptables -L -n -v --line-numbers -
链的默认策略 :
iptables -L -n看链的policy是不是DROPbashiptables -P INPUT ACCEPT # 临时放行所有,确认是防火墙问题 -
firewalld 的 zone 不对:网卡可能绑定到了其他 zone
bashfirewall-cmd --get-active-zones -
firewalld runtime vs permanent:
bash# runtime 有但 permanent 没有 → 重启后丢失 firewall-cmd --list-services # 当前生效 firewall-cmd --permanent --list-services # 持久化配置 -
Docker 绕过了防火墙 :见 [6.3 节](#6.3 节)
问题 2:firewalld 和 iptables 服务冲突
bash
# 症状:两个服务同时 running,规则混乱
systemctl status firewalld
systemctl status iptables
# 解决:禁用 iptables 服务,统一用 firewalld
systemctl stop iptables
systemctl disable iptables
systemctl restart firewalld
问题 3:iptables 规则重启后丢失
bash
# 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 时应用了新规则。
bash
# 修复:通过控制台(非 SSH)登录
firewall-cmd --add-service=ssh --permanent
firewall-cmd --reload
# 预防:reload 前先在 runtime 测试
firewall-cmd --add-service=ssh
# 确认 OK 后再加 --permanent
九、总结与最佳实践
核心理念
- 最小权限原则:默认 DROP 一切,只开放必需的端口和服务
- 白名单优于黑名单:指定"谁可以"而不是"谁不可以"
- 分层防御:防火墙只是安全链中的一环,配合 SSH Key、SELinux、fail2ban、定期审计使用
操作 Checklist
部署新服务器时,按以下顺序配置防火墙:
- 确认防火墙状态:
firewall-cmd --state或systemctl status firewalld - 确认当前规则:
firewall-cmd --list-all或iptables -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 等主流发行版。