正常输出需同时满足:default_cert_file与ini_cafile指向同一存在、非空、2023年后更新的PEM文件,路径无中文空格符号链接且权限正确;二者为空或不一致均说明证书配置失效。

php -r "print_r(openssl_get_cert_locations());" 输出什么才正常
这行命令不是摆设,是唯一能确认 PHP 实际证书路径的手段。输出里必须同时满足:
• default_cert_file 和 ini_cafile 指向同一个存在的、非空的 PEM 文件
• 该文件最后修改时间不早于 2023 年(旧证书已大量失效)
• 路径不含中文、空格、符号链接,且当前用户有读权限
• 若 ini_cafile 为空,说明 php.ini 没生效;若两者不一致,说明配置被覆盖或加载了错误的 php.ini
openssl.cafile 和 curl.cainfo 必须配对且路径完全一致
Composer 同时用到 OpenSSL 和 cURL 两套底层,它们各读一个配置项:
• openssl.cafile 控制 file_get_contents()、stream_context_create() 等函数
• curl.cainfo 控制 curl_exec(),也就是 Composer 默认 HTTP 客户端的底层
只改其中一个?大概率仍报 cURL error 60 或 SSL3_GET_SERVER_CERTIFICATE
路径写错一字符(比如漏 .pem、Windows 下单反斜杠没转义)、用相对路径、引号不闭合——全都会让配置静默失效
正确写法示例(Linux):
openssl.cafile="/usr/local/etc/php/cacert.pem"<br>curl.cainfo="/usr/local/etc/php/cacert.pem"
Windows 下必须用正斜杠或双反斜杠:openssl.cafile="C:/php/extras/ssl/cacert.pem"
composer config --global cafile 是无效操作
很多人试过 composer config -g cafile "/path/to/cacert.pem",但依然报错——因为这个配置只影响 Composer 自封装的 HTTP 客户端(如使用 php-http 驱动时),而触发 cURL error 60 的是 PHP 原生 cURL 扩展,它根本不读这个值。
真正起作用的只有 php.ini 里的 curl.cainfo 和 openssl.cafile
验证是否生效的最快方式:php -r "$ch = curl_init('https://packagist.org'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); echo curl_exec($ch) ?: 'fail';"
返回非 fail 才算通
镜像源配置漏 type 或结尾 / 会导致静默回退官方源
你以为在连阿里云,其实 Composer 悄悄 fallback 到 packagist.org 并卡在它的证书校验上。
错误写法:composer config -g repo.packagist https://mirrors.aliyun.com/composer(缺 type,缺结尾 /)
正确写法:composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
验证命令:composer config -g repo.packagist,输出必须是完整 JSON:{"type": "composer", "url": "https://mirrors.aliyun.com/composer/"}
项目级更稳妥:composer config repo.packagist composer https://mirrors.aliyun.com/composer/,会直接写进 composer.json 的 repositories 字段
证书问题最常被忽略的点:CLI 和 Web SAPI 加载的是不同 php.ini,而 Composer 只走 CLI 版本;改完配置后没重启终端,php -v 还在读旧配置;杀毒软件或企业防火墙拦截了 cacert.pem 文件读取——这些都比证书内容本身更容易出问题。











