要稳定执行ssh命令,必须正确处理流阻塞、显式获取stderr、严格校验私钥权限,并优先手动验证系统层连接;若无法启用ext-ssh2,proc_open是可控兜底方案。

直接上手学 SSH 编程,别从“理论”开始。PHP 没有原生 SSH 支持,所有可用方案都依赖外部扩展或系统命令,选错路径会卡在权限、超时、流阻塞或私钥拒绝上,根本跑不出第一行输出。先明确:你要的是「能稳定执行命令」,不是「学会所有 API」。
ssh2_exec() 返回空或挂死?先检查 stream_set_blocking()
ssh2_exec() 返回的是一个流资源,不是字符串。不设阻塞,stream_get_contents() 可能立刻返回空,因为子进程还没写完;设了但没 fclose(),下次调用会卡住。
- 必须在
ssh2_exec()后立即调用stream_set_blocking($stream, true) - 读完后必须
fclose($stream),否则句柄泄漏,连接数耗尽 - 不要用
exec()或shell_exec()直接包ssh命令——它不继承 PHP 的 SSH 连接上下文,每次都是新连接,且无法可靠捕获 stderr
示例关键片段:
if ($stream = ssh2_exec($conn, 'ls -l /tmp')) {
stream_set_blocking($stream, true);
$output = stream_get_contents($stream);
$err = stream_get_contents($stream, -1, 0); // 实际需另开 stderr 流,见下条
fclose($stream);
}
stderr 读不到?得用 ssh2_fetch_stream()
ssh2_exec() 默认只打开 stdout,stderr 被丢弃。想捕获错误(比如 “Permission denied” 或 “command not found”),必须显式提取:
- 调用
$stdout = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO) - 调用
$stderr = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR) - 分别对两个流设阻塞并读取
- 注意:
SSH2_STREAM_STDIO和SSH2_STREAM_STDERR是常量,不是字符串
漏掉这步,远程命令报错你也只看到空白输出,排查无从下手。
私钥登录失败?90% 是权限或归属问题
用 ssh2_auth_pubkey_file() 时,OpenSSH 服务端会严格校验客户端私钥文件:
- 私钥文件(如
/var/www/.ssh/id_rsa)必须属主为 PHP 进程用户(如www-data),不能是root - 权限必须是
600(chmod 600),连 group 可读都不行 -
.ssh目录权限必须是700 - 公钥必须完整追加进远程用户的
~/.ssh/authorized_keys,末尾不能有多余空格或换行
用 sudo -u www-data ssh -i /var/www/.ssh/id_rsa user@host date 手动测试是否通——不通就别进 PHP,先解决系统层权限。
不想装扩展?proc_open + ssh 是最可控的兜底方案
如果你无法启用 ext-ssh2(比如共享主机、容器无 root 权限),又不愿引入 phpseclib(纯 PHP 实现,慢且部分新算法支持弱),proc_open() 是唯一不依赖扩展、完全由你掌控的方式:
- 构造命令时必须带
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null,否则首次连接会卡在交互确认 - 私钥路径用
-i /abs/path/to/key,绝对路径,不能用~ -
$descriptorspec至少定义 stdin/stdout/stderr 三路管道 - 一定要用
proc_close()或proc_terminate()清理子进程,否则大量并发时会耗尽系统进程数
最简可行命令模板:
$cmd = 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /var/www/.ssh/id_rsa user@host "df -h"';
$proc = proc_open($cmd, $descriptorspec, $pipes);
if (is_resource($proc)) {
$output = stream_get_contents($pipes[1]);
$error = stream_get_contents($pipes[2]);
fclose($pipes[1]); fclose($pipes[2]);
proc_close($proc);
}
真正的难点从来不在“怎么写”,而在“为什么没输出”——权限、流控制、错误通道、首次连接信任机制,每一处都可能静默失败。把日志打开,把手动命令跑通,再套进 PHP,比对着文档抄代码快十倍。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











