【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 会尝试验证证书链:

  1. 检查服务器证书是否由信任的 CA 签发
  2. 验证证书链是否完整(中间证书是否可追溯到根证书)
  3. 验证证书是否在有效期内

当缺少本地 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. 证书链验证流程

证书链验证需要:

  1. 服务器证书(leaf certificate)
  2. 中间证书(intermediate certificate)
  3. 根证书(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=openssl

3. 证书文件准备

创建一个包含所有 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 certificate

2. 解决方案一:指定 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.crt

2. 证书格式不支持

错误示例:

curl_setopt($ch, CURLOPT_CAINFO, 'ca-bundle.pem');

问题: 文件格式为 PEM,但 PHP 期望 DER 格式

解决: 使用 openssl 转换格式:

openssl x509 -in ca-bundle.pem -out ca-bundle.der -outform der

3. 证书链不完整

错误示例:

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 请求。

PHP , ssl
最后修改于:2026年09月26日 14:03

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日