编程语言
cURL error 60:PHP 证书校验失败的原因与修法
cURL error 60: SSL certificate problem: unable to get local issuer certificatecURL error 60: SSL certificate problem 的意思是:PHP 的 curl 扩展连上了服务器,但无法验证对方的证书链。 在 Windows 和自己编译的 PHP 上,通常是 PHP 没配置 CA 证书包;在 Linux 服务器上,更多是对方服务器缺中间证书,或者对方用的是 PHP 不认识的私有根证书。
快速修复
多数情况给 php.ini 配上 CA 证书包就能解决。先在报错的同一个环境(网页还是命令行)里,查看 PHP 用的是哪个 CA 文件:
php --ini
php -i | grep -iE 'curl.cainfo|openssl.cafile|openssl.capath'
php -r 'print_r(openssl_get_cert_locations());'
如果 curl.cainfo 和 openssl.cafile 都是空的,默认文件也不存在,就在实际加载的 php.ini 里设置:
curl.cainfo = "/etc/ssl/certs/ca-certificates.crt"
openssl.cafile = "/etc/ssl/certs/ca-certificates.crt"
Windows 上填从 curl.se 下载的 cacert.pem 的路径。改完重启 PHP-FPM、Apache 或 IIS。如果证书包本来就没问题,用 SSL 检测工具检查对方服务器的证书链。
原因
Windows 和自编译 PHP 上最常见的是没配置 CA 证书包;Linux 服务器上更多是对方证书链不完整,或者对方用了私有根证书。
1. PHP 没有配置 CA 证书包
Windows 版 PHP、XAMPP、WampServer、phpStudy(小皮面板)以及很多自行编译的版本,自带的 libcurl 都没有可用的 CA 路径。
- 确认:
openssl_get_cert_locations()指向的文件不存在,curl.cainfo为空。 - 修复: 设置
curl.cainfo(curl 扩展使用)和openssl.cafile(file_get_contents('https://...')等流函数使用),都指向有效的证书包。
2. 对方服务器证书链不完整
如果只有某一个网站或接口报错、其他都正常,多半是对方没装中间证书。对接一些小型第三方接口时并不少见。
- 确认:
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts只列出一张证书。 - 修复: 需要对方服务器装上完整证书链,见缺少中间证书和证书链补全工具。可以把检测结果发给对方运维。
3. CA 证书包过旧
很久没更新的 cacert.pem 或者没打补丁的系统(如 CentOS 6/7)缺少较新的根证书,访问用新根证书的网站就会失败。
- 修复: 更新系统的
ca-certificates包,或重新下载cacert.pem。
4. 公司代理或内网 CA
做 HTTPS 解密的公司代理、内部接口用的根证书都不在 Mozilla 列表里。
- 修复: 把 PEM 格式的根证书追加到证书包副本里,让
curl.cainfo指向副本,或者按请求单独指定:
$ch = curl_init('https://intranet.example.com/api');
curl_setopt($ch, CURLOPT_CAINFO, '/etc/ssl/certs/company-bundle.pem');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$body = curl_exec($ch);
5. 改错了 php.ini
CLI 和 FPM 常用不同的文件,例如 Debian、Ubuntu 上分别是 /etc/php/8.3/cli/php.ini 和 /etc/php/8.3/fpm/php.ini。
- 修复: 以网页端
phpinfo()里的“Loaded Configuration File”为准修改。
各环境修法
每个环境都要先找到实际生效的 php.ini 或客户端选项,再让它指向可信的 CA 证书包。
宝塔面板: PHP 的配置文件在 /www/server/php/<版本号>/etc/php.ini,例如 PHP 8.1 是 /www/server/php/81/etc/php.ini。可以在面板“软件商店 → PHP → 设置 → 配置修改”里搜索 curl.cainfo,填入 /etc/pki/tls/certs/ca-bundle.crt(CentOS 系)或 /etc/ssl/certs/ca-certificates.crt(Debian/Ubuntu 系),保存后重启 PHP。
Debian / Ubuntu:
sudo apt-get install --reinstall ca-certificates
sudo systemctl restart php8.3-fpm # 换成你的 PHP 版本
CentOS / RHEL / Alibaba Cloud Linux: 证书包是 /etc/pki/tls/certs/ca-bundle.crt,私有根证书放进 /etc/pki/ca-trust/source/anchors/ 后执行 update-ca-trust。
Windows、XAMPP、WampServer、phpStudy: 下载 cacert.pem,在 Apache 实际使用的 php.ini 里设置两个参数,然后在控制面板里重启 Apache。
Docker: 确认镜像里装了 ca-certificates,私有根证书复制进镜像后再执行 update-ca-certificates。
框架: Guzzle、Laravel 用 'verify' => '/path/to/bundle.pem',Symfony HttpClient 用 cafile 选项。Composer 有自己的配置,见 Composer SSL operation failed。
WordPress: 核心的 HTTP 请求用的是 wp-includes/certificates/ 里自带的证书包,保持 WordPress 更新即可保持证书包最新;直接调用 curl 的插件则使用 PHP 的设置。
不要这样做
网上流传的几种绕法会关闭证书校验,或者引入来路不明的信任。
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false)。 这是网上转载最多的“解决方法”,它关闭了证书链校验,任何网络上的攻击者都能冒充接口服务器,支付回调、用户数据都可能被篡改。- 把
CURLOPT_SSL_VERIFYHOST设为 0。 关闭域名校验后,任意其他域名的有效证书都会被接受。 - Guzzle 或 Laravel 里写
'verify' => false。 效果相同,而且经常是“临时测一下”后被带进了生产环境。 - 使用来路不明的
cacert.pem。 只用 curl 项目官方发布的文件或系统包管理器提供的证书包。
常见问题
php.ini 改了还是报 60,为什么?
多半改错了文件。php --ini 显示的是命令行用的 php.ini,PHP-FPM 和 Apache 的 mod_php 通常加载另一份。在网页里输出 phpinfo(),看“Loaded Configuration File”是哪个文件,改它并重启 PHP-FPM 或 Apache。宝塔用户直接在“软件商店 → PHP → 设置 → 配置修改”里改,保存后重启该版本 PHP。
Windows 上的 cacert.pem 从哪里下载?
从 curl 官方的 CA extract 页面(curl.se/docs/caextract.html)下载,这是转换成 PEM 格式的 Mozilla 根证书列表。放到固定路径(比如 PHP 安装目录下),把 curl.cainfo 和 openssl.cafile 指向它,并定期更新。
同一台服务器上 curl 命令正常,PHP 却报错,怎么回事?
curl 命令和 PHP 的 curl 扩展可能链接的是不同的 TLS 库、默认 CA 路径不同,PHP 也可能跑在 chroot 或容器里。对比 curl -v 输出里的 CAfile: 和 php -r "print_r(openssl_get_cert_locations());" 的结果。
Guzzle 或 Laravel 里怎么指定 CA 文件?
给 verify 选项传路径:new GuzzleHttp\Client(['verify' => '/etc/ssl/certs/ca-certificates.crt']),Laravel 里用 Http::withOptions(['verify' => '/path/to/cacert.pem'])。这个值只应该是路径或 true,写成 false 就是关闭校验。