PHP通过exec执行SSH命令时的SSH密钥认证问题排查
我之前也碰到过一模一样的问题——手动在shell里能免密SSH到目标服务器,但PHP CLI脚本执行就卡壳,还出现认证失败的日志。核心原因其实是PHP进程的运行环境和你手动打开shell时的环境不一样,导致它找不到你的SSH密钥,或者用了错误的用户身份。下面是几个亲测有效的解决方案:
1. 直接在SSH命令里指定密钥路径
PHP CLI可能没有自动加载你默认的SSH密钥(比如~/.ssh/id_rsa),最直接的办法就是在ssh命令里用-i参数明确指定密钥文件的绝对路径。修改你的$localCmd变量:
$localCmd = 'ssh -i /home/你的本地用户名/.ssh/id_rsa -o StrictHostKeyChecking=no myuser@uk-staging.internal.myhost.com "mkdir -p /home/deploy/backups/etl; rm -f /home/deploy/backups/etl/*.gz; rm -f /home/deploy/backups/etl/*.csv; sudo chown myhost:mysql /home/deploy/backups/etl"';
记得把/home/你的本地用户名/.ssh/id_rsa替换成你实际的密钥路径,比如你手动登录shell的用户是john,那路径就是/home/john/.ssh/id_rsa。
2. 确认PHP CLI的运行用户身份
有时候你以为PHP用的是当前用户,但实际可能不是。在你的serverCmd函数开头加一行代码,看看PHP到底以哪个用户运行:
private function serverCmd($server, $cmd, $exec = false) { // 新增:打印PHP运行的用户 echo "PHP运行用户:" . exec('whoami') . "\n"; $line = system("whoami"); // ... 其余代码不变 }
如果输出的用户和你手动登录shell的用户不一样(比如你用普通用户john,但PHP用root或者www-data运行),那肯定会找不到你的密钥。这时候要么切换到正确的用户运行PHP脚本(比如su john -c "php your-script.php"),要么给那个用户配置免密登录目标服务器(生成新的SSH密钥,把公钥传到目标服务器的~/.ssh/authorized_keys里)。
3. 确保SSH密钥和目录的权限符合要求
SSH对密钥文件的权限要求非常严格,只要权限不对就会拒绝使用。执行下面的命令修复权限:
# 设置.ssh目录权限为700(只有所有者能访问) chmod 700 /home/你的本地用户名/.ssh # 设置密钥文件权限为600(只有所有者可读可写) chmod 600 /home/你的本地用户名/.ssh/id_rsa # 确保密钥文件的所有者是PHP运行的用户 chown 你的用户名:你的用户名 /home/你的本地用户名/.ssh/id_rsa
如果PHP运行的用户不是密钥的所有者,就算路径对了也读不到密钥,这一步很容易忽略!
4. 开启SSH调试模式定位问题
如果上面的办法都没用,就给SSH命令加-v参数开启调试模式,看看具体哪一步出错了。修改代码捕获详细输出:
// 把原来的system调用改成exec,同时捕获错误输出 $output = array(); exec($localCmd . ' -v 2>&1', $output); print_r($output);
这样你就能看到SSH的完整交互日志,比如是不是找不到密钥文件、密钥被目标服务器拒绝、或者有其他认证方式的问题,根据日志就能精准定位。
5. 配置SSH Config文件(可选但推荐)
为了避免每次都手动指定密钥,你可以在~/.ssh/config里添加目标服务器的配置,让SSH自动用正确的参数连接:
Host uk-staging HostName uk-staging.internal.myhost.com User myuser IdentityFile /home/你的本地用户名/.ssh/id_rsa StrictHostKeyChecking no
然后修改你的$localCmd为:
$localCmd = 'ssh uk-staging "mkdir -p /home/deploy/backups/etl; rm -f /home/deploy/backups/etl/*.gz; rm -f /home/deploy/backups/etl/*.csv; sudo chown myhost:mysql /home/deploy/backups/etl"';
这样不管是手动shell还是PHP执行,都会用同一个配置,减少出错的概率。
最常见的问题就是PHP运行用户和你手动shell的用户不一致,或者密钥路径/权限不对。先排查用户身份,再指定密钥路径,基本就能解决问题。
内容的提问来源于stack exchange,提问作者user794846

