根本原因是composer调用git clone时复用系统git的ssh配置,若composer.json中url未使用~/.ssh/config定义的host别名(如git@github-personal),则无法匹配对应密钥;需确保url显式使用别名、git版本≥2.10、ssh配置完整且权限正确,并通过composer update -vvv验证实际执行的git命令。

Composer install 时 SSH 连接 GitHub 失败,报错 Permission denied (publickey)
根本原因不是 Composer 本身的问题,而是它调用 git clone 时复用了系统 Git 的 SSH 配置。如果你为不同 Git 服务(比如 GitHub、GitLab、公司私有仓库)配置了多个 SSH 密钥,并用 Host 别名区分(如 github-work、github-personal),但 composer.json 里写的却是 git@github.com:xxx/yyy.git,那 Git 就不会走你定义的别名,直接连默认的 github.com —— 而这个 Host 往往没配密钥或配错了密钥。
实操建议:
- 检查
composer.json中所有repositories的 URL:必须显式使用你定义的 Host 别名,比如把git@github.com:user/repo改成git@github-personal:user/repo - 确认
~/.ssh/config中对应 Host 的配置完整,包含HostName github.com、User git、IdentityFile ~/.ssh/id_rsa_personal和IdentitiesOnly yes - 运行
ssh -T git@github-personal测试是否能成功认证;失败则说明 SSH 配置未生效或密钥权限不对(chmod 600 ~/.ssh/id_rsa_*)
为什么 composer update 有时走 HTTPS、有时走 SSH?
Composer 默认优先用 SSH(如果 URL 是 git@... 格式),但一旦某次 clone 失败,它可能缓存失败状态,后续尝试降级用 HTTPS —— 这会导致你误以为“问题修好了”,其实只是绕过了 SSH 鉴权。更麻烦的是,HTTPS 方式不走 SSH config,也就完全无视你的多密钥配置。
实操建议:
- 强制让 Composer 始终走 SSH:在
composer.json的repositories中确保 URL 以git@开头,且不含https://或git:// - 清掉 Composer 的 Git 缓存:删掉
vendor/composer/installed.json和项目根目录下的.composer/cache/vcs/(路径依 Composer 版本略有差异) - 加
-vvv参数跑composer update -vvv,观察日志里实际执行的git clone命令,确认 URL 和协议是否符合预期
git@xxx 别名在 Composer 中被忽略的隐藏原因
不是所有 Git 客户端版本都支持 SSH config 的 Host 别名透传。低版本 Git(git version )在某些场景下会跳过 <code>~/.ssh/config 解析,尤其当 Git 被 PHP 的 proc_open() 调用时(Composer 就是这么调的)。这时即使你配置正确,也白搭。
实操建议:
- 运行
git --version确认本地 Git 版本 ≥ 2.10;低于则升级,Ubuntu 可用apt install git(注意源),macOS 推荐brew install git - 临时验证:手动执行
git clone git@github-personal:user/repo,看是否成功;若成功但 Composer 失败,大概率是 PHP 进程环境变量隔离导致读不到~/.ssh/config - 给 PHP 显式指定 SSH 配置路径:在
php.ini或脚本开头加putenv('GIT_SSH_COMMAND=ssh -F /home/you/.ssh/config');(路径按实际替换)
私有包依赖中嵌套的 Git URL 不受当前项目 SSH 配置控制
如果你的 A 包依赖 B 包,而 B 的 composer.json 里写了 git@github.com:xxx,那么 Composer 安装 B 时会再次触发 Git clone —— 此时用的仍是全局 SSH 配置,而不是你为 A 包特意设的别名。这就造成“主项目能装,子依赖装不了”的诡异现象。
实操建议:
- 不要依赖别人写死的
github.com地址;要么 fork 后改自己仓库的 URL,要么用repositories的package类型硬编码覆盖 - 在根项目
composer.json中用repositories显式声明所有私有包,URL 写成你可控的别名形式,例如:"repositories": [ { "type": "vcs", "url": "git@github-work:myorg/internal-lib.git" } ] - 对已发布的私有包,考虑统一迁移到 HTTPS + Personal Access Token 方式,避免 SSH 密钥管理复杂度(Token 可通过
COMPOSER_AUTH环境变量注入)
真正卡住人的往往不是密钥本身,而是 Git 到底读了哪份 ~/.ssh/config、PHP 进程有没有继承 shell 的环境、以及 Composer 在哪一层 fallback 到了 HTTPS。调试时盯住 composer update -vvv 输出里的每一条 git clone 命令,比反复改密钥更有效。











