ssh2-sftp-client连接SFTP报ERR_GENERIC_CLIENT握手超时问题
问题定位
从调试日志看,错误根因是SSH握手阶段超时,命令行可以正常连接但代码连接失败,按出现概率排序,触发原因如下:
- ssh2-sftp-client底层依赖ssh2库,默认握手超时时间仅10秒,跨网段、企业内网SFTP设备做了流量审计、密钥交换计算耗时较长时,10秒不足以完成握手流程,而命令行sftp客户端默认超时时间远大于该值,因此可以正常连接。
- 现有代码存在两个隐性bug:一是
Promise.all的回调中没有给sftp.put加await,上传任务未执行完就会进入后续流程;二是没有在finally块主动关闭SFTP连接,长期运行会出现句柄泄漏。 - 部分老旧内网SFTP服务器仅支持旧版密钥交换、加密算法,ssh2库出于安全考虑默认禁用了这些旧算法,会导致算法协商阶段卡住直到超时。
- 若代码运行在容器/云主机环境,需确认Node进程的出网规则,部分环境会限制非系统命令进程的22端口访问,出现命令行可连但代码连不上的情况。
修复方案
按以下顺序调整即可:
- 调整连接配置,延长握手超时时间,添加兼容算法配置
给connect参数增加readyTimeout字段(单位毫秒,建议先设为30000,网络差可调整为60000),同时添加兼容新旧SFTP服务端的算法列表,避免协商卡住,注意端口要强制转数字类型,避免配置读成字符串导致连接异常:
await sftp.connect({ host: SFTP.HOST, port: Number(SFTP.PORT), username: SFTP.USERNAME, password: SFTP.PASSWORD, readyTimeout: 30000, algorithms: { kex: [ 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group14-sha256', 'diffie-hellman-group15-sha512', 'diffie-hellman-group16-sha512', 'diffie-hellman-group14-sha1' ], cipher: [ 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm', 'aes128-gcm@openssh.com', 'aes256-gcm', 'aes256-gcm@openssh.com', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc' ] } })
- 修复代码逻辑问题,移除冗余的
.then链,统一用async/await,给上传操作加await,最后主动关闭连接
修正后的完整代码参考:
import Client from 'ssh2-sftp-client'; import config from 'config'; import logger from './logger'; const upload = async csvFiles => { const sftp = new Client(); try { const SFTP = config.get('SFTP'); const remotePath = SFTP.REMOTE; await sftp.connect({ host: SFTP.HOST, port: Number(SFTP.PORT), username: SFTP.USERNAME, password: SFTP.PASSWORD, readyTimeout: 30000, algorithms: { kex: [ 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group14-sha256', 'diffie-hellman-group15-sha512', 'diffie-hellman-group16-sha512', 'diffie-hellman-group14-sha1' ], cipher: [ 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm', 'aes128-gcm@openssh.com', 'aes256-gcm', 'aes256-gcm@openssh.com', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc' ] } }); await Promise.all( csvFiles.map(file => { const fileName = file.split(/[\\/]/).pop(); return sftp.put(file, `${remotePath.replace(/\/$/, '')}/${fileName}`); }) ); logger.log({ level: 'info', message: 'sftp-success' }); } catch (err) { logger.log({ level: 'error', message: 'sftp-error', err }); } finally { await sftp.end(); } }; module.exports = upload;
- 若调整后仍超时,在代码运行的环境下,用和启动Node服务相同的系统用户执行
sftp <用户名>@<SFTP地址> -p <端口>验证连通性,同时执行nc -zv <SFTP地址> <端口>确认端口可达,排除系统级网络策略、权限限制问题。
内容的提问来源于stack exchange,提问作者William Mandang
相关产品推荐
相关产品推荐

