服务器端口不通怎么办?从安全组、防火墙到监听服务逐项排查

"端口不通"是运维里最常见的故障,也是最容易让人手忙脚乱的一种——明明服务起来了、进程也在、配置文件看不出问题,但从外面就是连不上。更麻烦的是,造成这个现象的原因至少有七八种,分布在不同的层:可能是云平台的安全组没放行,可能是系统防火墙拦了,可能是服务只监听在 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 和负载均衡要检查映射端口一致性和健康检查状态。所有检查都必须在真正的外部网络复测,服务器上自测成功不代表对外可用。