防火墙与TCP/IP网络基础学习笔记
防火墙与 TCP/IP 网络基础学习笔记
学习日期:2026-07-08
背景:个人运维平台(zhoujiayi.xyz)无法访问,排查发现是 firewalld 启动后拦截了 80/443 端口
完整记录这次学习过程中的每个问题和讲解
一、故障排查:网站无法访问
现象
用户反馈「个人运维平台无法访问此网站」,域名是 zhoujiayi.xyz。
排查命令与解说
1. 查看端口监听:ss -tlnp
命令:
ss -tlnp
命令拆解:
| 参数 | 含义 |
|---|---|
ss |
Socket Statistics,查看套接字状态,替代旧的 netstat |
-t |
TCP 协议 |
-l |
只显示 LISTEN(监听)状态的端口 |
-n |
不解析服务名,直接显示端口号 |
-p |
显示使用该端口的进程信息 |
执行结果:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 127.0.0.1:5001 0.0.0.0:* users:(("python3",pid=2594376,fd=4))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=877734,fd=7))
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1031,fd=3))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=877734,fd=10))
LISTEN 0 4096 0.0.0.0:3306 0.0.0.0:* users:(("docker-proxy",pid=2410519,fd=8))
逐行解读:
| 端口 | 进程 | 说明 |
|---|---|---|
| 127.0.0.1:5001 | python3 (Flask) | 只监听本地,Nginx 代理访问 |
| 0.0.0.0:80 | nginx | HTTP,对外公开 |
| 0.0.0.0:22 | sshd | SSH 远程登录 |
| 0.0.0.0:443 | nginx | HTTPS,对外公开 |
| 0.0.0.0:3306 | docker-proxy | MySQL 容器端口映射 |
0.0.0.0表示绑定所有网络接口(公网可访问),127.0.0.1只绑定本地环回(只有本机可访问)。
结论: Flask、Nginx、SSH、MySQL 都在正常监听 ✅
2. 本地测试服务是否正常
# 测试 Flask 本身
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5001/
# 返回 200
# 测试 Nginx HTTP(应该重定向到 HTTPS)
curl -I http://localhost/
# 返回 301
# 测试 Nginx HTTPS
curl -sk -o /dev/null -w "%{http_code}" https://localhost/
# 返回 200
命令拆解:
| 参数 | 含义 |
|------|------|
| -s | Silent,静默模式,不显示进度 |
| -o /dev/null | 丢弃响应体,只看状态码 |
| -w "%{http_code}" | 输出 HTTP 状态码 |
| -I | HEAD 请求,只返回响应头 |
| -k | 允许自签名证书(insecure) |
结论: 本地访问一切正常 ✅
3. 检查防火墙状态:systemctl status firewalld
systemctl status firewalld
命令拆解:
| 参数 | 含义 |
|------|------|
| systemctl | Systemd 服务管理器,管理 Linux 系统服务 |
| status | 查看服务状态 |
| firewalld | 防火墙服务名称 |
执行结果:
● firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; preset: disabled)
Active: active (running) since Tue 2026-07-07 18:44:55 CST; 19h ago
Docs: man:firewalld(1)
Main PID: 2966394 (firewalld)
Tasks: 2 (limit: 4341)
Memory: 20.7M (peak: 39.3M)
CPU: 928ms
CGroup: /system.slice/firewalld.service
└─2966394 /usr/bin/python3 /usr/sbin/firewalld --nofork --nopid
逐行解读:
| 行 | 含义 |
|---|---|
● firewalld.service |
服务名称,● 表示正在运行(白色=未运行) |
Loaded: loaded |
服务配置文件已加载 |
(disabled; preset: disabled) |
⚠️ 服务器重启后不会自启动 |
Active: active (running) |
✅ 当前正在运行 |
since ... 18:44:55 |
🔑 关键线索:昨天18:44才启动的! |
Main PID: 2966394 |
进程ID |
Memory: 20.7M |
占用内存 |
CGroup |
Control Group,systemd 的资源隔离 |
└─2966394 /usr/bin/python3 /usr/sbin/firewalld |
实际执行的命令 |
这里有个重要问题: 为什么网站原来可以访问?因为这个 firewalld 是昨天下午18:44才启动的!之前防火墙没运行,所以 80/443 畅通。启动之后它的默认规则只放行了 ssh,80/443 就被拦截了。
4. 查看防火墙规则:firewall-cmd --list-all
firewall-cmd --list-all
命令拆解:
| 参数 | 含义 |
|------|------|
| firewall-cmd | firewalld 的命令行管理工具 |
| --list-all | 列出当前 zone 的所有配置 |
完整输出和逐行解读:
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: dhcpv6-client mdns ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
逐行讲解:
| 行 | 含义 |
|---|---|
public (active) |
当前 zone 是 public,且正在使用中 |
target: default |
没有规则匹配时走默认行为(通常 reject) |
icmp-block-inversion: no |
不反转 ICMP 拦截 |
interfaces: eth0 |
此 zone 应用在 eth0 网卡上 |
sources:(空) |
没有指定来源 IP 限制 |
services: dhcpv6-client mdns ssh |
🔑 放行的服务列表:IPv6自动配置 / 多播DNS / SSH |
ports:(空) |
没有额外开放端口 |
protocols:(空) |
没有放行特定协议 |
forward: yes |
允许转发 |
masquerade: no |
不开启 NAT 伪装 |
forward-ports:(空) |
没有端口转发 |
source-ports:(空) |
没有源端口限制 |
icmp-blocks:(空) |
没有拦截 ICMP |
rich rules:(空) |
没有富规则 |
关键发现:
services里只有dhcpv6-client mdns ssh,没有http和https,ports也是空的 → 80 和 443 端口没有被放行 ❌
5. 查看 iptables 兼容层(踩坑演示)
iptables -L -n
命令拆解:
| 参数 | 含义 |
|------|------|
| iptables | 传统的 Linux 防火墙工具 |
| -L | List,列出规则 |
| -n | Numeric,不解析名称,直接显示数字 |
输出(节选):
Chain INPUT (policy ACCEPT)
target prot opt source destination
YJ-FIREWALL-INPUT all -- 0.0.0.0/0 0.0.0.0/0
陷阱: 看到 policy ACCEPT 很容易误以为防火墙没拦截。但新版系统(CentOS 8+ / OpenCloudOS)的 firewalld 默认用 nftables 后端,iptables 命令只是一个兼容层,看不到真实规则。
6. 查看 nftables 真实规则:nft list ruleset
nft list ruleset
命令拆解:
| 参数 | 含义 |
|------|------|
| nft | nftables 的命令行工具 |
| list ruleset | 列出所有表和链的全部规则 |
输出节选+讲解:
table inet firewalld {
table inet firewalld → 定义一个名为 firewalld 的表,inet 表示同时处理 IPv4 和 IPv6。
chain filter_IN_public_allow {
tcp dport 22 ct state { new, untracked } accept
ip daddr 224.0.0.251 udp dport 5353 ct state { new, untracked } accept
ip6 daddr ff02::fb udp dport 5353 ct state { new, untracked } accept
ip6 daddr fe80::/64 udp dport 546 ct state { new, untracked } accept
tcp dport 80 ct state { new, untracked } accept
tcp dport 443 ct state { new, untracked } accept
}
说明: 这是修复后的规则。修复前只有前 4 行,没有 http 和 https。
规则解释:
| 规则 | 含义 |
|---|---|
tcp dport 22 |
TCP 目标端口 22(SSH) |
ct state { new, untracked } |
新连接或不需要追踪的连接 |
accept |
允许通过 |
ip daddr 224.0.0.251 |
目标 IP 是组播地址(mDNS) |
udp dport 5353 |
UDP 目标端口 5353 |
tcp dport 80 |
TCP 目标端口 80(HTTP)— 修复后新增 |
tcp dport 443 |
TCP 目标端口 443(HTTPS)— 修复后新增 |
chain filter_IN_public {
jump filter_INPUT_POLICIES_pre
jump filter_IN_public_pre
jump filter_IN_public_log
jump filter_IN_public_deny
jump filter_IN_public_allow
jump filter_IN_public_post
jump filter_INPUT_POLICIES_post
meta l4proto { icmp, ipv6-icmp } accept
reject with icmpx admin-prohibited
}
规则匹配流程(从上到下,匹配第一条就停止):
请求进来(比如浏览器访问 443 端口)
→ jump filter_IN_public_pre(预处理)
→ jump filter_IN_public_log(日志)
→ jump filter_IN_public_deny(黑名单检查)
→ jump filter_IN_public_allow(白名单检查)
→ dport 22? → 不是,下一条
→ dport 5353? → 不是,下一条
→ dport 80? → 不是,下一条
→ dport 443? → ✅ 匹配!accept 放行 ← 修复后
→ jump filter_IN_public_post(后处理)
→ ICMP 规则(ping)
→ ⭐ reject with icmpx admin-prohibited(兜底:全部拒绝)
修复之前: 没有 80/443 规则 → 经过 allow 链没有匹配 → 回到主链 → 跳过前面的 → 落到最后的
reject→ ❌ 被拒
总结:iptables vs nftables 的关系
| 工具 | 本质 | 适用场景 |
|---|---|---|
iptables |
旧版防火墙工具 | CentOS 7 及之前 |
nftables |
新版防火墙引擎 | CentOS 8+ / OpenCloudOS |
firewall-cmd |
firewalld 管理工具 | 统一管理,不管后端是啥 |
nft |
nftables 直接操作工具 | 查看/调试底层规则 |
🔑 关键教训: 新系统上
iptables -L -n看到的是兼容层,并非真实规则。查防火墙一定先用systemctl status firewalld,再用firewall-cmd --list-all。
7. 测试公网 IP 连通性
timeout 5 bash -c 'echo > /dev/tcp/43.134.84.11/80' 2>&1 && echo "可达" || echo "不可达"
命令拆解:
- timeout 5 — 5 秒超时,防止命令卡住
- bash -c '...' — 用 Bash 执行命令
- echo > /dev/tcp/IP/端口 — Bash 内置的 TCP 连接测试(不是普通文件!)
- && — 前面命令执行成功(连接建立)才执行后面的
- || — 前面命令执行失败才执行后面的
- 2>&1 — 将错误输出重定向到标准输出
执行结果:
bash: connect: No route to host
端口80不可达
从服务器内部连自己的公网 IP 的 80 端口都提示 "No route to host" — 说明流量在到达网卡之前就被拦截了,这是防火墙(不是服务本身)的问题。
作为对比,ping(ICMP)是通的:
ping -c 2 43.134.84.11
# 64 bytes from 43.134.84.11: icmp_seq=1 ttl=63 time=1.11ms ✅
8. 确认云平台
curl -s http://metadata.tencentyun.com/latest/meta-data/
返回了 app-id、instance-id、public-ipv4 等信息,确认是腾讯云服务器。
注意:当时老师一开始错误地认为是腾讯云安全组的问题,实际上是因为没先查
systemctl status firewalld看到 firewalld 刚刚启动。正确顺序应该是先查本地防火墙,最后再查云平台安全组。
修复
# 放行 HTTP 和 HTTPS(永久生效)
firewall-cmd --add-service=http --add-service=https --permanent
# 重载配置使永久规则生效
firewall-cmd --reload
命令拆解:
第一行:
| 参数 | 含义 |
|------|------|
| --add-service=http | 放行 HTTP 服务(等价于 --add-port=80/tcp) |
| --add-service=https | 放行 HTTPS 服务(等价于 --add-port=443/tcp) |
| --permanent | 写入磁盘配置文件,reload/重启后永久保留 |
第二行:
| 参数 | 含义 |
|------|------|
| --reload | 重新加载磁盘配置到运行时,使 --permanent 规则生效 |
关于 --permanent 和 --reload 的完整说明:
firewalld 的规则分两层:
| 层 | 位置 | 特点 |
|---|---|---|
| 运行时(Runtime) | 内存 | 立即生效,但 reload/重启后丢失 |
| 永久(Permanent) | 磁盘文件 | 重启保留,但需要 reload 才生效 |
四种操作对比:
| 操作 | 立即生效? | 重启保留? |
|---|---|---|
--add-service=http |
✅ | ❌ |
--add-service=http --permanent |
❌ | ✅ |
--permanent + 单独 --reload |
✅ | ✅ |
不加 --permanent 然后 --reload |
规则会丢失! | — |
验证修复:
firewall-cmd --list-services
# 输出: dhcpv6-client http https mdns ssh ✅
timeout 5 bash -c 'echo > /dev/tcp/43.134.84.11/80' 2>&1 && echo "可达"
# 端口80 ✅ 可达
timeout 5 bash -c 'echo > /dev/tcp/43.134.84.11/443' 2>&1 && echo "可达"
# 端口443 ✅ 可达
二、防火墙基础知识
问题 1:防火墙和端口是什么关系?
防火墙就像大楼的保安,服务器 IP 是大楼地址,端口是门牌号。
常用端口:
| 端口 | 服务 | 说明 |
|---|---|---|
| 22 | SSH | 管理员远程登录 |
| 80 | HTTP | 网页访问(无加密) |
| 443 | HTTPS | 网页访问(加密) |
| 21 | FTP | 文件传输 |
| 25 | SMTP | 发邮件 |
| 3306 | MySQL | 数据库 |
| 6379 | Redis | 缓存 |
| 5001 | Flask | 自建应用 |
防火墙的职责:
1. 哪些端口对外开放
2. 什么 IP 可以访问
3. 什么协议可以通过
4. 连接状态追踪
问题 2:firewalld 的 Zone(区域)是什么?
Zone 是一套预定义的规则模板,不同场景用不同 Zone。
查看系统所有 Zone:
firewall-cmd --get-zones
block dmz drop external home internal nm-shared public trusted work
常用 Zone 对比:
| Zone | 场景 | 通常开放的服务 |
|---|---|---|
| public(默认) | 公网服务器 | ssh, http, https, dhcpv6-client, mdns |
| trusted | 内网/家庭网络 | 全部放行 |
| drop | 高危环境 | 全部丢弃(无任何回复) |
| block | 受限环境 | 全部拒绝(回复"不可达") |
| internal | 公司内网 | ssh, samba-client, mdns, dhcpv6-client |
| external | 外网网关 | ssh(其余受限) |
| dmz | 隔离区 | ssh(只允许管理) |
| home | 家庭网络 | ssh, samba-client, mdns, dhcpv6-client |
| work | 工作网络 | ssh, dhcpv6-client |
指定 Zone 查看规则:
firewall-cmd --zone=public --list-all
firewall-cmd --zone=trusted --list-all
Zone 的用途: 服务器有多个网口时,可以给不同网口分配不同 Zone:
firewall-cmd --zone=public --change-interface=eth0 # 外网口:严格限制
firewall-cmd --zone=internal --change-interface=eth1 # 内网口:开放更多
问题 3:--add-port=80/tcp 为什么必须加 /tcp?
测试结果:
firewall-cmd --add-port=80
# Error: INVALID_PORT: bad port (most likely missing protocol),
# correct syntax is portid[-portid]/protocol
因为同一个端口号 + 不同协议可以是完全不同的服务:
| 端口 | TCP 用途 | UDP 用途 |
|---|---|---|
| 53 | DNS 区域传送(服务器间同步) | DNS 普通查询(日常上网) |
| 80 | HTTP 网页 | 视频流(极少用) |
| 443 | HTTPS | QUIC/HTTP3 |
格式规则:
firewall-cmd --add-port=80/tcp # ✅ 单个端口+协议
firewall-cmd --add-port=80 # ❌ 不写协议 → 报错
firewall-cmd --add-port=3000-3010/tcp # ✅ 端口范围
firewall-cmd --add-port=53/tcp --add-port=53/udp # ✅ TCP+UDP 同时放行
问题 4:--add-service=http 和 --add-port=80/tcp 有什么区别?
本质一样。--add-service=http 是 firewalld 帮你封装好的快捷方式:
cat /usr/lib/firewalld/services/http.xml
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>WWW (HTTP)</short>
<description>HTTP is the protocol used to serve Web pages...</description>
<port protocol="tcp" port="80"/>
</service>
推荐原则:
- 标准服务用 --add-service=http(更语义化)
- 自建服务用 --add-port=5001/tcp(灵活)
三、TCP 与 UDP 的区别
核心对比
Q:TCP 和 UDP 本质上有什么区别?
用生活类比:
| TCP | UDP | |
|---|---|---|
| 类比 | 📞 打电话 | 📡 对讲机 |
| 连接 | 拨号→对方接起→"喂?"→"能听到吗?" | 按下去直接说,不管对方在不在 |
| 传输过程 | 发一个包→等确认→收到→发下一个 | 直接发,不确认 |
| 丢包处理 | 重发,保证完整 | 丢了就丢了 |
| 顺序保证 | 按发送顺序到达 | 可能先发的后到 |
| 速度 | 慢(每次要确认) | 快(没有确认过程) |
| 头部大小 | 20-60 字节 | 8 字节 |
实际应用:
用 TCP 的场景(必须完整准确):
- HTTP/HTTPS 网页浏览:页面必须完整加载
- SSH:按键不能丢
- 邮件(SMTP):内容不能缺
- 文件下载(FTP):文件损坏一点就没法用
用 UDP 的场景(追求速度,能容忍少量丢包):
- 视频直播:卡一帧无所谓,但等重发就卡死了
- 在线游戏:开枪要即时显示
- DNS 查询:一问一答,没必要建连接
- 语音通话:丢几个字能猜出来,延迟更致命
四、TCP 三次握手
Q:TCP 建立连接时具体怎么握手的?
先用 tcpdump 抓包看真实过程:
# 在终端1运行:抓取三次握手
timeout 3 tcpdump -i lo -c 3 -nn -tt 'tcp port 443 and (tcp[tcpflags] & (tcp-syn|tcp-ack) != 0)'
# 在终端2运行:发起 HTTPS 请求
curl -sk https://127.0.0.1/
tcpdump 命令拆解:
| 参数 | 含义 |
|------|------|
| tcpdump | 网络抓包工具 |
| -i lo | 监听 lo(本地环回)网卡 |
| -c 3 | 只抓 3 个包 |
| -nn | 不解析主机名和端口名 |
| -tt | 显示完整时间戳 |
| 'tcp port 443 ...' | 过滤条件:只抓 443 端口且包含 SYN/ACK 标志的 TCP 包 |
抓到的三个包:
① 第一次握手(SYN):
127.0.0.1.58046 > 127.0.0.1.443: Flags [S], seq 2669367652
| 字段 | 含义 |
|---|---|
127.0.0.1.58046 |
源 IP.源端口(客户端随机选的临时端口) |
> |
流向标记:从源到目标 |
127.0.0.1.443 |
目标 IP.目标端口 |
Flags [S] |
SYN 标志位(Synchronize),请求建立连接 |
seq 2669367652 |
序列号,用于后续数据排序和确认 |
客户端说:"你好,我想连接你的 443 端口,这是我的序列号 2669367652"
② 第二次握手(SYN-ACK):
127.0.0.1.443 > 127.0.0.1.58046: Flags [S.], seq 2641890000, ack 2669367653
| 字段 | 含义 |
|---|---|
Flags [S.] |
SYN + ACK 标志 |
seq 2641890000 |
服务器自己的序列号 |
ack 2669367653 |
确认号 = 客户端的 seq + 1 |
服务器说:"收到你的连接请求!这是我的序列号 2641890000,我收到了你的第 2669367652 号包(所以我期望你下一个发 2669367653)"
③ 第三次握手(ACK):
127.0.0.1.58044 > 127.0.0.1.443: Flags [.], ack 1, win 11, length 0
| 字段 | 含义 |
|---|---|
Flags [.] |
纯 ACK 标志,不带数据 |
ack 1 |
确认号(简化显示,实际是服务器的 seq+1) |
length 0 |
这条包不携带数据,纯粹是确认 |
客户端说:"好的,我确认收到了你的序列号,连接建立完毕!"
完整流程图:
客户端 服务器
│ │
│ ── ① SYN, seq=2669367652 ───────→ │ 客户端:我要连443!
│ │
│ ← ② SYN-ACK, seq=2641890000 ───── │ 服务器:收到,这是我的seq
│ ack=2669367653 │ 确认收到你的seq
│ │
│ ── ③ ACK, ack=2641890001 ───────→ │ 客户端:好,确认收到!
│ │
│ ←──────── 开始传数据 ────────────→ │ ✅ 连接建立成功
Q:为什么非要三次?两次不行吗?
假设只有两次握手:
场景:客户端发 SYN 建立连接,这个 SYN 在网络中延迟了
时间线:
① 客户端发 SYN ──→(网络延迟中)
② 客户端等太久,以为丢了 → 重发 SYN → 服务器回 SYN-ACK → 连接建立
③ 传输数据 → 完成 → 关闭连接
④ ❗ 延迟的 SYN 终于到达服务器
⑤ 服务器以为新连接 → 回 SYN-ACK → 资源分配 → 傻等客户端发数据
⑥ 客户端根本不知道这个"连接"存在 → 浪费服务器资源
三次握手解决了这个问题:
第三次 ACK 是客户端对服务器 SYN-ACK 的确认
如果客户端根本没想建立连接,就不会回 ACK
服务器收不到 ACK → 知道这是无效连接 → 不浪费资源
一句话:三次握手保证了双方都有收发能力,避免了"空连接"浪费资源。
五、TCP 滑动窗口
Q:TCP 怎么保证传输效率的?什么是滑动窗口?
没有窗口的传输(停等协议):
客户端 → 发 包1 → 服务器
客户端 ← ACK1 ← 服务器
客户端 → 发 包2 → 服务器
客户端 ← ACK2 ← 服务器
... 每发一个包就要等确认,效率极低
滑动窗口的传输:
窗口大小=4(一次可以连续发4个包,不用等)
客户端 → 发包1 ─┐
客户端 → 发包2 ├─ 一口气发出去
客户端 → 发包3 │
客户端 → 发包4 ─┘
← ACK 包1 ✅
客户端 → 发包5 ← 收到ACK,窗口往前滑
← ACK 包2 ✅
客户端 → 发包6 ← 继续发
像一扇窗在数据流上滑动:
┌───────────┐
│1│2│3│4│5│6│7│8│...
└───────────┘
↑收到ACK1→窗口右移
┌───────────┐
│2│3│4│5│6│7│...
└───────────┘
窗口大小是动态协商的:
从抓包可以看到,建立连接时双方就协商了窗口大小:
第一次握手: win=43690 ← 客户端说"我一次能收 43690 字节"
第二次握手: win=65483 ← 服务器说"我一次能收 65483 字节"
系统配置:
sysctl net.ipv4.tcp_wmem
# 4096 65536 134217728
# 最小 默认 最大(128MB)
sysctl net.ipv4.tcp_rmem
# 4096 87380 134217728
| 值 | 含义 |
|---|---|
| 4096(最小) | 每个 TCP 连接至少分配 4KB 缓冲区 |
| 65536/87380(默认) | 新建连接时的初始窗口大小 |
| 134217728(最大) | 网络通畅时最多能涨到 128MB |
窗口大小动态调整:
- 网络通畅 + 不丢包 → 窗口增大(一次发更多包,更快)
- 出现丢包 → 窗口缩小(减少并发,降低丢包率)
这就是 TCP 能同时适应光纤(高带宽)和卫星链路(高延迟)的关键机制。
六、面试模拟题
以下是在学习过程中老师模拟面试官出的题目:
第一题(基础排查)
面试官: 服务器上 ss -tlnp 显示 Nginx 在监听 80 和 443 端口,但从外网访问 http://你的IP 就是连不上,你会按什么顺序排查?用哪些命令?
答题思路(标准流程):
第一步:ss -tlnp → 确认端口在监听 ✅
第二步:curl localhost → 确认服务本地正常 ✅
第三步:systemctl status firewalld → 确认防火墙是否在运行 🔑
第四步:firewall-cmd --list-all → 查看放行了哪些服务
第五步:firewall-cmd --add-service=http --add-service=https → 修复
第六步:如果还不行,检查云平台安全组
追问: 看到 iptables -L -n 显示 policy ACCEPT,能确认防火墙没拦截吗?
答: 不能。新版系统(CentOS 8+ / OpenCloudOS)用 nftables 后端,iptables 只是兼容层,看不到完整规则。要用 firewall-cmd --list-all 或 nft list ruleset 查看真实规则。
第二题(概念理解)
面试官: iptables -L -n 显示 policy ACCEPT,但你的网站还是被拦了。这是为什么?
答: 可能有三个原因:
1. 系统用 nftables 后端 — iptables 显示的是兼容层,不是真实规则
2. firewalld 在运行 — 规则通过 nftables 管理,需要用 firewall-cmd 查看
3. 云平台安全组 — 腾讯云/阿里云的安全组在虚拟机外部,服务器内部看不到
排查方法:
systemctl status firewalld # 先看防火墙是否运行
firewall-cmd --list-all # 查看真实的放行规则
nft list ruleset # 查看 nftables 底层规则
第三题(实操命令)
面试官: 要用 firewalld 放行一个自定义端口 8080/tcp,命令怎么写?要求重启后依然生效。
答:
# 方法一:分开写
firewall-cmd --add-port=8080/tcp --permanent
firewall-cmd --reload
# 方法二:一条命令多个参数
firewall-cmd --add-port=8080/tcp --permanent && firewall-cmd --reload
追问: 如果还要放行 8080/udp 呢?
答:
firewall-cmd --add-port=8080/tcp --add-port=8080/udp --permanent && firewall-cmd --reload
可以一条命令加多个 --add-port 参数。
第四题(理解深度)
面试官: 为什么 firewalld 要求端口必须带协议写成 80/tcp?TCP 和 UDP 在同一个端口号上有什么区别?
答: 因为端口号只是传输层的一个编号,TCP 和 UDP 是两种完全不同的协议,同一个端口号可以承载不同的服务。例如:
- 53/tcp → DNS 区域传送(服务器间同步大量数据)
- 53/udp → DNS 普通查询(日常域名解析)
如果用 --add-port=53 不写协议,防火墙不知道你要放行 TCP 还是 UDP。
第五题(故障排查)
面试官: 用户说"昨天网站还能访问,今天就不行了"。你登录服务器发现 systemctl status firewalld 显示服务是今天凌晨才启动的。可能是什么原因?怎么确认?
答: 基本可以确定是 firewalld 启动后默认规则拦截了 Web 端口。
确认方法:
firewall-cmd --list-all
# 如果 services 里没有 http/https → 就是这个问题
# 验证:临时放行测试
firewall-cmd --add-service=http --add-service=https
# 修复:永久放行
firewall-cmd --add-service=http --add-service=https --permanent
firewall-cmd --reload
根本原因分析: firewalld 安装后默认配置只放行 SSH。如果系统重启或服务启动,防火墙规则立即生效。对应到这次故障,就是 firewalld 服务启动后,以前能进来的 80/443 流量被拦截了。如果希望避免类似问题,可以设置 firewalld 不自启动:
systemctl disable firewalld
七、排查总结
网站无法访问的正确排查顺序
① ss -tlnp → 进程是否在监听
② curl localhost / curl IP → 本地服务是否正常
③ systemctl status firewalld → 🔑 防火墙是否在运行
④ firewall-cmd --list-all → 查看放行了哪些服务
⑤ nft list ruleset → 查看 nftables 真实规则
⑥ 以上都正常 → 最后查云平台安全组
关键教训
- 不要信任
iptables -L -n— 新系统上它显示的是兼容层,不是真实规则 - 调试前先看
systemctl status firewalld— 知道防火墙是否在运行 - firewalld 默认只放行 ssh — 装完系统或启动 firewalld 后,记得开放需要的端口
--permanent单独用不会立即生效 — 需要配合--reload- 不加
--permanent直接生效 — 但 reload/重启后会丢失
常用命令速查
| 命令 | 用途 |
|---|---|
ss -tlnp |
查看 TCP 监听端口和进程 |
systemctl status firewalld |
查看防火墙运行状态 |
firewall-cmd --list-all |
查看当前 zone 全部配置 |
firewall-cmd --add-service=http --permanent |
永久放行 HTTP |
firewall-cmd --add-port=8080/tcp --permanent |
永久放行自定义端口 |
firewall-cmd --reload |
重载配置 |
firewall-cmd --get-zones |
列出所有 zone |
nft list ruleset |
查看 nftables 真实规则 |
curl -s -o /dev/null -w "%{http_code}" http://... |
只检查 HTTP 状态码 |
timeout 3 bash -c 'echo > /dev/tcp/IP/PORT' |
测试 TCP 端口连通性 |
tcpdump -i lo -c 3 -nn 'tcp port 443' |
抓 TCP 包 |
sysctl net.ipv4.tcp_wmem |
查看 TCP 窗口大小配置 |
排查时的错误判断
老师当时的错误: 看到 iptables -L -n 显示 policy ACCEPT,同时发现服务器是腾讯云实例,从本机连公网 IP 的 80/443 又连不上,就错误地推断是腾讯云安全组的问题。实际上是因为没先跑 systemctl status firewalld,没发现 firewalld 昨天刚刚启动。正确做法是先查本地防火墙状态,这是排查的第一步。