端口在监听外面却连不上:一次 Linux 防火墙 iptables 与 firewalld 排查复盘

服务监听 8080,本机 curl 通,外部 telnet 却超时连不上,ss 明明显示在 0.0.0.0 上听着。排查梳理:监听地址 0.0.0.0 与 127.0.0.1 的天壤之别、refused 与 timeout 指向不同病根、firewalld 的 zone 与 runtime/permanent 两套配置、iptables 规则从上到下匹配即停、规则不持久化重启就丢、云服务器还有安全组这第二层防火墙,以及一套防火墙排查纪律。

2024 年,我在一台服务器上部署了一个新服务,监听 8080 端口。部署完我在这台机器上 curl localhost:8080,响应正常,服务本身没毛病。可换到另一台机器上去连它,telnet 过去就是一个干等到超时——外面死活连不进来。我第一反应是服务没监听对,回去一查,ss -lntp 明明白白显示 8080 端口在 0.0.0.0 上听着,任何地址都该能连。服务在听、本机能连、外面连不上——这三件事凑在一起,把我整懵了:一个端口,它到底是"开着"还是"关着"?这件事逼着我把 Linux 的防火墙、iptables、firewalld 这一整套彻底理清了。本文复盘这次实战。

问题背景

环境:CentOS 7,部署一个服务监听 8080
事故现象:
- 本机 curl localhost:8080 正常
- 别的机器 telnet 这台机的 8080,超时连不上
- ss 看端口确实在 0.0.0.0:8080 监听着

现场排查:
# 1. 确认服务在监听,而且监听地址是对的
$ ss -lntp | grep 8080
LISTEN 0 128 0.0.0.0:8080 ...   # ★ 0.0.0.0,所有地址都该能连

# 2. 本机连本机 —— 通
$ curl -s localhost:8080/health
ok

# 3. 从另一台机器连 —— 不通
[B 机]$ telnet 10.0.1.10 8080
Trying 10.0.1.10...
(长时间卡住,最后 timed out)   # ★ 超时,不是 refused

# 4. ★ 看防火墙 —— CentOS 7 默认是 firewalld
$ systemctl status firewalld
   Active: active (running)        # ★ firewalld 开着

# 5. 看 firewalld 放行了哪些端口
$ firewall-cmd --list-all
  services: ssh dhcpv6-client
  ports:                           # ★ ports 是空的!8080 没放行

根因(后来想清楚的):
1. 服务确实在 0.0.0.0:8080 正常监听,服务本身没问题。
2. ★ 本机 curl localhost 通,是因为走 lo 回环网卡,
   流量根本没经过防火墙对外的那条链。
3. ★ 外部流量进来,要先过【主机防火墙 firewalld】这一关。
   firewalld 默认策略:没明确放行的端口,一律拦掉。
4. 8080 从没被加进放行列表 -> 外部的 SYN 包到了网卡,
   被 firewalld 直接 DROP 掉 -> 对方干等,直到 timeout。
5. ★ 超时(而非 refused),正是"被防火墙静默丢弃"的特征。
端口在监听,不代表防火墙放它进来。

修复 1:先确认服务真在监听、且监听地址对

# === 排查"连不上",第一步永远是:服务到底在听吗 ===
$ ss -lntp | grep 8080
LISTEN 0 128 0.0.0.0:8080 users:(("myapp",pid=8123,fd=6))
# -l 只看 LISTEN  -n 不解析端口名  -t TCP  -p 显示进程
# ★ 看清楚监听地址那一列,它决定了"谁能连":

# === ★ 监听地址的三种情况,含义天差地别 ===
# 0.0.0.0:8080    监听【所有网卡】 -> 本机和外部都能连
# 127.0.0.1:8080  ★ 只监听【回环】 -> 只有本机能连,外部【永远连不上】
# 10.0.1.10:8080  只监听这一个具体 IP
# ★ 一个超高频的坑:服务配置写成 127.0.0.1,
#   于是本机 curl 通、外部死活连不上 —— 这跟防火墙无关,
#   是服务【根本没在对外的网卡上监听】。

# === 这次先排除了"监听地址"嫌疑:是 0.0.0.0,没问题 ===
# 如果你的是 127.0.0.1,改服务配置(bind / listen 地址)
# 成 0.0.0.0,而不是去动防火墙。

# === ★ 分清两种"连不上",它们指向完全不同的方向 ===
# Connection refused(立刻被拒)
#   -> 包到了机器,但那个端口【没有进程在监听】,
#      内核直接回了个 RST。或者防火墙用 REJECT 主动拒绝。
# Connection timed out(干等到超时)
#   -> 包【石沉大海】,没有任何回应。
#      典型就是被防火墙 DROP 了,或网络根本不通。
# ★ 这次是 timed out -> 强烈指向"包被防火墙静默丢弃"。

# === 本机能连、外部不能连,先想清楚流量路径 ===
# curl localhost -> 走 lo 回环网卡 -> 通常不经过对外防火墙规则
# 外部连进来 -> 进物理网卡 -> ★ 要穿过防火墙的 INPUT 链
# 所以"本机通、外部不通",防火墙是头号嫌疑。

修复 2:firewalld——CentOS 7 默认的防火墙

# === ★ CentOS 7 / RHEL 7 默认的防火墙是 firewalld ===
$ systemctl status firewalld    # 它在不在跑
$ firewall-cmd --state          # running / not running

# === ★ firewalld 的核心概念:zone(区域)===
# firewalld 把网卡分到不同 zone,每个 zone 有自己的放行规则。
# 常见:public(默认,较严)、trusted(全放行)、internal 等。
$ firewall-cmd --get-default-zone        # 默认 zone
public
$ firewall-cmd --get-active-zones        # 各网卡属于哪个 zone

# === ★ 看某个 zone 到底放行了什么(排查第一命令)===
$ firewall-cmd --list-all
public (active)
  interfaces: eth0
  services: ssh dhcpv6-client            # 放行的"服务"
  ports:                                 # ★ 放行的"端口" —— 空的!
# 这次就卡在这:8080 既不在 services 也不在 ports。

# === 放行一个端口 ===
$ firewall-cmd --add-port=8080/tcp                  # ★ 临时,立刻生效
$ firewall-cmd --permanent --add-port=8080/tcp      # ★ 永久,但不立刻生效
$ firewall-cmd --reload                             # 让 permanent 生效

# === ★ 这是 firewalld 最大的坑:runtime 与 permanent 是两套 ===
# 不加 --permanent  -> 改的是【runtime 运行时】配置:
#   立刻生效,但【重启 firewalld 或重启机器就丢】。
# 加 --permanent    -> 改的是【permanent 永久】配置:
#   写进磁盘、重启也在,但【不会立刻生效】,要 --reload。
# ★ 正确姿势:两条都执行 —— 既立刻生效,又重启不丢:
$ firewall-cmd --add-port=8080/tcp
$ firewall-cmd --permanent --add-port=8080/tcp
$ firewall-cmd --reload
# ★ 验证:--reload 之后,再 --list-all 确认 ports 里有了 8080。

# === 也可以按"服务名"放行(firewalld 预定义了一批)===
$ firewall-cmd --permanent --add-service=http        # 放行 80
$ firewall-cmd --get-services                        # 看支持哪些服务名

# === 限定来源 IP 放行(更安全)===
$ firewall-cmd --permanent --add-rich-rule='rule family=ipv4 \
    source address=10.0.0.0/8 port port=8080 protocol=tcp accept'
# rich rule:只放行 10.0.0.0/8 网段访问 8080,别的拒掉。

修复 3:iptables——firewalld 底层,规则顺序是关键

# === ★ firewalld 只是个"前端",底层真正干活的是 iptables/netfilter ===
# 有些机器没装 firewalld,直接用 iptables;排查必须会看 iptables。

# === 看 iptables 当前所有规则(★ 带计数,排查神器)===
$ iptables -L -n -v
Chain INPUT (policy ACCEPT 0 packets)
 pkts bytes target  prot opt in  source        destination
 1234  98K  ACCEPT  tcp  --  *   0.0.0.0/0     0.0.0.0/0   tcp dpt:22
    5  300  DROP    tcp  --  *   0.0.0.0/0     0.0.0.0/0   tcp dpt:8080
# -n 不做 DNS 反查  -v 显示包/字节计数(看哪条规则被命中)
# ★ pkts 列:这条规则匹配了多少包 —— 在涨,说明它真的在起作用。

# === ★ iptables 的灵魂:规则【从上到下,匹配即停】 ===
# 一个包进来,iptables 从链的第一条规则开始,逐条比对:
#   匹配上了 -> 执行那条规则的动作(ACCEPT/DROP/REJECT),【停止】。
#   没匹配  -> 看下一条。
#   所有规则都没匹配 -> 执行链的默认策略(policy)。
# ★ 致命推论:规则的【顺序】决定一切。
#   如果一条 DROP 8080 排在 ACCEPT 8080 的【前面】,
#   那个 ACCEPT 永远轮不到 —— 包在 DROP 那里就被拦下了。

# === ★ 加规则:-A 追加到末尾,-I 插到最前面 ===
$ iptables -A INPUT -p tcp --dport 8080 -j ACCEPT   # 追加到最后
$ iptables -I INPUT -p tcp --dport 8080 -j ACCEPT   # ★ 插到最前面
# 如果链里已经有一条"兜底 DROP",你必须用 -I 把 ACCEPT
# 插到它前面,用 -A 加在它后面是无效的。

# === 看规则的"行号",精确地删 / 插 ===
$ iptables -L INPUT --line-numbers -n
1   ACCEPT  tcp ... dpt:22
2   DROP    tcp ... dpt:8080
$ iptables -D INPUT 2                  # 按行号删掉第 2 条(那条 DROP)
$ iptables -I INPUT 2 -p tcp --dport 8080 -j ACCEPT  # 在第 2 行插入

# === DROP 与 REJECT 的区别(直接影响对方看到什么)===
# DROP    静默丢弃,不回任何包 -> 对方【超时】(这次的现象)
# REJECT  回一个拒绝包         -> 对方立刻收到 Connection refused
# ★ 反推:对方说"超时",查 DROP 规则 / 安全组;
#         对方说"refused",查 REJECT 规则 / 端口没监听。

# === 看链的默认策略 ===
$ iptables -L INPUT -n | head -1
Chain INPUT (policy DROP)              # ★ 默认策略若是 DROP,
# 那么"没被任何 ACCEPT 规则放行"的端口,统统连不上。

修复 4:规则没保存——重启之后全没了

# === ★ 一个让人崩溃的坑:iptables 规则【默认不持久】 ===
# 你用 iptables -A 加的规则,只存在【内存】里。
# 机器一重启,内存清空 —— 所有手动加的规则【全部消失】,
# 防火墙打回原形。
# ★ 典型事故:"昨天明明放行了,今天重启完又连不上了。"

# === iptables 规则的保存与恢复 ===
$ iptables-save > /etc/sysconfig/iptables     # 把当前规则存盘
$ iptables-restore < /etc/sysconfig/iptables  # 从文件恢复
# CentOS 6 时代:service iptables save
# CentOS 7+ 若用 iptables-services 包:
$ systemctl enable iptables
$ service iptables save

# === ★ firewalld 没有这个坑,但前提是你用对了 --permanent ===
# firewalld 的 permanent 配置本来就写在磁盘上、重启不丢。
# 所以这个坑的本质,还是上一节说的:
#   只 --add-port 不加 --permanent -> 重启就丢。
# ★ 想确认 permanent 里到底有什么:
$ firewall-cmd --permanent --list-all
# 对比 firewall-cmd --list-all(runtime)——
# 两者不一致,就说明 runtime 改了但没落盘,重启会变。

# === ★ 排查"重启后防火墙变了",就比这两份配置 ===
# runtime(当前生效) vs permanent(重启后会加载的)
# 不一致 = 有人改了 runtime 没存 permanent,或反之。

# === 一条纪律:改防火墙,runtime 和 permanent 同时改 ===
# firewalld:--add-port 和 --permanent --add-port 都执行 + --reload
# iptables :iptables -A 之后,立刻 iptables-save 落盘

修复 5:两层防火墙——主机防火墙之外,还有云安全组

# === ★ 云服务器上,流量要穿过【两层】防火墙,不是一层 ===
# 第 1 层:云平台的【安全组 / 网络 ACL】——
#          在你的机器【之外】,由云控制台管理。
# 第 2 层:机器【自己】的防火墙 —— firewalld / iptables。
# ★ 一个端口要能从公网连上,这【两层都必须放行】。
#   任何一层没放,结果都是连不上。

# === 怎么判断卡在哪一层 ===
# 在【同一个内网】的另一台机器上测:
$ telnet 10.0.1.10 8080
#   内网能连通 -> 主机防火墙(第 2 层)放行了,
#                 那公网连不上就是【安全组】(第 1 层)的事。
#   内网也连不上 -> 主机防火墙(第 2 层)就有问题。
# ★ 这一步能快速把问题切到某一层,别在两层之间来回猜。

# === 第 1 层:云安全组,怎么查 ===
# 阿里云 / 腾讯云 / AWS 都在【控制台】里配安全组:
# - 看入方向(Inbound)规则里,有没有放行 8080
# - 注意来源:是放行了 0.0.0.0/0,还是只放行了某网段
# - 安全组也分"入站"和"出站",通常入站是重点
# ★ 安全组在机器外面,机器上 ss/iptables 任何命令都看不到它,
#   它是排查时最容易被遗忘的一层。

# === 用工具确认"包到底有没有到达机器" ===
# 在目标机上抓包,看外部的 SYN 有没有进来:
$ tcpdump -i any -nn 'tcp port 8080 and tcp[tcpflags] & tcp-syn != 0'
# 对方在连,你这边:
#   抓到 SYN  -> 包到了机器 -> 卡在【主机防火墙】或服务
#   抓不到 SYN -> 包根本没到机器 -> 卡在【安全组】或网络路由

# === 排查顺序:由外到内,逐层确认 ===
# ① 云安全组放行了吗(控制台)
# ② 主机防火墙放行了吗(firewall-cmd --list-all)
# ③ 服务在对外网卡上监听吗(ss -lntp,不是 127.0.0.1)
# ④ 服务本身健康吗(本机 curl)

修复 6:防火墙排查纪律

# === 这次事故暴露的认知盲区,定几条纪律 ===

# === 1. ★ "本机能连、外部连不上",先查防火墙 ===
# 本机走回环不过对外规则,外部进来要穿 INPUT 链。
# 这个症状,防火墙是头号嫌疑。

# === 2. 先分清 refused 还是 timeout ===
# refused -> 端口没监听,或 REJECT 规则
# timeout -> 被 DROP,或安全组拦,或网络不通

# === 3. ★ 服务监听地址要看清:0.0.0.0 还是 127.0.0.1 ===
$ ss -lntp | grep 端口
# 127.0.0.1 的话,外部永远连不上 —— 这不是防火墙问题。

# === 4. firewalld:runtime 和 permanent 要一起改 ===
$ firewall-cmd --add-port=端口/tcp
$ firewall-cmd --permanent --add-port=端口/tcp
$ firewall-cmd --reload
# 只改一个,要么重启丢,要么不立刻生效。

# === 5. iptables:规则顺序决定一切,看清是不是被前面的 DROP 截了 ===
$ iptables -L -n -v --line-numbers
# 用 -v 看 pkts 计数,哪条规则在被命中一目了然。

# === 6. ★ 云服务器别忘了安全组这一层 ===
# 内网能连、公网不能连 -> 八成是安全组没放行。
# 安全组在机器外,本机任何命令都看不到它。

# === 7. 排查"端口连不上"的命令链 ===
$ ss -lntp | grep 端口         # ① 服务在监听吗、地址对吗
$ curl localhost:端口          # ② 服务本身健康吗
$ firewall-cmd --list-all      # ③ 主机防火墙放行了吗
$ iptables -L -n -v            # ④ 底层规则、顺序、命中计数
$ tcpdump -nn tcp port 端口    # ⑤ 外部的包到底到没到机器
# 再加一步:控制台看云安全组。按这个顺序,基本能定位。

命令速查

需求                        命令
=============================================================
看端口监听情况              ss -lntp | grep 端口
看 firewalld 状态           firewall-cmd --state
看某 zone 放行了什么        firewall-cmd --list-all
临时放行端口                firewall-cmd --add-port=8080/tcp
永久放行端口                firewall-cmd --permanent --add-port=8080/tcp
让永久配置生效              firewall-cmd --reload
看 iptables 规则和计数      iptables -L -n -v --line-numbers
追加 / 插入 iptables 规则   iptables -A / -I INPUT ...
保存 iptables 规则          iptables-save > /etc/sysconfig/iptables
抓外部进来的 SYN 包         tcpdump -nn tcp port 8080 and ...syn

口诀:本机通外部不通先查防火墙 -> timeout 多是被 DROP
      firewalld 改 runtime 也要改 permanent -> 云上还有安全组一层

避坑清单

  1. 端口在监听不代表能连,外部流量还要穿过防火墙的 INPUT 链
  2. 本机 curl 走回环不过对外规则,本机通外部不通先查防火墙
  3. refused 是端口没监听或被 REJECT,timeout 多是被 DROP 或安全组拦
  4. ss 看监听地址,127.0.0.1 外部永远连不上,要 bind 成 0.0.0.0
  5. firewalld 的 runtime 和 permanent 是两套,只改一个会出问题
  6. firewall-cmd 不加 permanent 重启就丢,加了 permanent 不 reload 不生效
  7. iptables 规则从上到下匹配即停,顺序错了 ACCEPT 会被前面的 DROP 截掉
  8. iptables 规则默认只在内存,要 iptables-save 落盘否则重启全丢
  9. 云服务器有安全组和主机防火墙两层,两层都放行端口才能连
  10. 安全组在机器外面,本机 ss/iptables 看不到它,排查时最易遗漏

总结

这次"端口在监听、外面却连不上"的事故,纠正了我一个对"端口"这个概念的过度简化的理解。在这次之前,我心里把"端口"想象成一个二元的、非黑即白的东西:一个端口,要么是"开着"的——有进程在监听它,于是全世界都能连进来;要么是"关着"的——没进程监听,谁都连不上。在这个简化模型里,我只要用 ss 确认了有进程在 0.0.0.0:8080 上监听,就等于盖章认定"这个端口是开着的、是对全世界开放的",于是外部连不上这件事,就成了一桩在我的世界观里无法成立的悬案。复盘到根上,我才真正理解,一个数据包想从外部机器,抵达我那个正在监听的服务进程,它要走的根本不是一条畅通无阻的直路,而是一条要【依次穿过好几道独立关卡】的通道——而"进程在监听",仅仅是这条通道【最末端】的那一道门而已。一个从外部发来的 TCP SYN 包,它的旅程是这样的:它先要被云平台的【安全组】放行,这是第一道关卡,它在我的机器之外,由云控制台掌管;穿过安全组之后,包到达我的机器网卡,接着它要穿过机器自己的【主机防火墙】——在 CentOS 7 上,就是 firewalld,这是第二道关卡;只有连这第二道关卡也放行了,包才终于被交到内核的 TCP 协议栈,而协议栈这时才会去看"8080 端口上有没有进程在监听"——这才是我之前以为是【唯一】一道、实际上却是【最后】一道的关卡。我那台机器的真实情况是:服务进程这道末端的门,是敞开的;但它前面的 firewalld 那道门,是死死关着的——firewalld 的默认策略,是"凡没有被明确放行的端口,一律拦截",而 8080 从头到尾就没被加进过它的放行列表。于是外部的 SYN 包,在 firewalld 这一关就被静默地丢弃了,根本没有机会走到"进程监听"那一步。这也解释了另一个我当时觉得很微妙的细节:为什么对方看到的是"超时"而不是"拒绝"。因为 firewalld 用的是 DROP 这个动作——它把包悄无声息地扔掉,不给对方任何回应;对方的机器发出了 SYN,却永远等不到回包,只能一直等到自己的超时计时器耗尽。如果当时拦截我的是一条 REJECT 规则,对方会立刻收到一个明确的拒绝包,看到的就会是 Connection refused。"超时"还是"拒绝",这两个词不是同义词,它们是指向不同病根的、信息量很大的诊断线索:超时,意味着包石沉大海,矛头指向 DROP 规则或安全组;拒绝,意味着包到了但被明确回绝,矛头指向没有进程监听或 REJECT 规则。这次事故还顺带教会了我 firewalld 一个极其反直觉、却极其重要的设计:它的配置分成 runtimepermanent 两套——不加 --permanent 改的是运行时配置,立刻生效但重启就丢;加了 --permanent 改的是永久配置,重启不丢却不立刻生效,必须 --reload。无数"昨天放行了、今天重启完又连不上"的怪事,根子都在这里。这次从一个"端口明明开着"的悬案出发,我最大的收获,是把脑子里那个"端口非开即关"的二元模型,换成了一个"数据包要逐层闯关"的通道模型:判断一个服务能不能被连上,我不能只盯着最末端那道"进程监听"的门,而要顺着数据包的旅程,从云安全组、到主机防火墙、再到监听进程,一关一关地确认下来——任何一关的门是关着的,这条通道就是不通的。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

cron 手动跑没问题定时就不跑:一次 Linux 定时任务排查复盘

2026-5-20 19:06:21

Linux教程

文件删了磁盘空间却没释放:一次 Linux inode 与文件删除机制排查复盘

2026-5-20 19:14:03

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索