【PHP】解决cURL error 60: SSL certificate problem: unable to get local issuer certificate (see
'# 【PHP】解决cURL error 60: SSL certificate problem: unable to get local issuer certificate
一、背景与问题
在使用 PHP 的 cURL 进行 HTTPS 请求时,开发者经常会遇到 cURL error 60 错误。这个错误的完整描述是:
SSL certificate problem: unable to get local issuer certificate它表明 PHP 在进行 SSL/TLS 握手时,无法验证服务器的证书链。具体来说,PHP 无法找到本地信任的 CA(证书颁发机构)证书,导致无法验证服务器证书的合法性。
常见场景
- 本地开发环境未配置完整的 CA 证书库
- 服务器缺少系统证书更新
- 使用自签名证书的测试环境
- 容器化部署时证书路径配置错误
核心原理
SSL/TLS 握手过程需要验证服务器证书是否由受信任的 CA 签发。PHP 通过 cURL 的 SSL 验证机制(CURLOPT_SSL_VERIFYPEER)来实现这一功能。当配置为 true 时,PHP 会尝试验证证书链:
- 检查服务器证书是否由信任的 CA 签发
- 验证证书链是否完整(中间证书是否可追溯到根证书)
- 验证证书是否在有效期内
当缺少本地 CA 证书时,PHP 会抛出 cURL error 60,因为无法完成证书链的验证。
二、基本原理
1. SSL/TLS 握手流程
Client → Server: ClientHello (包含支持的加密套件)
Server → Client: ServerHello (包含服务器证书)
Client → Server: CertificateVerify (验证证书链)
Client → Server: Finished (握手完成)PHP 的 cURL 在 ServerHello 阶段会验证服务器证书。如果证书链无法验证,会触发错误。
2. PHP 的 SSL 验证机制
PHP 的 cURL 默认使用系统 CA 证书库(如 /etc/ssl/certs 或 /usr/local/etc/openssl/cert.pem)。当使用 CURLOPT_SSL_VERIFYPEER 设置为 true 时,会启动以下验证流程:
- 检查服务器证书是否由系统信任的 CA 签发
- 验证证书链是否完整(中间证书是否可追溯到根证书)
- 检查证书是否在有效期内
如果以上条件不满足,会抛出 cURL error 60。
3. 证书链验证流程
证书链验证需要:
- 服务器证书(leaf certificate)
- 中间证书(intermediate certificate)
- 根证书(root certificate)
PHP 会通过以下方式验证:
- 使用
CURLOPT_SSL_VERIFYHOST检查域名是否匹配 - 使用
CURLOPT_CAINFO指定本地 CA 证书文件 - 使用
CURLOPT_SSL_VERIFYPEER控制是否验证证书链
三、环境准备
1. 系统依赖
确保系统安装了 OpenSSL 和 CA 证书库:
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y openssl ca-certificates
# CentOS/RHEL
sudo yum install -y openssl ca-certificates
# Windows
# 安装 OpenSSL,确保 %OPENSSL_DIR%\bin 在 PATH 中2. PHP 配置
检查 PHP 的 OpenSSL 扩展是否启用:
php -m | grep openssl确保配置文件中包含 openssl 模块:
; php.ini
extension=openssl3. 证书文件准备
创建一个包含所有 CA 证书的文件(如 ca-bundle.pem):
# 获取最新 CA 证书包
curl -O https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt
# 或者使用系统证书
sudo cp /etc/ssl/certs/ca-certificates.crt ca-bundle.pem四、核心实现
1. 基础错误示例
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // 启用证书验证
$response = curl_exec($ch);
if ($response === false) {
echo 'cURL error: ' . curl_error($ch);
}
curl_close($ch);输出示例:
cURL error: SSL certificate problem: unable to get local issuer certificate2. 解决方案一:指定 CA 证书文件
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/ca-bundle.pem'); // 指定 CA 证书文件
$response = curl_exec($ch);
if ($response === false) {
echo 'cURL error: ' . curl_error($ch);
}
curl_close($ch);关键点:
CURLOPT_CAINFO指定本地 CA 证书文件路径- 证书文件需要包含完整的证书链(根证书+中间证书)
3. 解决方案二:临时禁用证书验证(不推荐)
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 禁用证书验证
$response = curl_exec($ch);
if ($response === false) {
echo 'cURL error: ' . curl_error($ch);
}
curl_close($ch);警告:
- 该方法会完全绕过证书验证,存在中间人攻击风险
- 仅适用于开发测试环境
4. 解决方案三:自签名证书处理
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://localhost:443');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 禁用证书验证
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 验证域名匹配
$response = curl_exec($ch);
if ($response === false) {
echo 'cURL error: ' . curl_error($ch);
}
curl_close($ch);关键点:
CURLOPT_SSL_VERIFYHOST控制域名验证- 对于自签名证书,需要手动添加到信任库
五、完整案例
场景:调用第三方天气 API
<?php
$apiUrl = 'https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=Beijing';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_CAINFO, '/etc/ssl/certs/ca-certificates.crt'); // 指定系统证书
$response = curl_exec($ch);
if ($response === false) {
echo 'cURL error: ' . curl_error($ch);
} else {
$data = json_decode($response, true);
print_r($data);
}
curl_close($ch);输出示例:
{
"location": {
"name": "Beijing",
"region": "Beijing",
"country": "CN"
},
"current": {
"temp_c": 25,
"humidity": 60
}
}关键点:
- 使用系统证书库进行验证
- 确保证书文件路径正确
- 使用 JSON 解析返回结果
六、源码解析
1. PHP 的 cURL 扩展
PHP 的 cURL 扩展基于 libcurl 实现。在 php-curl 源码中,关键函数包括:
PHP_FUNCTION(curl_setopt) {
// 设置 cURL 选项
if (zend_parse_parameters(ZEND_NUM_ARGS, "rz!", &option, &value, &is_null) == FAILURE) {
RETURN_FALSE;
}
switch (option) {
case CURLOPT_SSL_VERIFYPEER:
// 设置是否验证证书
break;
case CURLOPT_SSL_VERIFYHOST:
// 设置是否验证域名
break;
case CURLOPT_CAINFO:
// 设置 CA 证书文件路径
break;
default:
// 其他选项处理
}
}2. 证书验证流程
在 libcurl 的源码中,证书验证主要由 curl_easy_setopt 和 curl_easy_perform 实现:
void curl_easy_setopt(CURL *handle, CURLoption option, ...) {
switch (option) {
case CURLOPT_SSL_VERIFYPEER:
// 设置 SSL 验证选项
break;
case CURLOPT_CAINFO:
// 设置 CA 证书文件路径
break;
}
}七、进阶使用
1. 多服务器证书管理
在微服务架构中,可能需要为不同服务器配置不同的 CA 证书:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://service1.example.com');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/service1-ca.pem');
// 然后重复配置 for service2...2. 容器化部署
在 Docker 容器中,需要将 CA 证书挂载到容器中:
FROM php:8.2-cli
COPY ca-bundle.pem /etc/ssl/certs/3. 自签名证书的特殊处理
对于内部服务的自签名证书,可以将证书添加到信任库:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://localhost:443');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/self-signed-cert.pem');八、性能与工程实践
1. 性能优化
- 缓存 CA 证书:避免重复下载证书文件
- 批量请求:减少网络请求次数
- 异步处理:使用
curl_multi处理多个请求
2. 安全风险
- 禁用证书验证:可能导致中间人攻击
- 不完整证书链:可能绕过部分验证
- 过期证书:可能导致验证失败
3. 配置建议
- 生产环境:始终启用
CURLOPT_SSL_VERIFYPEER - 开发环境:使用临时证书文件,避免使用系统证书
- 容器环境:确保证书文件路径正确
九、常见问题与踩坑
1. 证书文件路径错误
错误示例:
curl_setopt($ch, CURLOPT_CAINFO, '/etc/ssl/certs/ca-certificates.crt');问题: 文件不存在或权限不足
解决: 检查文件路径和权限:
ls -l /etc/ssl/certs/ca-certificates.crt2. 证书格式不支持
错误示例:
curl_setopt($ch, CURLOPT_CAINFO, 'ca-bundle.pem');问题: 文件格式为 PEM,但 PHP 期望 DER 格式
解决: 使用 openssl 转换格式:
openssl x509 -in ca-bundle.pem -out ca-bundle.der -outform der3. 证书链不完整
错误示例:
curl_setopt($ch, CURLOPT_CAINFO, 'self-signed-cert.pem');问题: 未包含中间证书
解决: 将所有证书合并到一个文件:
cat self-signed-cert.pem intermediate.pem root-cert.pem > ca-bundle.pem十、最佳实践
1. 推荐使用场景
- 需要严格验证服务器证书的生产环境
- 调用第三方 API(如支付网关、云服务)
- 需要确保数据传输安全的场景
2. 不推荐使用场景
- 开发测试环境(可临时禁用验证)
- 内部自签名证书服务(需手动添加到信任库)
- 需要快速调试的临时需求
3. 安全建议
- 定期更新 CA 证书库
- 避免在生产环境禁用证书验证
- 对敏感数据使用 HTTPS 传输
十一、总结
cURL error 60 是 PHP 中常见的 SSL 证书验证问题,其核心原因是缺少本地 CA 证书或证书链不完整。通过正确配置 CURLOPT_SSL_VERIFYPEER 和 CURLOPT_CAINFO,可以有效解决该问题。
在实际开发中,应始终启用证书验证以确保数据安全。对于特殊场景(如自签名证书),需手动添加到信任库。同时,需注意性能和安全的平衡,避免因过度优化导致安全漏洞。
通过本文的深入解析和代码示例,开发者可以更好地理解和应对 cURL error 60,确保在实际项目中安全、稳定地使用 HTTPS 请求。
评论已关闭