本质是PHP cURL扩展无法验证HTTPS证书链,主因是CA证书过旧或代理劫持后根证书未导入PHP信任库;需更新系统/PHP的CA证书或手动配置openssl.cafile与curl.cainfo指向有效证书文件。

Composer 在执行 composer install 或 composer update 时卡在 SSL 握手失败(如 SSL handshake failed、Unable to connect to repo.packagist.org),本质不是 Composer 本身的问题,而是它依赖的 PHP cURL 扩展无法验证 HTTPS 连接背后的证书链——通常因为系统或 PHP 内置的 CA 证书过旧,或被中间代理(如公司防火墙、Charles/Fiddler)劫持后未正确安装根证书。
为什么 Composer 会报 SSL handshake failed?
Composer 默认通过 HTTPS 访问 Packagist(https://repo.packagist.org),而 PHP 的 cURL 必须能验证该域名的证书是否由可信 CA 签发。常见触发点:
- PHP 使用的 CA 证书包(如
curl-ca-bundle.crt)停留在 2020 年前,不包含 Let’s Encrypt 新的 ISRG Root X1 或 R3 中间证书; - 你在用代理抓包(如 Charles),但没把代理的根证书导入到 PHP 的信任库中;
- 服务器时间严重偏差(>5 分钟),导致证书“尚未生效”或“已过期”被拒绝;
- 某些国产 Linux 发行版(如 CentOS 7)自带的
ca-certificates包长期未更新,缺少新 CA 根证书。
检查当前 PHP 使用的 CA 证书路径
运行以下命令确认 PHP 实际加载的证书文件位置:
php -r "print_r(openssl_get_cert_locations());"
重点关注 default_cert_file 和 capath 字段。常见路径包括:
-
/etc/ssl/certs/ca-certificates.crt(Debian/Ubuntu) -
/etc/pki/tls/certs/ca-bundle.crt(CentOS/RHEL) -
C:\xampp\php\extras\ssl\cacert.pem(Windows XAMPP)
如果路径指向一个空文件、404 文件,或最后修改时间早于 2022 年,基本可判定为证书过期。
快速修复:更新系统或 PHP 的 CA 证书
不要禁用 SSL 验证(composer config -g secure-http false)——这等于裸奔,会暴露你所有依赖下载过程。
- Linux(Debian/Ubuntu):
sudo apt update && sudo apt install --reinstall ca-certificates - Linux(CentOS/RHEL 7+):
sudo yum update ca-certificates或sudo dnf update ca-certificates - macOS(Homebrew PHP):
brew reinstall ca-certificates,再执行brew link --force ca-certificates - Windows(XAMPP/WAMP):从 https://www.php.cn/link/5fe4dadcdb001d8566cd20e6d8a20251 下载最新
cacert.pem,替换 PHP 目录下的旧文件,并在php.ini中显式指定:openssl.cafile="/path/to/cacert.pem"curl.cainfo="/path/to/cacert.pem"
改完记得重启 Web 服务或 CLI 的 PHP 进程(php -v 看是否生效)。
代理场景下必须额外做的一步
如果你在用 Charles / Fiddler / mitmproxy 抓包,它们会动态签发证书。PHP 不会自动信任这些代理根证书,必须手动导入:
- 在 Charles 中导出根证书(Help → SSL Proxying → Save Charles Root Certificate…),格式选 PEM;
- 将导出的
charles-ssl-proxying-certificate.pem合并进你的 CA 证书文件(用cat charles-*.pem >> /path/to/cacert.pem); - 或者更稳妥:用
openssl x509 -in charles-*.pem -text -noout确认是 PEM 格式,再单独配置 PHP:openssl.cafile="/path/to/charles-ssl-proxying-certificate.pem"
注意:不要只改 curl.cainfo 而漏掉 openssl.cafile,Composer 内部同时使用两者。
真正麻烦的从来不是“怎么换证书”,而是“换完之后有没有被所有组件看到”。PHP CLI、Web 服务器里的 PHP、甚至 Docker 容器里的 PHP,CA 证书路径可能各不相同——每次换环境都得重新核对 openssl_get_cert_locations() 输出。











