最稳妥的选择是phpseclib,因其纯PHP实现、无扩展依赖、兼容性强;而ext-ssh2易受环境限制、算法支持滞后且大文件上传内存暴涨。

直接用 phpseclib 是目前最稳妥的选择——它纯 PHP 实现,不依赖系统 ssh2 扩展,避免了扩展缺失、版本冲突、权限限制等常见翻车点。
为什么不用 php_ssh2 扩展
虽然 ssh2_connect() 看起来原生、轻量,但实际落地时容易卡在三处:
-
ssh2扩展在很多共享主机或容器环境里默认没装,装还要 sudo 权限,CI/CD 流水线常失败 - 扩展对 PHP 8.2+ 的某些加密算法支持滞后,遇到
Unable to negotiate with ... no matching key exchange method错误得手动降级 OpenSSH 或打补丁 -
ssh2_scp_send()不支持断点续传、无进度回调、上传大文件时内存暴涨(它会把整个文件读进内存再发)
composer require phpseclib/phpseclib:~3.0 的实操要点
别只敲命令完事。3.x 版本和旧版 2.x 不兼容,且默认启用严格类型和现代加密套件,以下参数必须显式确认:
- 安装命令要带版本约束:
composer require phpseclib/phpseclib:~3.0,避免意外升级到未适配的 4.x(尚未稳定) - 如果远程 SFTP 服务器很老(比如 CentOS 6 上的 OpenSSH 5.3),可能不支持
chacha20-poly1305@openssh.com,需手动禁用:$sftp->disableAlgorithm('encryption', 'chacha20-poly1305@openssh.com') - 连接后建议立刻调用
$sftp->setTimeout(30),否则网络抖动时会卡死 60 秒以上才超时
put() 和 get() 的坑:路径、模式、权限
$sftp->put() 默认是覆盖写入,且不校验本地文件是否存在;$sftp->get() 默认返回文件内容字符串,不是写入本地磁盘——这两点最容易导致静默失败:
立即学习“PHP免费学习笔记(深入)”;
- 上传前务必检查本地文件:
if (!is_readable($localPath)) { throw new RuntimeException("Local file missing: $localPath"); } - 上传用
\phpseclib3\Net\SFTP::SOURCE_LOCAL_FILE模式,避免把整个文件 load 到内存:$sftp->put($remotePath, $localPath, \phpseclib3\Net\SFTP::SOURCE_LOCAL_FILE) - 下载大文件别用
get()直接返回内容,改用流式写入:file_put_contents($localPath, $sftp->get($remotePath))—— 虽然简单,但千万注意$sftp->get()返回的是二进制字符串,不能用fopen()+fwrite()分块,它本身已是一次性读完 - 远程路径必须是绝对路径,
$sftp->put('/home/user/file.txt', ...)有效,$sftp->put('file.txt', ...)会写到用户 home 目录下,位置不可控
私钥登录比密码更可靠,但格式必须对
用密码登录看似简单,但生产环境基本被禁用;切到私钥时,90% 的问题出在密钥格式上:
- OpenSSH 格式(
-----BEGIN OPENSSH PRIVATE KEY-----)PHP 3.x 原生支持,无需转换 - PEM 格式(
-----BEGIN RSA PRIVATE KEY-----)也支持,但若含密码,必须传入第 3 个参数:$sftp->login($user, $key, $passphrase) - 绝对不要用 PuTTY 的
.ppk文件——phpseclib不识别,得先用 PuTTYgen 转成 OpenSSH 或 PEM - 私钥文件路径必须可被 PHP 进程读取(
www-data用户能 access),且权限不能太宽松(如0644会被库拒绝加载)
真正麻烦的从来不是连上或传完,而是连接中断后重试逻辑、文件完整性校验(比如传完比对 remote md5)、以及并发上传时的目录锁——这些得自己补,phpseclib 只管协议层。



















