开头
如果你也遇到过这种情况:远程主机明明能ping通,网络通着,可Xshell一连接就弹出一个“connection failed”,这时候多数人第一反应是“服务器是不是挂了?”,但ping通又说明网络是通的。这个组合很迷惑,我最初也被它卡了半天。后来把整个链路从头到尾捋了一遍,才发现“能ping通”和“能SSH连上”之间隔着好几道关卡,每一关卡住都会报这个错。
这篇文章我就按我的实际排查顺序,把Xshell报“connection failed”的可能原因、排查方法、修复步骤全部整理出来。不管你是运维新手还是日常要用远程服务器的开发者,照着这个思路走一遍,基本都能定位到问题。内容里我会讲原理、给命令、贴配置,也会把踩过的坑单独列出来,争取让你一次搞清楚。
1. 先搞清楚“能ping通”和“连接失败”到底差在哪
1.1 ping通意味着什么
ping用的是ICMP协议,走的是网络层的ICMP请求和应答。你本地发出一个ICMP Echo Request,目标主机内核协议栈收到后回一个Echo Reply,ping就是这样打通的。它只验证了“从你的IP到目标IP的路由是通的,目标主机的网络栈是活的”,仅此而已。
所以ping通只能说明网络层没问题。举个例子:你家小区门口到公司楼下这条路能走通,但不代表公司大门开着、前台在岗、你预约的会客室能用。ping通就是“路通”,而Xshell要连的SSH服务,是“公司大门后边的具体房间”有没有开。
很多朋友一看到ping通就认为网络没问题,然后把问题全归到软件上,其实这样会漏掉很多中间环节。反过来,ping不通也不代表SSH一定连不上,因为有些主机为了安全会直接禁掉ICMP,但对外服务端口照样开。所以第一步先放下“ping通就万事大吉”的惯性认知,后面排查起来才不容易钻牛角尖。
1.2 connection failed到底是在哪一步报出来的
Xshell建立SSH连接时,大致要经过“DNS解析 -> TCP三次握手 -> SSH协议握手 -> 认证 -> 会话建立”这么几个阶段。“connection failed”是一个笼统的客户端错误提示,通常表示在TCP三次握手或者更早期阶段就没成功。
如果把Xshell的日志打开(菜单栏“工具 -> 新建会话”里可以设置日志记录),你会发现报这个错的时候,往往连TCP连接都没有完成,数据包根本没到达SSH服务端。换句话说,Xshell的端口敲门没人应答。
真正到了SSH协议层,如果密钥交换或认证失败,报错通常会是“Connection closed by remote host”或者“Authentication failed”,而不是“connection failed”。所以看到“connection failed”,优先怀疑还没到SSH那一层,先把目光放在TCP端口、防火墙、IP路由这些位置上。
1.3 常见原因初步定位
根据我处理过的案例,“Xshell能ping通但连接失败”这个组合,最常出现的几个原因排序大概是:
- 目标主机SSH服务没启动或监听在非默认端口
- 防火墙(iptables/firewalld/安全组)拦截了22端口
- 目标主机的连接数达到上限,拒绝新连接
- IP地址冲突,导致包发到了别的机器
- 网络中有设备做了ACL只允许ICMP不允许TCP(这种情况少见但存在)
- 本地或远程路由配置异常,导致回包路径不通
其中前三个占到了八成以上。下面我的排查思路也是从这三个重点入手,再逐步向外围扩散。只要按顺序走,一般十分钟内能定位。
2. 从网络层到应用层逐个排查:我的实操顺序
2.1 第一步:确认端口监听状态
在远程主机上执行:
netstat -tlnp | grep 22 # 或者用 ss ss -tlnp | grep ':22'这一条命令能看到22端口是否处于LISTEN状态。如果没有任何输出,说明SSH服务根本没在监听。接着看SSH服务是否运行:
systemctl status sshd # 或者老版本用 service sshd status如果服务没启动,启动它:
systemctl start sshd systemctl enable sshd这里有个细节:很多系统默认的SSH端口是22,但如果你之前改过配置,把Port改成了别的号码,比如2222,那么netstat看到的监听端口就不是22了。这时候你用Xshell连22端口,自然报connection failed。所以排查端口时别只看22,先看配置文件里的Port是多少:
grep -i '^Port' /etc/ssh/sshd_config如果配置里没写Port,那默认就是22。配置文件里如果写的是“Port 2222”,那你Xshell连接时要填2222。
2.2 第二步:检查防火墙规则
确认端口在监听之后,下一步就是看防火墙。远程主机如果是Linux,最常见的是iptables和firewalld两个管理体系。先用下面的命令看当前防火墙状态:
firewall-cmd --state # CentOS 7+ / RHEL 系 systemctl status firewalld # 或者 iptables -L -n -v如果你用云服务器,还要去云控制台看安全组/防火墙策略,很多云平台默认只放行ICMP没放行TCP,或是只放行了80/443,这种最坑:ping通,但22端口被安全组挡了。
我的检查习惯是两条命令同时看:
firewall-cmd --list-all iptables -L -n | grep 22firewalld和iptables在有些系统上是两套独立的东西,但新系统里firewalld底层会直接操作iptables,所以用iptables看更直观。想快速验证“防火墙挡住了SSH”这种猜测,可以在远程主机上临时把防火墙关掉试试(生产环境慎用,先和团队确认):
systemctl stop firewalld然后本地Xshell再连一次。如果这时候能连上,那就确定是防火墙拦截。确认后不要图省事一直关着防火墙,而是改规则放行22端口。
2.3 第三步:检查SSH服务状态与配置
服务明明在跑、端口也在监听,但Xshell还是失败?那就得看SSH服务本身有没有问题。有时候是sshd配置写错了导致服务状态异常,或者只监听在某个特定IP上。
先看sshd监听地址:
ss -tlnp | grep sshd如果监听的是127.0.0.1:22,那说明sshd只接受本机回环连接,外网一定连不上。正常应该监听0.0.0.0:22或具体的内网/公网IP。如果发现监听在127.0.0.1,修改/etc/ssh/sshd_config里的ListenAddress:
ListenAddress 0.0.0.0然后重启sshd。还有一种是配置文件语法有误,导致sshd没起来。可以手动运行一下检查配置:
sshd -t如果输出“Bad configuration options”之类的提示,就照着提示去修改对应配置项。最常见的错误是某些参数值写错,比如PermitRootLogin写成了“yes”和“no”之外的值,或Port写了非数字。
另外还要确认sshd确实在运行。有一种情况是systemd会自动拉起sshd,表面看服务active,但实际绑定的端口已经被其他进程占用了,sshd起不来。这种可以用dmesg或journalctl看日志:
journalctl -u sshd -n 50日志里通常能看到“Address already in use”或“sshd: no hostkeys available”这类信息。
2.4 第四步:确认远程主机的连接数限制
如果是高峰期连服务器,或者是被大量扫描,sshd的连接数可能达到上限。Linux下的MaxSessions、MaxStartups参数,都会影响并发连接数。如果MaxStartups设置为10:30:60这种格式,表示在并发10个未认证连接时开始拒绝,后续概率递增,到60个时全部拒绝。这时你从外面看,端口能ping通、服务在运行,但新连接进不来,Xshell就会报connection failed。
验证方法很简单:登录远程主机(如果还有办法登录的话),查看当前连接数:
ss -state established '( dport = :ssh or sport = :ssh )' | wc -l或者看看sshd日志中有没有“Maximum number of connections reached”之类的记录。如果确实是这个原因,可以适当调大MaxSessions和MaxStartups,但不要过度放开,不然容易招来暴力破解。
排查到这里,网络层、防火墙、服务层基本都覆盖了。如果还没解决,那就要考虑更外围的问题,比如路由、IP冲突等,下一节我会根据具体场景逐步给出修复方法,并把每一步操作写清楚。
3. 手把手修复:不同场景下的解决步骤
3.1 场景一:防火墙拦截
先说最常见也最容易处理的防火墙问题。假设远程主机是CentOS/RHEL系统,使用firewalld,你需要放行22端口:
firewall-cmd --permanent --add-port=22/tcp firewall-cmd --reload然后再检查一下是否生效:
firewall-cmd --list-ports如果看到22/tcp,那就没问题了。如果远程主机用的是iptables而不是firewalld,可以这样放行:
iptables -A INPUT -p tcp --dport 22 -j ACCEPT iptables-save > /etc/sysconfig/iptables后一条命令是把规则永久保存,否则重启后失效。
如果是云服务器,登录云控制台,在安全组入方向规则中增加允许TCP 22端口的规则,来源IP建议写你本地出口IP或者一个固定IP段,别直接0.0.0.0/0,降低被扫描风险。
这里要提醒一点:修改防火墙规则前,最好用某种方式保证自己还能再进到系统里,比如提前开启云平台的VNC控制台,或者确保还有一个备用端口开着。否则你清了防火墙规则又没放行22端口,等于把自己锁在门外了。我就干过这种事,大半夜被关在机房外面,最后靠带外管理才恢复。
3.2 场景二:SSH服务未启动
适用于远程主机刚装完系统、系统重启过、或sshd服务被误停的情况。启动并设置开机自启:
systemctl start sshd systemctl enable sshd启动后确认状态:
systemctl status sshd如果显示“active (running)”就正常。如果是“inactive (dead)”或“failed”状态,看日志:
journalctl -u sshd -n 30 --no-pager常见原因还有SELinux限制。虽然不算服务未启动,但影响效果很像。验证方法:
getenforce如果输出“Enforcing”,可以先临时改成Permissive试试:
setenforce 0如果改成Permissive后能连接了,说明是SELinux的布尔值问题。针对sshd放行一下:
setsebool -P ssh_sysadm_login 1 # 或者恢复对一个端口的正确上下文 semanage port -a -t ssh_port_t -p tcp 22这里“semanage”命令可能没装,需要装一下policycoreutils-python-utils。不过对于绝大多数场景,不会动SELinux这块,我只有一次在自定义端口的时候遇到过,后面会细说。
3.3 场景三:端口被改动
很多人为了提高“安全性”会把SSH端口改掉,比如改成2200、2222,结果自己忘了或者记错了。Xshell里填的还是22,那肯定变成connection failed。
排查方法:
cat /etc/ssh/sshd_config | grep -i port如果看到“Port 2222”,那你用Xshell连接的时候,端口必须填2222。如果你确定要用22端口,就把它改回来:
sed -i 's/^#Port 22/Port 22/' /etc/ssh/sshd_config # 或者手动编辑文件,确保只有一行 Port 22 systemctl restart sshd但这里有个坑:如果改了端口,SELinux又不认,启动sshd会报错。这时需要用semanage把这个新端口加到ssh_port_t的类型里:
semanage port -a -t ssh_port_t -p tcp 2222否则sshd虽然能起来,但连接依然会被SELinux拦截,表现还是“connection failed”。这个坑我当时排查了大半天,后来靠ausearch -m avc看SELinux拒绝日志才定位。
3.4 场景四:IP地址冲突或掩码问题
这个相对隐蔽。如果你的远程主机IP和局域网内另一台主机IP冲突了,当你的ping请求发过去时,可能那台冲突主机会响应,而SSH端口却是由正确主机提供的,但TCP回包的路由却走错了,导致连接失败。
排查方法:
ip addr show确认远程主机IP有没有冲突,可以看有没有重复的IP,或者在路由器/交换机上查ARP表。更简单的测试:在远程主机上抓包看看有没有来自你本地IP的TCP SYN到达:
tcpdump -i eth0 port 22 -n在本地用Xshell连接一次,然后看远程主机的tcpdump输出。如果完全没有SYN包,说明包根本没到这台主机,可能有其他设备把流量抢走了。如果能看到SYN包但客户端还是报connection failed,那就是回包路径有问题。
掩码错误的情况也遇到过:远程主机网络掩码配错,导致它认为你的IP不在同一网段,回包走网关绕路,绕到某个黑洞里。检查掩码:
ip addr show | grep inet对比一下本地的IP和掩码,确认同网段。如果你和远程主机在同一个vlan,网关配置错误一般不会影响ping,但会影响TCP的往返,因为TCP比ICMP复杂,更容易受路径问题影响。
4. 常见问题速查表
把上面所有情况整理成一个速查表,方便你以后直接对号入座。表格里的排查命令和解决方案都是实际验证过有效的。
| 现象 | 可能原因 | 快速验证命令 | 解决办法 |
|---|---|---|---|
| ping通,Xshell报connection failed | sshd服务未运行 | systemctl status sshd | systemctl start sshd && systemctl enable sshd |
| ping通,Xshell报connection failed | 防火墙拦截22端口 | firewall-cmd --state/iptables -L -n | 放行22/tcp,或修改安全组规则 |
| ping通,Xshell报connection failed | sshd监听在127.0.0.1 | `ss -tlnp | grep sshd` |
| ping通,Xshell报connection failed | 端口不是22 | grep -i port /etc/ssh/sshd_config | 用正确端口连接,或改回22 |
| ping通,Xshell报connection failed | SELinux拦截自定义端口 | getenforce+ausearch -m avc | semanage port -a -t ssh_port_t -p tcp <端口> |
| ping通,Xshell报connection failed | 连接数达到上限 | `journalctl -u sshd | grep maximum` |
| ping通,Xshell报connection failed | IP冲突 | tcpdump -i eth0 port 22 | 解决IP冲突,绑定静态IP |
| ping通,Xshell报connection failed | 回包路由问题 | ip route get <本地IP> | 修正路由或掩码 |
| ping通,Xshell报connection failed | 本地出方向防火墙禁止TCP | telnet <目标IP> 22 | 检查本地防火墙/杀毒软件 |
这个表里的每一行,我基本都真真切切遇到过。其中端口和防火墙占比最高,建议先从这两行开始查。
5. 我踩过的坑和最终心得
5.1 踩坑记录
第一个坑:检查防火墙时没分清楚firewalld和iptables。某个系统里firewalld关了,但iptables还有残留规则,同样能挡住22端口。我那时候看到firewalld是inactive,就判定防火墙没问题,结果折腾了半小时,一查iptables才发现还有一条DROP规则。后来我养成习惯:不管用哪套管理工具,都直接iptables -L -n看底层实际规则。
第二个坑:改了SSH端口后忘了SELinux。那次是给一台刚装好的系统换端口,sshd服务能起来,系统里的本地测试ss -tlnp也显示监听在新端口上,但外面就是连不上,Xshell也是“connection failed”。问题定位到最后,才发现是SELinux拦截。用ausearch -m avc -ts recent能搜到类似“denied { name_bind } for”的日志,结合semanage port -a -t ssh_port_t -p tcp 端口号解决。
第三个坑:过于相信ping。有一次处理某跨平台系统的远程连接故障,远程主机ping通得很流畅,但反复报错connection failed。我在网络层排查来排查去,最后发现是云平台安全组把22端口漏了。ICMP和TCP本身就是不同的协议,安全组可以只放行ICMP不放行TCP,这种配置IT管理的时候经常发生。从那以后我再去排类似故障,第一步就改成用telnet <IP> 22或nc -vz <IP> 22直接测端口,不再用ping做唯一判断依据。
5.2 最终心得
根据我多年的远程维护经验,处理“Xshell能ping通但连接失败”这类问题,最重要的不是背命令,而是建立一条完整的排查链路。我自己的固定顺序是:先测端口通不通,再看服务监听状态,接着看防火墙,然后看SELinux,再考虑连接数和网络冲突。每一步花不了多少时间,但能避免在错误的方向上越走越远。
另外,建议你动手排查前先记录下来当前的改动,尤其是防火墙和SSH配置。能通过直接控制台或带外管理的时候别偷懒,先把环境摸清楚,再动手改。如果你在远程维护一堆机器,强烈建议把SSH服务设为开机自启,并把防火墙规则变更通过配置管理工具统一推送,不要每台机器手动敲iptables。
Xshell报“connection failed”只是一个开始,排查思路本身比这个报错更有价值。你能把网络层和应用层分清楚,很多看似奇怪的问题其实都能迎刃而解。下次再遇到这类问题,希望这篇文章能帮你少走几步弯路,快速回到正轨。