phpseclib/Flysystem连接SFTP报Expected SSH_FXP_VERSION错误求助
SFTP连接报
Expected SSH_FXP_VERSION/Expected NET_SFTP_VERSION故障处理 问题基础信息
- 业务场景:基于Symfony 5开发服务器解析、托管站点识别功能,使用底层依赖phpseclib的Flysystem库实现SFTP文件操作
- 故障现象:已接入的29台服务器中28台连接运行正常,仅单台连接失败
- Flysystem调用时报错:
User notice: Expected SSH_FXP_VERSION - 切换为直接调用phpseclib连接后报错:
Expected NET_SFTP_VERSION. Got packet type:
- Flysystem调用时报错:
- 对照验证:同账号使用FileZilla客户端可正常连接该服务器,支持目录列举、路径查询等常规操作,连接日志如下:
2022-06-30 14:29:35 2308 1 Statut : Connexion à sftp.sd3.gpaas.net… 2022-06-30 14:29:35 2308 1 Réponse : fzSftp started, protocol_version=11 2022-06-30 14:29:35 2308 1 Commande : open "XXX@sftp.sd3.gpaas.net" 22 2022-06-30 14:29:35 2308 1 Statut : Using username "XXX". 2022-06-30 14:29:35 2308 1 Commande : Pass: ************************ 2022-06-30 14:29:35 2308 1 Statut : Connected to sftp.sd3.gpaas.net 2022-06-30 14:29:49 2308 1 Statut : Récupération du contenu du dossier… 2022-06-30 14:29:49 2308 1 Commande : pwd 2022-06-30 14:29:49 2308 1 Réponse : Current directory is: "/" 2022-06-30 14:29:49 2308 1 Commande : ls 2022-06-30 14:29:49 2308 1 Statut : Listing directory / 2022-06-30 14:29:49 2308 1 Statut : Contenu du dossier « / » affiché avec succès
- 业务侧原始调用代码:
$filesystem = new Filesystem(new SftpAdapter( new SftpConnectionProvider( $this->server->getHost(), $this->server->getUsername(), $this->server->getPassword(), ), '/', // root path PortableVisibilityConverter::fromArray([ 'file' => [ 'public' => 0640, 'private' => 0604, ], 'dir' => [ 'public' => 0740, 'private' => 7604, ], ]) ));
可能诱因
- 连接建立后存在前置非协议报文:云服务商托管SFTP网关、存储网关类服务,通常会在SSH连接建立、SFTP子系统初始化前,返回欢迎公告、合规提示类纯文本内容。phpseclib默认逻辑会直接将读取到的第一段内容判定为SFTP协议版本包,无法识别时就会抛出该类错误。FileZilla等桌面客户端内置了非协议包过滤逻辑,因此不受影响,这是该类故障的最高频诱因。
- SFTP子系统协商参数不匹配:phpseclib默认的SFTP子系统请求路径、协议版本协商范围与目标服务器要求不匹配,比如部分服务器要求显式指定sftp-server执行路径、仅支持特定范围的SFTP协议版本,协商失败时服务器返回的错误文本会被识别为非法协议包。
- 连接超时或链路拦截:目标服务器配置了SSH连接频控、会话建立延迟规则,phpseclib默认的连接、读取超时时间过短,未等到服务器返回SFTP版本包就开始读取缓冲区,拿到空内容或WAF/网关返回的拦截提示报文。
- 认证后置校验拦截:部分服务器在密码校验通过后,还会触发IP白名单二次校验、账号权限预检查流程,校验未通过时不会直接断开连接,而是返回纯文本错误提示,被phpseclib识别为非法协议包。
排查与解决方法
- 第一步先开启phpseclib全流程日志,确认缓冲区收到的实际报文内容,精准定位根因:
// 开启NET_SSH2完整报文日志 define('NET_SSH2_LOGGING', 2); $ssh = new \phpseclib\Net\SSH2($host, 22); $ssh->login($username, $password); // 打印全流程交互原始内容,确认非版本包的实际内容 echo '<pre>' . $ssh->getLog() . '</pre>';
- 针对前置非协议报文场景:升级phpseclib到最新稳定版本,新版已内置前置非SFTP报文过滤逻辑;使用旧版本的场景可以在SFTP子系统初始化前,手动读取缓冲区所有内容丢弃后,再发起SFTP初始化请求。
- 针对子系统协商不匹配场景:显式指定SFTP子系统路径,手动限定协商的SFTP协议版本范围适配服务器要求,参考实现:
$ssh = new \phpseclib\Net\SSH2($host, 22, 10); // 第三个参数传超时时间,单位秒 if (!$ssh->login($username, $password)) { throw new \RuntimeException('SSH认证失败'); } $sftp = new \phpseclib\Net\SFTP($ssh); // 若默认初始化失败,尝试手动指定sftp-server路径发起子系统请求 if (!$sftp->initSFTP()) { $ssh->exec('/usr/lib/openssh/sftp-server'); // 替换为服务器实际sftp-server路径 $sftp->initSFTP(); }
- 针对超时或链路拦截场景:将phpseclib连接超时、读取超时调整为10秒以上,确认业务服务器IP在目标SFTP服务器的白名单中,排查服务器侧是否存在频控、WAF拦截规则。
- 保留Flysystem调用的场景:可以自定义实现SftpConnectionProvider,在连接建立逻辑中注入前置缓冲清理、超时调整、子系统适配逻辑,不需要修改上层业务的Filesystem操作代码。
内容的提问来源于stack exchange,提问作者Paul
相关产品推荐
相关产品推荐

