✏️ 编辑

防火墙与TCP/IP网络基础学习笔记

创建于 2026-07-08 10:20:06 · 更新于 2026-07-08 10:34:27

防火墙与 TCP/IP 网络基础学习笔记

学习日期:2026-07-08
背景:个人运维平台(zhoujiayi.xyz)无法访问,排查发现是 firewalld 启动后拦截了 80/443 端口
完整记录这次学习过程中的每个问题和讲解


一、故障排查:网站无法访问

现象

用户反馈「个人运维平台无法访问此网站」,域名是 zhoujiayi.xyz。

排查命令与解说

1. 查看端口监听:ss -tlnp

命令:

Bash
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. 本地测试服务是否正常

Bash
# 测试 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

Bash
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

Bash
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,没有 httphttpsports 也是空的 → 80 和 443 端口没有被放行

5. 查看 iptables 兼容层(踩坑演示)

Bash
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

Bash
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 行,没有 httphttps

规则解释:

规则 含义
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 连通性

Bash
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)是通的:

Bash
ping -c 2 43.134.84.11
# 64 bytes from 43.134.84.11: icmp_seq=1 ttl=63 time=1.11ms ✅

8. 确认云平台

Bash
curl -s http://metadata.tencentyun.com/latest/meta-data/

返回了 app-idinstance-idpublic-ipv4 等信息,确认是腾讯云服务器。

注意:当时老师一开始错误地认为是腾讯云安全组的问题,实际上是因为没先查 systemctl status firewalld 看到 firewalld 刚刚启动。正确顺序应该是先查本地防火墙,最后再查云平台安全组。


修复

Bash
# 放行 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 规则会丢失!

验证修复:

Bash
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:

Bash
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 查看规则:

Bash
firewall-cmd --zone=public --list-all
firewall-cmd --zone=trusted --list-all

Zone 的用途: 服务器有多个网口时,可以给不同网口分配不同 Zone:

Bash
firewall-cmd --zone=public --change-interface=eth0    # 外网口:严格限制
firewall-cmd --zone=internal --change-interface=eth1  # 内网口:开放更多

问题 3:--add-port=80/tcp 为什么必须加 /tcp

测试结果:

Bash
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

格式规则:

Bash
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 帮你封装好的快捷方式:

Bash
cat /usr/lib/firewalld/services/http.xml
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 抓包看真实过程:

Bash
# 在终端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 字节"

系统配置:

Bash
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-allnft list ruleset 查看真实规则。


第二题(概念理解)

面试官: iptables -L -n 显示 policy ACCEPT,但你的网站还是被拦了。这是为什么?

答: 可能有三个原因:
1. 系统用 nftables 后端iptables 显示的是兼容层,不是真实规则
2. firewalld 在运行 — 规则通过 nftables 管理,需要用 firewall-cmd 查看
3. 云平台安全组 — 腾讯云/阿里云的安全组在虚拟机外部,服务器内部看不到

排查方法:

Bash
systemctl status firewalld    # 先看防火墙是否运行
firewall-cmd --list-all       # 查看真实的放行规则
nft list ruleset              # 查看 nftables 底层规则

第三题(实操命令)

面试官: 要用 firewalld 放行一个自定义端口 8080/tcp,命令怎么写?要求重启后依然生效。

答:

Bash
# 方法一:分开写
firewall-cmd --add-port=8080/tcp --permanent
firewall-cmd --reload

# 方法二:一条命令多个参数
firewall-cmd --add-port=8080/tcp --permanent && firewall-cmd --reload

追问: 如果还要放行 8080/udp 呢?

答:

Bash
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 端口。

确认方法:

Bash
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 不自启动:

Bash
systemctl disable firewalld

七、排查总结

网站无法访问的正确排查顺序

① ss -tlnp                  → 进程是否在监听
② curl localhost / curl IP  → 本地服务是否正常
③ systemctl status firewalld → 🔑 防火墙是否在运行
④ firewall-cmd --list-all   → 查看放行了哪些服务
⑤ nft list ruleset          → 查看 nftables 真实规则
⑥ 以上都正常 → 最后查云平台安全组

关键教训

  1. 不要信任 iptables -L -n — 新系统上它显示的是兼容层,不是真实规则
  2. 调试前先看 systemctl status firewalld — 知道防火墙是否在运行
  3. firewalld 默认只放行 ssh — 装完系统或启动 firewalld 后,记得开放需要的端口
  4. --permanent 单独用不会立即生效 — 需要配合 --reload
  5. 不加 --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 昨天刚刚启动。正确做法是先查本地防火墙状态,这是排查的第一步。