资讯动态

ping通但telnet端口不通:Linux防火墙与端口排查实战

发布时间:2026/9/18 21:20:23 来源:尧图企业网站定制
1. ping 通不等于端口能连先把这两件事分开看“服务器能 ping 通但 telnet 端口就是不通”——这几乎是我在群里、工单里见到重复率最高的一类网络问题。很多人第一反应是“网络没问题啊能 ping 通”然后就开始怀疑网线、怀疑交换机、怀疑运营商绕了一大圈最后发现是 firewalld 里少写了一条--add-port。问题的根源在于ping 和 telnet 验证的根本不是同一条通路把它们混为一谈排查方向从第一步就跑偏了。ping走的是 ICMP 协议它属于网络层做的事情非常朴素发一个 ICMP Echo Request对方回一个 Echo Reply收到就说明这台机器的 IP 层面可达、路由正常、中间设备没有把你的包丢掉。而telnet ip 端口走的是 TCP 协议它要完成一次完整的三次握手SYN 发出去、对方回 SYNACK、你再回 ACK。只要中间任何一个环节把 TCP 包拦了、丢了、拒了握手就完不成telnet 就会失败。ICMP 能过不代表 TCP 能过这是两套完全独立的策略体系防火墙里也常常是分开配置的。所以当你遇到“能 ping 通、telnet 不通”的时候正确的心理模型不是“网络不通”而是“目标主机的某个 TCP 端口没有被放行或者根本没有服务在监听”。这个判断一旦立住后面的排查就变成了一道有明确层级的选择题是服务没起、是监听地址不对、是本机防火墙拦了、是云安全组拦了还是中间还有一台设备做了 ACL。我下面会按这个顺序一层层拆。适合读这篇的人大概分三类刚接手 Linux 服务器、第一次遇到端口不通的新手被 firewalld 和 iptables 的--permanent坑过一次、想彻底搞明白的人还有在云主机上折腾了半天最后发现是安全组没开的人。不管你属于哪一类下面的内容都可以直接对着敲。2. 用 ss 和 nc 把问题锁死在某一层2.1 第一步永远是看本机有没有在监听在动防火墙之前先做一件最容易被跳过、但收益最高的事在目标服务器上确认端口到底有没有被监听。很多人上来就firewall-cmd --add-port结果服务压根没起来白折腾半小时。ss -lntp | grep 8080 # 或者老系统上用 netstat -lntp | grep 8080ss的参数含义值得记一下-l只看 LISTEN 状态-n不做端口到服务名的反向解析直接用数字避免看到http而不是80这种干扰-t只看 TCP-p显示占用端口的进程。这条命令如果没有输出说明没有任何进程在这个端口上监听那么 telnet 不通是必然的跟防火墙一点关系都没有先去查服务日志。如果有输出你还要多看一列监听地址。LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:((java,pid1234,fd56)) LISTEN 0 128 127.0.0.1:8080 0.0.0.0:* users:((java,pid1234,fd56))这两行的差别是决定性的我在 2.2 单独讲。2.2 监听地址是 127.0.0.1 还是 0.0.0.0这是分水岭0.0.0.0:8080表示监听所有网卡外部能连127.0.0.1:8080表示只监听本地回环外部永远连不上哪怕你把防火墙全关了也没用。这是“ping 通但 telnet 不通”里第二高频的原因仅次于防火墙。典型踩坑案例我列几个都是真实遇到过的服务配置项默认值现象Redisbind127.0.0.1本机 redis-cli 正常外部 telnet 6379 拒绝MySQLbind-address127.0.0.1应用服务器连不上 3306TomcataddressConnector不写就是全监听一般没这个问题Nginxlisten80等于 0.0.0.0一般没这个问题自己写的 Python 服务app.run(host127.0.0.1)本地外部必然不通改法很直接Redis 把bind 127.0.0.1改成bind 0.0.0.0生产环境建议配合防火墙和密码不要裸奔MySQL 把bind-address 0.0.0.0Flask 的host改成0.0.0.0。改完重启服务再ss看一次确认监听地址变了。注意0.0.0.0不是“某个地址”它是“本机所有 IPv4 地址”的通配写法。看到这个才说明服务对外的门是开着的。2.3 telnet 的三种报错分别指向哪里telnet失败时的提示语信息量其实很大只是很多人不看Connection refusedTCP 包到了目标主机但对方回了 RST。这说明网络是通的是本机拒绝的。常见原因服务没起、端口没监听、防火墙用了--reject-with tcp-reset、监听在 127.0.0.1。这个报错反而是好事说明不用去查中间网络。Connection timed out或一直卡在 Trying 不动SYN 发出去了但没有回应包被**静默丢弃DROP**了。典型场景是防火墙默认策略是 DROP、云安全组没放行、或者中间有设备丢包。No route to host路由不可达或对方主机直接不可达通常是路由表、网关、或者对方机器 down 了。这种情况 ping 一般也不通。把报错和这三类原因对上你就能快速判断该往哪个方向查而不是盲目地把所有防火墙命令都敲一遍。2.4 一份可以直接照着走的分层排查顺序我把顺序整理成下面这张表从下往上查命中最快顺序检查位置命令不通时的动作1服务是否监听ss -lntp | grep 端口启动服务查日志2监听地址同上看是 0.0.0.0 还是 127.0.0.1改配置为 0.0.0.03本机自测telnet 127.0.0.1 端口本机都不通服务有问题4本机防火墙firewall-cmd --list-all放行端口5SELinuxgetenforce加端口标签或调策略6云安全组控制台入方向规则加规则7中间设备tcpdump抓包找网络同学查 ACL第 3 步单独拎出来说一句先在本机 telnet 127.0.0.1通不过就是服务自己的问题压根不用碰防火墙。这个动作能省掉一大半无谓的排查。3. firewalld 放行端口的正确写法与反直觉细节3.1 --permanent 和 --reload 为什么必须成对出现CentOS 7 之后默认的防火墙是 firewalld它的规则管理有个设计上的“坑”我第一次用的时候也栽过# 这种写法立即生效但重启后消失 firewall-cmd --add-port8080/tcp # 这种写法写进了配置文件但当前不生效 firewall-cmd --permanent --add-port8080/tcp # 正确姿势两条一起用最后 reload firewall-cmd --permanent --add-port8080/tcp firewall-cmd --reload原因在于 firewalld 有**运行时配置runtime和永久配置permanent**两套。不带--permanent的命令改的是内存里的运行时规则马上生效但重启丢失带--permanent的命令改的是/etc/firewalld/zones/下的 XML 文件持久但不会自动同步到运行时。--reload这个动作做的事情就是把永久配置重新加载成运行时配置。我踩过的具体坑是这样的先敲了--permanent --add-port然后直接 telnet 测试不通于是以为命令写错了又敲一遍还是不通最后才发现忘了 reload。所以我的习惯是永远两条一起敲再 reload然后用--list-ports确认firewall-cmd --list-ports firewall-cmd --list-all--list-all的信息更全能看到当前默认 zone、绑定的网卡、开放的服务和端口、以及富规则是排查时的第一手资料。3.2 zone 选错规则写了但就是不生效firewalld 是按zone区域组织规则的每个网卡会绑定到某个 zone默认通常是public。如果你在不带--zone参数时执行命令操作的是默认 zone但如果你手动把网卡绑到了别的 zone命令就写到了错的地方。# 先看网卡实际绑在哪个 zone firewall-cmd --get-active-zones # 输出类似 # public # interfaces: eth0 # 再针对这个 zone 放行 firewall-cmd --zonepublic --permanent --add-port8080/tcp firewall-cmd --reload明确的建议是所有命令都显式带上--zonepublic哪怕默认就是 public。这样写有两个好处一是不会因为默认 zone 被改过而写错地方二是复制给别人时不会产生歧义。还有一个细节--add-port必须带协议后缀8080/tcp和8080/udp是两条不同的规则。只写8080会被拒绝。TCP 服务就写tcpDNS、SNMP 这类才需要udp。3.3 富规则只给特定来源网段开门生产环境里我不太喜欢--add-port这种全放行因为等于对全球开放。更稳的做法是用 rich rule 限定来源firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address10.20.30.0/24 port protocoltcp port3306 accept firewall-cmd --reload这条规则的含义是只允许10.20.30.0/24这个网段的机器访问本机 3306其他来源一律按 zone 的默认策略处理。数据库、内部管理端口都应该这么配。删除的时候把--add-rich-rule换成--remove-rich-rule即可规则串要完全一致。还有一个容易忽略的点如果 zone 的默认 target 是DROP或者有REJECT规则在前你的 accept 规则可能排不上队。firewalld 内部也是按顺序匹配的用firewall-cmd --list-all看清楚规则全貌再下结论。4. iptables 老派但在用顺序和持久化是两个雷4.1 -A 还是 -I决定了规则会不会被前面的 REJECT 吃掉iptables 是纯粹的顺序匹配从上往下逐条比对命中第一条就结束。所以规则插入的位置极度重要。# 追加到链尾如果前面已经有 REJECT all这条永远匹配不到 iptables -A INPUT -p tcp --dport 8080 -j ACCEPT # 插入到链首几乎总能生效 iptables -I INPUT -p tcp --dport 8080 -j ACCEPT我一般直接用-I也就是插入到第一条避免被前面的兜底规则截胡。查看时带上--line-numbers能直观看到顺序iptables -L INPUT -n --line-numbers输出里每条规则前面有编号删除时按编号删比复制整条规则字符串可靠得多iptables -D INPUT 3这里有个很容易误判的现象你看iptables -L明明有 ACCEPT 规则端口却还是不通原因往往就是前面某条更靠前的 REJECT 或者 DROP 已经拦截了。iptables 不会告诉你“哪条规则生效了”它只按顺序执行所以排查时必须从第一条往下读。4.2 重启就白干规则持久化的正确做法iptables命令加进去的规则同样活在内存里重启服务或者重启机器后会全部丢失。持久化方式跟发行版有关# CentOS / RHEL 系 service iptables save # 或者 iptables-save /etc/sysconfig/iptables # 恢复 iptables-restore /etc/sysconfig/iptablesDebian/Ubuntu 老版本用iptables-persistent会把规则存到/etc/iptables/rules.v4。新版 Ubuntu 一般直接用 ufw这个放到第 5 章讲。我的经验是调试阶段随便加稳定之后一定要立刻 save 一次并且用iptables-save看一眼输出内容确认规则真的被写进去了。4.3 一条 REJECT 把自己锁在门外的教训说一个我自己踩过的真实场景。当时为了收紧安全策略在 INPUT 链尾部加了一条iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited想的是“除了上面放行的其余全拒”。结果过了一会儿发现 SSH 断了因为我在加这条规则之前没有先把当前 SSH 端口 22 的位置理清楚而那条 REJECT 排在了某些必要规则前面。幸好当时有带外管理云控制台的 VNC进去把规则删了才恢复。这件事之后我固定了三条操作纪律改远程机器的防火墙之前先开一个不断开的会话比如 screen/tmux 里的 SSH或者云控制台 VNC 页面挂在那里。加兜底 REJECT 之前先把已有的放行规则和它们的位置列全。改完立刻在另一个窗口测 SSH 和业务端口两个都通才算过。这三条没有技术含量但能救命。5. ufw 与云安全组两道门的双重拦截5.1 ufw 的默认策略比单条规则更关键Ubuntu/Debian 上更常见的是 ufw。它的命令很好记但默认策略容易被忽略ufw status verbose # 输出会告诉你 # Default: deny (incoming), allow (outgoing), disabled (routed) ufw allow 8080/tcp ufw reload ufw status numbered关键在Default: deny (incoming)这一行所有入站默认拒绝你只放行你明确允许的端口。所以放行 8080 之后其他端口该不通还是不通这是预期行为不是 bug。有一个新手很容易搞错的地方ufw allow 8080和ufw allow 8080/tcp不一样。不带协议后缀时某些版本会被解释成同时放行 tcp 和 udp也可能给你警告建议一律写清楚/tcp。删除规则推荐用编号ufw status numbered ufw delete 3另外ufw 和 firewalld 不要同时开着两个防火墙叠加管理会互相覆盖规则排查时你会看到“我明明没拦它啊”的假象。先确认系统上到底哪个在跑systemctl status firewalld systemctl status ufw5.2 云主机安全组是另一道门很多人只开了一道这个坑我在云上见过太多次了。现象是本机ss显示在监听firewall-cmd --list-all也放行了本机telnet 127.0.0.1 端口通但外网就是Connection timed out。原因很简单云主机的流量要先经过平台侧的安全组才到你的操作系统防火墙。安全组不放行你的 iptables/firewalld 配得再漂亮都没用因为包根本没进到你的机器。操作上就是去云控制台找到这台实例的安全组在入方向加一条规则字段值规则方向入方向协议类型自定义 TCP或 TCP端口范围8080/8080授权对象0.0.0.0/0开放或指定网段优先级数字越小优先级越高顺手提一个生产建议不要习惯性写 0.0.0.0/0。8080 这种业务端口来源应该限定为负载均衡器或者固定出口 IP 的网段安全组是很多人唯一能拦住扫描的门随手全开等于把自己的服务暴露在公网扫描器面前。用tcpdump -i eth0 port 8080 -nn抓一下包就能确认 SYN 到底有没有到达你的机器抓到了说明安全组放行了没抓到就说明卡在平台侧。6. SELinux 与 Docker两个容易被忽略的隐形拦截者6.1 SELinux 端口标签不匹配会让服务直接绑定失败CentOS/RHEL 上 SELinux 默认是 enforcing它会给每个端口打上一个类型标签服务只能绑定它被允许的标签。比如 Nginx 默认只能绑http_port_t类型下的端口80、81、443、488、8008、8009、8443、9000 这些你想让它跑在 8090 上就会遇到一个非常迷惑的现象服务日志报 Permission denied 或者直接起不来但端口、防火墙全都没问题。getenforce # Enforcing 表示开启 # 查看 http 允许的端口列表 semanage port -l | grep http_port_t # 给 8090 加上 http 标签 semanage port -a -t http_port_t -p tcp 8090 # 确认 semanage port -l | grep 8090semanage命令来自policycoreutils-python-utils包最小化安装的机器上可能没有需要先装。排查这类问题时可以用下面这个组合快速定位# 临时把 SELinux 调成宽容模式验证 setenforce 0 # 再 telnet 测试如果通了基本可以确认是 SELinux 的锅确认之后不要长期开着setenforce 0重启就恢复了而且关掉 SELinux 会牺牲安全能力正确做法是加端口标签。也可以从审计日志里找证据ausearch -m avc -ts recent看到avc: denied相关记录方向就明确了。6.2 Docker 发布端口绕过 firewalld 的经典现象用 Docker 的人一定会遇到这个反直觉的情况Docker 用-p 8080:80发布端口后宿主机 firewalld 里明明没放行 8080外部却能访问反过来你放行了某个端口容器却不通。原因是 Docker 会直接往 iptables 的DOCKER链里插规则而且这条链的处理顺序在很多发行版上是先于 firewalld 的规则的。所以如果你发现“宿主机防火墙拦不住容器端口”这是正常现象不是配置错了。想控制容器端口暴露范围有两个方向发布端口时绑到具体地址-p 127.0.0.1:8080:80这样只有本机能连外网连不上再由 Nginx 反代对外。在 daemon 配置里关掉 Docker 自动改 iptablesiptables: false然后自己管理转发规则。这个动作影响面大改动前要评估现有容器。排查容器端口问题时记住一条先docker ps看端口映射对不对再docker exec进容器里ss -lntp看容器内监听地址最后才是宿主机防火墙。因为容器内服务如果监听 127.0.0.1映射到宿主机也是连不上的。7. 一次 8080 端口不通的完整排查复盘7.1 现象与第一个错误判断背景是一台 CentOS 的测试机上面跑一个 Java 服务。现象是从我的笔记本ping 10.0.1.20正常telnet 10.0.1.20 8080卡住十几秒然后Connection timed out。同一台机器上 22 端口 telnet 是通的。我当时的第一个判断是“防火墙把 8080 拦了”于是直接敲firewall-cmd --add-port8080/tcp结果还是不痛。注意这里我犯了个错误没加--permanent虽然当时是生效的但我后来 reload 了一次规则就没了导致后面观察到的现象反复变化白白多绕了十分钟。7.2 分层验证把范围从整条链路缩到一台机器冷静下来之后我按第 2 章那张表重新走了一遍# 1. 服务在不在 ss -lntp | grep 8080 # 有输出LISTEN 0 128 0.0.0.0:8080 ... java # 2. 本机自测 telnet 127.0.0.1 8080 # 通了说明服务本身没问题监听地址也是 0.0.0.0 # 3. 抓包看 SYN 有没有到 tcpdump -i eth0 port 8080 -nn第 3 步是从另一台机器发起 telnet 的同时在本机抓包。结果是只看到 SYN 进来没有 SYNACK 出去。这个信息非常关键——包到了本机但本机没回说明拦截发生在本机不在中间网络也不在云安全组因为 SYN 已经进来了。既然包到了、服务在监听、那问题就只可能在本机防火墙这条链上。于是去查 firewalldfirewall-cmd --list-all看到ports:那一行是空的心里就有数了。同时确认--get-active-zones显示 eth0 绑在 public 上我之前的命令操作的就是 publiczone 没问题。7.3 真正的元凶与验证方式到这里结论已经清楚了规则没写进永久配置中间又被 reload 清掉了。修复动作firewall-cmd --zonepublic --permanent --add-port8080/tcp firewall-cmd --reload firewall-cmd --list-ports # 输出 8080/tcp然后再从笔记本 telnet秒通。为了彻底验证我做了三件事systemctl restart firewalld之后重新测仍然通说明永久配置生效了。重启整台机器再测仍然通。用curl -v http://10.0.1.20:8080/health确认能拿到预期的 HTTP 响应不只是 TCP 握手成功。这三步里第 2 步是很多人会节省掉的一步但恰恰是它能验证持久化有没有做对。我现在的习惯是只要碰了防火墙最后一定要重启服务或重启机器再测一次否则你不知道哪天重启之后就又挂了。顺便说一下这次遗留的一个经验tcpdump抓包在排查端口问题上性价比极高。它能把“包到底有没有到本机”这个最关键的判定做出来一下子就把排查范围砍掉一半。命令不用复杂tcpdump -i eth0 port 目标端口 -nn就够了看到单向的 SYN 就说明是本机在拦看到完全没有包就说明卡在更上游。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价