"端口不通"是运维里最常见的故障,也是最容易让人手忙脚乱的一种——明明服务起来了、进程也在、配置文件看不出问题,但从外面就是连不上。更麻烦的是,造成这个现象的原因至少有七八种,分布在不同的层:可能是云平台的安全组没放行,可能是系统防火墙拦了,可能是服务只监听在 127.0.0.1 上,可能是中间运营商封了端口,还可能是应用自己做了来源限制。
这篇文章给出一套从外到内的四层排查顺序,每一层都配具体的验证命令。核心思路是用不同的错误表现快速缩小范围——"连接被拒绝"和"连接超时"指向完全不同的原因,先看清错误信息能省掉一半的排查时间。
一、先看错误表现:这决定了排查方向
在动手改配置之前,先仔细看一下连接失败时返回的是什么。三种典型表现的成因完全不同:
| 表现 | 含义 | 指向的原因 |
|---|---|---|
Connection refused(拒绝连接) |
主机收到了包,但没有任何进程监听该端口,于是回了 RST | 服务没起来,或监听在别的地址/端口上。网络层面是通的 |
Connection timed out(连接超时) |
SYN 包发出去后没有任何回应 | 包被丢了:安全组、系统防火墙、或上游网络拦截 |
No route to host(无路由) |
找不到到达目标的路径 | 路由配置问题、目标不可达、或者被 ICMP 拒绝 |
这个区分极其有用:Connection refused 说明你的包已经到达了目标主机的协议栈,安全组和防火墙都是放行的,问题在"服务没监听";而 Connection timed out 说明包在路上被丢掉了,要在安全组、防火墙、中间网络这几层找原因。
# Linux 上测端口
nc -zv -w 3 1.2.3.4 8080
telnet 1.2.3.4 8080
curl -v http://1.2.3.4:8080/
# Windows 上
Test-NetConnection 1.2.3.4 -Port 8080
Windows 的 Test-NetConnection 输出里,TcpTestSucceeded 为 False 且 PingSucceeded 为 True 是典型的"主机可达但端口不通"。
二、第一层:云安全组与网络 ACL
这是最容易漏掉的一层,因为它在云平台的虚拟化网络上生效,在服务器系统内部看不到任何痕迹。你在服务器上把防火墙全关了,安全组仍然会拦。
云安全组(各家叫法不同,有的叫安全组、有的叫防火墙、有的叫 ACL)通常工作在实例网卡之外,采用默认拒绝模型:没有明确放行的流量一律丢弃。它同时管入方向和出方向。
排查要点:
| 检查项 | 说明 |
|---|---|
| 入方向规则 | 是否放行了目标端口和协议(TCP/UDP 要分清) |
| 源地址范围 | 是否限制了来源 IP,你的测试地址是否在范围内 |
| 出方向规则 | 容易被忽略。如果出方向默认拒绝,回包发不出去,表现为连接超时 |
| 规则优先级 | 部分平台支持优先级,拒绝规则可能排在允许规则之前 |
| 实例是否绑定 | 安全组是否已关联到目标实例或网卡 |
| 子网 ACL | 有些平台在子网层还有一层 ACL,也要检查 |
一个实用的验证方法:临时把安全组改成允许所有来源访问该端口,再测一次。如果能通,说明就是安全组的问题,然后按最小权限原则改回精确的规则。不要长期保持全放开状态,验证完立刻收紧。
出方向被忽略是常见坑。安全组默认策略在不同平台不一致:有的默认放行所有出方向,有的默认拒绝。如果入方向放行了但连接仍然超时,一定要检查出方向。
三、第二层:系统防火墙
安全组放行后,包到达了服务器自己的协议栈,接下来是系统内的防火墙。
firewalld(RHEL、Rocky、CentOS 7+ 默认)
sudo firewall-cmd --state
sudo firewall-cmd --list-all
sudo firewall-cmd --list-ports
sudo firewall-cmd --get-active-zone
放行端口:
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
注意 --permanent 和运行时规则的区别:不加 --permanent 的修改立即生效但重启后丢失;加了 --permanent 需要 reload 才生效。正确做法是两条都执行,或者加 --permanent 后 reload。
还要确认网卡在正确的 zone 里:
sudo firewall-cmd --get-zone-of-interface=eth0
sudo firewall-cmd --permanent --zone=public --change-interface=eth0
ufw(Ubuntu 默认)
sudo ufw status verbose
sudo ufw allow 8080/tcp
sudo ufw allow from 10.0.0.0/24 to any port 3306
sudo ufw reload
iptables(传统,仍广泛使用)
sudo iptables -L INPUT -n -v --line-numbers
sudo iptables -t nat -L -n -v # Docker 等会在 nat 表加规则
sudo iptables -S # 以规则形式显示,便于复制
重点看 INPUT 链的默认策略(policy DROP 还是 ACCEPT),以及规则的顺序。iptables 是顺序匹配,一旦匹配到就停止,所以一条位置靠前的 DROP 规则会让后面的 ACCEPT 失效。
临时放行(重启后丢失):
sudo iptables -I INPUT -p tcp --dport 8080 -j ACCEPT
插入到第 1 位(-I)而不是追加到末尾(-A),避免被前面的 DROP 规则挡住。
持久化:
sudo apt install -y iptables-persistent # Debian、Ubuntu
sudo netfilter-persistent save
# RHEL 系:iptables-save > /etc/sysconfig/iptables
nftables(新一代,Debian 10+ 和 RHEL 8+ 默认后端)
sudo nft list ruleset
重要提醒:firewalld 和 nftables 可能同时存在。 在新版系统上,firewalld 的后端就是 nftables,你用 firewall-cmd 加的规则会出现在 nft list ruleset 的输出里。如果同时手动操作了 nftables 和 firewalld,规则可能互相覆盖。建议只用一个工具管理。
Windows 防火墙
Get-NetFirewallProfile | Select-Object Name,Enabled
Get-NetFirewallRule -Direction Inbound -Enabled True |
Where-Object {$_.LocalPort -like '*8080*'} | Format-Table Name,LocalPort,Action
# 放行端口
New-NetFirewallRule -DisplayName "Open TCP 8080" -Direction Inbound `
-LocalPort 8080 -Protocol TCP -Action Allow
# 删除规则(回滚)
Remove-NetFirewallRule -DisplayName "Open TCP 8080"
命令行版本:
netsh advfirewall firewall show rule name=all dir=in
netsh advfirewall firewall add rule name="Open 8080" dir=in action=allow protocol=TCP localport=8080
风险提示:Windows 上修改防火墙规则时,如果通过远程桌面操作,务必先确认不会影响 3389 端口的规则。建议先在控制台确认已有规则,再新增而不是修改现有规则。
一个可能致命的 RHEL 专属问题:SELinux
RHEL 系上即使防火墙全放行,SELinux 也可能阻止服务监听非标准端口:
sudo getenforce
sudo ausearch -m AVC -ts recent # 看是否有被拒绝的记录
sudo semanage port -l | grep http_port_t
sudo semanage port -a -t http_port_t -p tcp 8080 # 允许 httpd 用 8080
临时排查可以用 setenforce 0 关掉 SELinux 验证(仅用于排查,确认后要改回 setenforce 1,正确做法是配 SELinux 策略而不是永久关闭)。
四、第三层:服务是否监听在正确的地址
这是"能 ping 通、防火墙也开了,但就是连不上"的第二大原因。
sudo ss -lntup
sudo ss -lntup | grep -E ':8080|:3306'
sudo ss -lntp 'sport = :8080'
sudo lsof -i :8080
sudo lsof -iTCP -sTCP:LISTEN -P -n
重点看 Local Address:Port 这一列:
| 监听地址 | 含义 | 谁能连 |
|---|---|---|
0.0.0.0:8080 |
监听所有 IPv4 地址 | 外部可以连(前提是防火墙放行) |
127.0.0.1:8080 |
只监听回环 | 只有本机能连,外部一律不通 |
10.0.0.10:8080 |
只监听某个内网地址 | 只有能到达该地址的网络能连 |
[::]:8080 |
监听所有 IPv6 地址 | IPv6 可连,IPv4 未必(取决于双栈配置) |
[::1]:8080 |
只监听 IPv6 回环 | 基本等于只本机 |
127.0.0.1 是最经典的坑。 很多应用(MySQL、Redis、MongoDB、各类开发框架自带的开发服务器)默认就监听在 127.0.0.1,这是出于安全的良好默认值,但如果你不知道这一点,就会困惑于"服务明明起来了为什么连不上"。
常见服务的默认监听与修改位置:
| 服务 | 配置文件 | 关键参数 |
|---|---|---|
| MySQL | /etc/mysql/mysql.conf.d/mysqld.cnf 或 /etc/my.cnf |
bind-address = 0.0.0.0(或指定内网地址) |
| Redis | /etc/redis/redis.conf |
bind、protected-mode |
| PostgreSQL | /etc/postgresql/*/main/postgresql.conf |
listen_addresses = '*' |
| Nginx | /etc/nginx/sites-enabled/* |
listen 8080; 默认即所有地址 |
| MongoDB | /etc/mongod.conf |
net.bindIp |
| Java/Node/Python 应用 | 应用配置或启动参数 | --host 0.0.0.0、server.address=0.0.0.0 |
改监听地址时务必同时考虑安全:把数据库从 127.0.0.1 改成 0.0.0.0 等于对网络开放,必须配合安全组限制来源网段,并设置强密码。
分级自测定位
用不同目标地址测试,能快速定位问题在哪一层:
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/ # 本机
curl -sS -o /dev/null -w '%{http_code}\n' http://10.0.0.10:8080/ # 内网地址
curl -sS -o /dev/null -w '%{http_code}\n' http://公网IP:8080/ # 公网地址
- 本机通、内网地址不通 → 监听地址绑在
127.0.0.1 - 本机和内网地址都通、公网不通 → 安全组或中间网络没放行
- 全都不通 → 服务根本没起来,或监听在别的端口
五、第四层:进程是否存在,端口是否一致
服务"看起来在跑"和实际在监听是两回事。进程可能启动失败后退出了,也可能监听在配置文件里写的另一个端口上。
systemctl status nginx
systemctl status nginx --no-pager -l
ps aux | grep -E 'nginx|node|java' | grep -v grep
journalctl -u nginx -n 50 --no-pager
配置文件里的端口和实际监听的端口不一致是常见情况,尤其是改过配置文件但没重载:
sudo nginx -t # 检查配置语法
sudo nginx -T | grep -n listen # 输出完整配置并找 listen
sudo systemctl reload nginx # 重载(不断连接)
sudo systemctl restart nginx # 重启(会短暂中断)
注意 reload 和 restart 的区别:reload 会重新加载配置且不中断已有连接,改监听端口这类变更有时必须 restart 才生效。
进程在但端口不对的另一个常见来源是配置被多处定义覆盖(Nginx 的 sites-enabled 里多个文件都定义了 listen,或者 include 顺序问题)。用 nginx -T 输出完整生效配置来确认。
应用层自身的访问控制
网络层全通了,应用还能自己拒绝你。这一层经常被遗漏:
| 应用 | 限制方式 | 位置 |
|---|---|---|
| Nginx | deny/allow 指令 |
server 或 location 块 |
| Nginx | 只允许特定 Host | server_name 不匹配时返回默认站点 |
| MySQL | 用户授权带主机限制 | 'user'@'10.0.0.%',来源不匹配会被拒绝 |
| Redis | protected-mode、requirepass |
redis.conf |
| PostgreSQL | pg_hba.conf |
按来源 IP 和网段授权 |
| 各类应用 | IP 白名单、API 网关策略 | 应用配置或管理后台 |
Nginx 的访问控制:
location /admin/ {
allow 10.0.0.0/24;
allow 203.0.113.5;
deny all;
}
Nginx 还有个容易忽略的点:如果请求的 Host 头不匹配任何 server_name,会命中默认 server 块,返回的可能是另一个站点或者 444。测试时用 curl -H 'Host: example.com' 指定 Host。
MySQL 的用户授权是"用户名 + 来源主机"的组合,'app'@'localhost' 和 'app'@'%' 是两个不同的账号。远程连不上时报 Access denied,通常就是来源主机不匹配,而不是网络问题。
六、中间网络:容易被忽略的一层
ISP 端口封堵
部分宽带运营商(尤其是家宽)会封堵常见服务端口的入站,最常见的是 80 和 443,以及 25(邮件发送端口)。云厂商出于反垃圾邮件的考虑,也普遍默认限制 TCP 25 的出站,需要单独申请解封。
表现:服务器配置完全正确,但从特定网络访问时连接超时;换一个网络(比如手机流量)就正常。
判断方法:从不同网络分别测试。如果只有特定网络不通,且服务端 tcpdump 根本没收到包,就是中间网络的问题。
解决方案:家用宽带跑服务改用非标准端口(如 8080、8443);邮件发送改用 465 或 587 端口(STARTTLS 或 SMTPS);或者干脆换到云主机上部署。
NAT 与端口映射
如果服务器在内网或做了 DNAT,还要检查映射规则:
sudo iptables -t nat -L -n -v
sudo nft list ruleset | grep -i dnat
云上用弹性 IP、NAT 网关、负载均衡时,映射关系在控制台配置。常见错误:负载均衡的监听器端口和后端实例端口不一致、健康检查失败导致后端被摘除、或者后端安全组没有放行负载均衡的地址段。
用 tcpdump 确认包到底有没有到达
这是区分"被防火墙丢"和"根本没到"的决定性手段:
sudo tcpdump -i eth0 -nn 'tcp port 8080'
然后外部发起一次连接,观察输出:
| 观察结果 | 结论 |
|---|---|
| 看到 SYN 进来,但没有 SYN-ACK 出去 | 包到了,被本机丢弃(系统防火墙或应用拒绝) |
| 看到 SYN 进来,且有 SYN-ACK 回去 | 服务端正常,问题在回程或中间网络 |
| 完全没有任何包 | 包没到达,问题在安全组、上游网络或 NAT 映射 |
这个测试非常有用,建议在排查陷入僵局时优先做。
七、完整排查清单
按顺序逐项确认,能通就停:
| 步骤 | 检查项 | 命令/位置 | 通过标准 |
|---|---|---|---|
| 1 | 错误表现 | nc -zv、Test-NetConnection |
明确是 refused 还是 timeout |
| 2 | 云安全组入方向 | 控制台 | 端口 + 协议 + 来源已放行 |
| 3 | 云安全组出方向 | 控制台 | 允许回包 |
| 4 | 系统防火墙 | firewall-cmd --list-all / ufw status / iptables -L -n |
端口已放行 |
| 5 | 服务监听地址 | ss -lntup |
含 0.0.0.0 或目标地址,不是 127.0.0.1 |
| 6 | 服务监听端口 | ss -lntup |
与预期端口一致 |
| 7 | 进程状态 | systemctl status、ps |
active (running),无报错 |
| 8 | 应用配置 | nginx -T、应用配置 |
端口与监听一致,配置已生效 |
| 9 | 应用层限制 | deny/allow、数据库授权、白名单 |
来源被允许 |
| 10 | 本机自测 | curl http://127.0.0.1:端口 |
返回正常响应 |
| 11 | 内网自测 | curl http://内网IP:端口 |
返回正常响应 |
| 12 | 外部测试 | 另一台机器 nc -zv、在线端口检测 |
连接成功 |
| 13 | 包是否到达 | tcpdump -i eth0 -nn 'tcp port N' |
能看到进来的 SYN |
| 14 | 中间网络 | 换网络测试、检查 ISP 封堵 | 非标准端口可通 |
| 15 | SELinux(RHEL) | getenforce、ausearch -m AVC |
无拒绝记录 |
八、本地与外部测试方法
Linux
nc -zv -w 3 1.2.3.4 8080 # TCP 端口连通性
nc -zvu -w 3 1.2.3.4 53 # UDP 端口(UDP 检测不太可靠,未必有回应)
telnet 1.2.3.4 8080 # 传统方式
curl -v http://1.2.3.4:8080/ # 看完整握手过程
curl -sS -o /dev/null -w '%{http_code} %{time_connect}s\n' http://1.2.3.4:8080/
注意 UDP 端口检测本质不可靠:UDP 无连接,没有服务监听时通常什么也不返回(而不是明确拒绝),所以"无响应"不能等同于"端口关闭"。测 UDP 要在应用层验证(比如 DNS 用 dig)。
Windows
Test-NetConnection 1.2.3.4 -Port 8080
tnc 1.2.3.4 -Port 8080 -InformationLevel Detailed
Resolve-DnsName www.example.com
旧版本 PowerShell 没有 Test-NetConnection 时,可以用 .NET 类:
$c = New-Object System.Net.Sockets.TcpClient
$c.ConnectAsync("1.2.3.4", 8080).Wait(3000)
$c.Connected
从外部测试的重要性
在服务器上自测成功,不代表外部能访问。 必须在真正的外部网络(另一台云主机、手机流量、或者在线端口检测服务)上验证。这一条也顺便覆盖了安全组和中间网络的问题。
在线端口检测服务可以搜索"端口扫描 在线"找到,输入 IP 和端口即可从外部检测。注意这类服务会记录你的 IP 和端口信息,敏感系统慎用。
常见问题
服务起来了,本机 curl 能访问,外部就是连不上?
按顺序查:先看安全组入方向是否放行(最常见),再看系统防火墙,最后看监听地址是不是 127.0.0.1。本机 curl 通只说明进程在跑且应用正常,与安全组、防火墙、监听地址三者都无关。
Connection refused 是什么意思,怎么修?
说明包到达了主机,但该端口没有进程监听,主机回了 RST。检查服务是否真的在运行(systemctl status)、监听端口是否与预期一致(ss -lntup)、以及监听地址是否包含你访问时用的那个地址。
Connection timed out 呢?
说明 SYN 包发出去没有任何回应,包被丢弃了。查安全组入方向和出方向、系统防火墙、以及中间网络(ISP 封堵、NAT 映射)。用 tcpdump 确认包有没有到达服务器,能一步区分是本机的锅还是上游的锅。
我把防火墙全关了还是不通,为什么?
因为云安全组在虚拟化层生效,与系统内的防火墙完全独立,关系统防火墙不影响它。另外还可能是安全组的出方向没放行,或者监听地址不对。
80 端口在家里怎么都开不了?
很可能是家宽运营商封堵了 80/443 的入站。验证方法是用手机流量测试同一台服务器,或者改用 8080、8443 这类非标准端口。长期方案是把服务部署到云主机上。
为什么 UDP 端口总是测不准?
UDP 是无连接协议,没有"握手"可完成,很多服务在收到不认识的 UDP 包时选择静默丢弃。所以 nc -zvu 的结果参考价值有限,应该在应用层验证,比如 DNS 用 dig @IP 域名、NTP 用 ntpdate -q。
改了监听地址后要注意什么?
把服务从 127.0.0.1 改成 0.0.0.0 等于对网络开放,尤其是数据库、Redis 这类服务。必须同时做三件事:安全组只放行必要的来源网段、设置强密码或启用认证、确认没有使用默认账号。
总结
- 先看错误表现再动手:
Connection refused说明网络通、服务没监听;Connection timed out说明包被丢弃,要查安全组、防火墙和中间网络。这一个判断能省掉一半排查时间。 - 云安全组是第一层且在系统内不可见,采用默认拒绝模型,入方向和出方向都要检查;它是"本机自测正常但外部不通"的最常见原因。临时全放开验证后必须立刻按最小权限收紧。
- 系统防火墙要看工具是否混用:RHEL 系用
firewall-cmd、Ubuntu 用ufw、底层可能是 iptables 或 nftables,新版系统上 firewalld 的后端就是 nftables,不要同时用两个工具管理。RHEL 系还要额外排查 SELinux。 ss -lntup的监听地址列是排查核心:127.0.0.1:8080意味着只有本机能连,这是数据库、Redis、开发服务器"外部连不上"的头号原因;改成0.0.0.0时必须同步收紧安全组和启用认证。- 分三级自测(本机 → 内网地址 → 公网地址)能快速定位层级;用
tcpdump -i eth0 -nn 'tcp port N'确认 SYN 是否到达,能决定性地区分"本机丢弃"和"上游拦截"。 - 中间网络别忘了:家宽常封 80/443/25,云厂商普遍限制 25 出站;NAT 和负载均衡要检查映射端口一致性和健康检查状态。所有检查都必须在真正的外部网络复测,服务器上自测成功不代表对外可用。
企业QQ咨询




