校园网封了 22 端口?一次完整的 SSH 排障记录
最近遇到了一次很典型的 SSH 网络故障:
同一台公网服务器,在手机热点下可以正常 SSH,但在校园网环境下,无论怎么尝试都连不上。
最开始看到的现象非常容易让人误判:
ping正常,延迟约 25 ms;ssh -v卡在Connecting to ... port 22;nc探测 22 端口同样没有立即返回结果;- 服务器上的
sshd正常运行; - UFW、iptables、云安全组都确认放行;
- 换成手机热点后,SSH 立即恢复正常。
最终定位到:问题不在服务器,而是在校园网出口侧对 TCP 22 连接进行了过滤。
这篇文章记录整个排查过程,也整理一下几个容易混淆的网络诊断点。
一、故障现象
目标服务器:
1 | 103.36.221.122 |
在校园网环境下执行:
1 | ssh -v -p 22 user@103.36.221.122 |
日志停在:
1 | debug1: Connecting to 103.36.221.122 [103.36.221.122] port 22. |
之后没有任何进一步输出。
与此同时:
1 | ping 103.36.221.122 |
完全正常:
1 | 64 bytes from 103.36.221.122: icmp_seq=1 ttl=52 time=27.9 ms |
看起来网络没有问题。
于是很容易产生一个疑问:
既然服务器能 ping 通,为什么 SSH 反而连不上?
关键就在这里:ping 和 SSH 根本不是同一种网络测试。
ping 使用的是 ICMP,而 SSH 建立在 TCP 之上。
所以:
1 | ICMP 正常 ≠ TCP 正常 |
更不能进一步推出:
1 | ping 正常 ≠ SSH 端口一定可达 |
二、第一步:确认 IP 层连通性
先测试最基础的 IP 层:
1 | ping 103.36.221.122 |
结果正常,说明至少可以确认:
- 目标主机能够对 ICMP Echo Request 做出响应;
- 基本 IP 路由是可达的;
- 当前链路不存在明显的整体断网问题。
但这里能得到的结论其实非常有限。
它只能说明:
ICMP 这条路径是通的。
至于 TCP 22 是否能够建立连接,还需要单独测试。
三、第二步:直接测试 TCP 22
使用 nc:
1 | nc -zv 103.36.221.122 22 |
结果没有立即返回:
1 | # 一直等待 |
这个现象比 ping 更有价值,因为现在测试的已经是目标 TCP 端口。
通常可以把 TCP 连接失败粗略分成几种典型情况:
| 现象 | 常见含义 |
|---|---|
succeeded/open |
TCP 三次握手成功,目标端口可以建立连接 |
Connection refused |
收到了 RST,目标端口没有对应服务监听,或被主机主动拒绝 |
| 长时间等待后超时 | 在规定时间内没有得到有效响应,常见原因包括中间设备丢包、过滤或路径问题 |
这里最值得注意的是第三种。
“一直卡住”本身就是一个诊断信号。
它至少说明:
TCP 连接请求没有在预期时间内完成。
一种非常常见的情况,就是防火墙直接丢弃了 SYN 包,因此客户端只能不断等待,直到 TCP 超时。
但要注意,严格来说:
仅凭客户端卡住,不能百分之百断定就是某一台设备执行了 DROP。
也可能存在其他路径异常,因此接下来还需要继续排除服务器侧。
四、第三步:检查服务器上的 SSH 服务
这一步的目的很简单:
先证明服务器自己真的在正常提供 SSH。
通过服务器控制台、VNC、串口或者云厂商网页终端登录服务器。
1. 检查 sshd 是否监听 22
1 | sudo ss -tlnp | grep ssh |
输出:
1 | LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=22310,fd=3)) |
这里可以看到:
1 | 0.0.0.0:22 |
说明 SSH 服务不仅监听在 127.0.0.1,而是监听在 IPv4 / IPv6 的非回环地址上,可以接受外部连接。
这一步可以排除最基础的一类问题:
1 | sshd 根本没启动 |
2. 检查服务器防火墙
如果服务器使用 UFW:
1 | sudo ufw status |
例如:
1 | Status: active |
可以看到 22/tcp 已经明确放行。
进一步检查底层 iptables 规则:
1 | sudo iptables -L -n |
其中可以看到类似:
1 | ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 |
这进一步说明服务器本机没有明显的 22 端口拦截规则。
3. 直接在服务器本机测试
1 | nc -zv 127.0.0.1 22 |
结果:
1 | Connection to 127.0.0.1 22 port [tcp/ssh] succeeded! |
说明:
sshd在运行;- SSH 服务正在监听;
- 本机 TCP 连接可以建立;
- 服务器本地配置基本正常。
到这里,服务器本身的问题已经被排除了一大部分。
五、第四步:检查云安全组
服务器本机正常,还不能马上断定网络没问题。
因为在很多云服务器环境中,请求实际上还需要经过云平台的安全组。
因此登录云厂商控制台,检查入站规则。
本次排查中:
- 22/tcp 已放行;
- 为了排除规则冲突,甚至临时放宽了安全组;
- 修改后重新测试,问题仍然存在。
因此基本可以排除:
1 | 云安全组拦截 |
此时已经得到一个很有价值的结论:
1 | 客户端 |
服务器这一侧从安全组到 sshd 基本全部正常。
那么问题就越来越像出在:
1 | 客户端 → 校园网出口 → Internet |
这一段。
六、第五步:最关键的对照实验——换网络
网络问题里有一个非常有效的方法:
不要一直猜,直接换一个网络环境。
于是切换到手机热点,再执行:
1 | ssh -p 22 user@103.36.221.122 |
结果:
1 | 成功连接。 |
这一结果非常关键。
因为目标服务器没有变化:
1 | 服务器没变 |
唯一发生变化的是:
1 | 校园网 → 手机热点 |
因此可以把问题范围迅速缩小到:
校园网出口路径。
结合之前的现象:
1 | 校园网 |
最合理的解释就是:
校园网出口侧对外发起的 TCP 22 连接存在过滤或丢弃策略。
这也解释了最开始那个看起来很奇怪的现象:
1 | ping 能通 |
因为 ICMP 和 TCP 是完全不同的协议,网络设备可以对它们采用不同的策略。
七、问题到底发生在哪里?
把整个排查过程串起来,就会得到这样一条链:
1 | ┌──────────────┐ |
这里需要强调一个措辞区别:
我们可以根据实验结果判断:
问题位于校园网这一侧的出口路径。
至于校园网具体是通过 ACL、端口过滤、防火墙策略还是其他网络设备实现拦截,仅凭这些测试结果无法进一步确定。
如果一定要确认具体是哪一台设备、哪条规则,可以继续使用 traceroute、tcpdump 等工具做更底层的抓包与路径分析。
八、最终解决方案:给 SSH 换一个端口
既然确认问题是:
1 | TCP 22 无法从校园网正常连接 |
最直接的方法就是:
不要再使用 TCP 22。
可以给 SSH 增加一个其他的监听端口,例如:
1 | 51832 |
这里真正重要的不是“51832”这个数字,而是:
- 端口没有被其他服务占用;
- 服务端防火墙允许该端口;
- 云安全组允许该端口;
- 当前网络环境允许访问该端口。
通常选择一个较高且没有实际业务占用的端口即可。
需要特别说明:
49152–65535属于 IANA 定义的动态/私有端口范围,并不意味着这些端口天然“更安全”或者“一定不会被占用”。
因此,检查端口是否真正空闲,比单纯选择一个大数字更加重要。
1. 先备份 SSH 配置
1 | sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak |
这是一个很值得养成的习惯。
服务器上的网络配置尤其如此,因为 SSH 一旦配置错误,可能直接把自己锁在服务器外面。
2. 修改 sshd 配置
编辑:
1 | sudo nano /etc/ssh/sshd_config |
增加:
1 | Port 51832 |
这次不要急着删除原来的:
1 | Port 22 |
而是暂时保留:
1 | Port 22 |
这样做的目的非常简单:
先让新端口和旧端口同时工作,确认新连接正常之后,再决定是否关闭 22。
3. 修改后先检查语法
不要直接重启。
先执行:
1 | sudo sshd -t |
如果没有任何输出,通常表示配置语法检查通过。
如果出现报错,先修复配置,不要继续重启 SSH 服务。
4. 放行防火墙
如果使用 UFW:
1 | sudo ufw allow 51832/tcp |
这一步很容易被忘记。
sshd 监听新端口,并不代表防火墙一定允许这个端口。
5. 重启 SSH
不同发行版的服务名称可能不同,例如有的系统使用 ssh,有的环境会使用 sshd。
例如:
1 | sudo systemctl restart ssh |
或者:
1 | sudo systemctl restart sshd |
以当前系统实际存在的服务名为准。
6. 确认新端口真的在监听
1 | sudo ss -tlnp | grep 51832 |
例如看到:
1 | LISTEN 0 128 0.0.0.0:51832 0.0.0.0:* users:(("sshd",pid=...,fd=...)) |
说明 sshd 已经成功监听新端口。
7. 从校园网重新测试
1 | ssh -p 51832 user@103.36.221.122 |
这次连接成功进入认证阶段,说明 TCP 连接已经建立。
至此,问题得到解决。
九、过程中踩到的几个坑
这次排查过程中,还有几个非常典型的问题值得单独记录。
坑 1:防火墙放行 ≠ 端口空闲
一开始曾经考虑使用:
1 | 23333 |
因为服务器的 UFW 中已经允许这个端口。
但实际测试时:
1 | kex_exchange_identification: Connection closed by remote host |
进一步检查:
1 | sudo ss -tlnp | grep -E '23333|24444|25565' |
发现:
1 | LISTEN 0 4096 0.0.0.0:25565 0.0.0.0:* users:(("java",pid=348140,fd=68)) |
也就是说:
1 | 23333 → Node 服务 |
这些端口根本不是 SSH。
这也解释了为什么会出现:
1 | Connection closed by remote host |
TCP 连接可能已经建立,但连接到该端口的实际上是另一个应用,而不是 sshd。
因此选端口时一定要检查:
1 | sudo ss -tlnp | grep <port> |
“防火墙允许这个端口”与“这个端口可以拿来运行 SSH”是两件完全不同的事情。
坑 2:不要把 Connection closed、refused 和 timeout 混为一谈
这几个错误背后的网络阶段并不一样。
Connection refused
例如:
1 | ssh: connect to host 103.36.221.122 port 22: Connection refused |
通常意味着 TCP 层已经收到对方的拒绝响应,例如 RST。
常见原因:
- 端口没有服务监听;
- 主机侧规则主动拒绝连接。
Connection closed by remote host
例如:
1 | kex_exchange_identification: Connection closed by remote host |
这意味着 TCP 连接至少已经进入建立后的阶段,然后连接被关闭。
如果目标端口实际上运行着 Node、Java 等其他服务,就可能出现这种情况。
长时间等待后超时
例如:
1 | Connection timed out |
说明客户端一直没有得到足够的响应来完成连接。
常见原因包括:
- 防火墙丢弃数据包;
- 中间设备进行过滤;
- 路由异常;
- 目标侧无响应。
所以:
1 | refused |
并不是三个“差不多”的错误,它们对应的是不同阶段的问题。
十、另一个容易误判的地方:nc 卡住到底说明什么?
之前我一度把:
1 | nc -zv 103.36.221.122 22 |
卡住直接理解成:
1 | DROP |
严格来说,这个结论太绝对了。
更加严谨的判断应该是:
TCP 连接在指定时间内没有完成,也没有立即收到拒绝响应。
其中一种典型原因确实是:
1 | SYN → 防火墙直接 DROP → 客户端一直等待 → 最终 timeout |
但还可能存在其他原因。
如果需要进一步确定,可以使用:
1 | sudo tcpdump -i any -n port 22 |
观察 SYN 是否发出、有没有 SYN-ACK 返回。
例如:
1 | 客户端 → SYN → |
说明服务器侧至少有返回。
如果只有:
1 | 客户端 → SYN → |
始终没有收到对应响应,那么就可以进一步怀疑中间路径存在丢弃或过滤。
这时候排障就从:
1 | “感觉端口有问题” |
进入了:
1 | “观察实际 TCP 报文” |
的阶段。
十一、为什么换网络是一个非常有效的排障方法?
这次真正让我快速定位问题的,其实不是某条命令,而是:
手机热点对照实验。
网络故障最怕的是同时存在太多变量。
例如:
1 | 客户端 |
如果只盯着服务器排查,就可能在 ufw、iptables、sshd、云安全组里来回折腾很久。
而一旦换成:
1 | 客户端 |
服务器完全没变。
所以:
1 | 校园网失败 |
本身就是一次非常强的“隔离变量”实验。
它直接把问题从:
1 | 服务器 / 云平台 / 网络路径 |
缩小到了:
1 | 校园网出口及其路径 |
这比不停修改服务器配置有效得多。
十二、以后遇到 SSH 连接问题,我会这样排查
现在回头看,这类问题其实可以形成一套比较固定的排查顺序。
第 1 层:IP 是否可达
1 | ping <ip> |
确认是不是连最基础的 IP 通信都不存在。
但注意:
1 | ping 通 ≠ TCP 一定通 |
第 2 层:目标 TCP 端口是否可建立
1 | nc -zv <ip> <port> |
重点观察:
1 | succeeded |
分别对应不同方向的排查。
第 3 层:服务器有没有服务监听
1 | sudo ss -tlnp | grep <port> |
确认:
1 | 端口存在吗? |
第 4 层:服务器本机测试
例如:
1 | nc -zv 127.0.0.1 22 |
证明服务本身至少能够接受本机连接。
第 5 层:检查服务器防火墙
例如:
1 | sudo ufw status |
或者其他发行版对应的防火墙工具。
第 6 层:检查云安全组
特别是云服务器,不要只看操作系统里的防火墙。
第 7 层:更换网络
例如:
1 | 校园网 → 手机热点 |
这是非常高性价比的排查手段。
第 8 层:必要时抓包
1 | sudo tcpdump -i any -n port 22 |
进一步观察:
1 | SYN 有没有出去? |
到了这个层面,基本就是在直接观察 TCP 的实际行为,而不是仅凭客户端错误信息猜测。
十三、最终结论
这次问题最后可以归纳成一句话:
服务器本身没有故障,SSH 服务、防火墙和云安全组均正常;真正的问题是校园网出口路径对 TCP 22 连接进行了过滤,使 SSH 无法完成 TCP 建连。
整个过程最值得记录的并不是“SSH 改成了 51832”,而是排障思路:
1 | ping 正常 |
而这次经历里,我觉得最值得记住的其实是三个判断:
1. ping 通,不代表 TCP 服务可达
ICMP 和 TCP 是不同协议,网络设备完全可以对它们采用不同的策略。
2. 网络故障不要只盯着服务器
当服务器配置全部正常时,换一个网络环境做对照实验,往往比继续改服务器配置更有效。
3. SSH 换端口时,先确认端口真的空闲
不要因为:
1 | UFW 允许 |
就认为:
1 | 这个端口可以直接拿来跑 SSH |
一定要先检查:
1 | sudo ss -tlnp | grep <port> |
确认没有其他服务占用。
附:常用排障命令
测试 IP 连通性
1 | ping <ip> |
测试 TCP 端口
1 | nc -zv <ip> <port> |
也可以使用:
1 | timeout 5 bash -c 'cat < /dev/null > /dev/tcp/<ip>/<port>' |
查看监听端口和进程
1 | sudo ss -tlnp |
或者:
1 | sudo ss -tlnp | grep <port> |
查看 UFW
1 | sudo ufw status |
查看 iptables
1 | sudo iptables -L -n |
查看 SSH 配置
1 | grep -i '^Port' /etc/ssh/sshd_config |
检查 SSH 配置语法
1 | sudo sshd -t |
重启 SSH 服务
1 | sudo systemctl restart ssh |
或:
1 | sudo systemctl restart sshd |
具体取决于发行版的服务名称。
抓包观察 TCP 连接
1 | sudo tcpdump -i any -n port 22 |
也可以进一步限定来源:
1 | sudo tcpdump -i any -n host <你的IP> and port <端口> |
这次排障让我比较直观地体会到一件事:
网络问题最重要的不是“多试几个命令”,而是不断缩小故障范围。
从 ICMP 到 TCP,从客户端到服务器,再从服务器切换到网络出口;每进行一次测试,实际上都应该是在排除一部分可能性。
当最后只剩下一个变量时,问题通常也就不难解决了。