默认libcurl仅验证证书链不验证主机名,必须同时设curlopt_ssl_verifypeer=1l和curlopt_ssl_verifyhost=2l才安全;指定ca证书首选curlopt_cainfo绝对路径pem文件,避免capath;自签名证书应追加至ca bundle末尾,切勿关闭验证。

curl_easy_setopt 设置证书验证开关
默认情况下 libcurl 会校验服务器证书,但不校验主机名(即不检查 CN 或 SAN 是否匹配请求域名),这容易被中间人攻击绕过。必须显式开启主机名验证,否则看似“能通”,实则不安全。
- 开启完整验证:
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L)(验证证书链)
- 同时必须开:
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L)(验证主机名,2L 是唯一有效值,1L 已废弃且无效)
- 如果跳过验证(仅调试用):
CURLOPT_SSL_VERIFYPEER 和 CURLOPT_SSL_VERIFYHOST 都设为 0L,但上线前必须删掉
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L)(验证证书链) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L)(验证主机名,2L 是唯一有效值,1L 已废弃且无效) CURLOPT_SSL_VERIFYPEER 和 CURLOPT_SSL_VERIFYHOST 都设为 0L,但上线前必须删掉 常见错误:只设 CURLOPT_SSL_VERIFYPEER 为 1L,却漏掉 CURLOPT_SSL_VERIFYHOST,结果证书链通过了,但连的是假域名(比如 DNS 劫持或 hosts 污染),程序毫无察觉。
指定 CA 证书路径的三种方式libcurl 默认不自带可信 CA 列表,不指定就会报 SSL certificate problem: unable to get local issuer certificate。
- 最可靠:用
CURLOPT_CAINFO 指向 PEM 格式的单个 CA bundle 文件,例如:curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-bundle.crt")
- Linux 系统常用路径有:
/etc/ssl/certs/ca-certificates.crt(Debian/Ubuntu)、/etc/pki/tls/certs/ca-bundle.crt(RHEL/CentOS)
- Windows 下需自行提供,推荐从 Mozilla 官方导出:
curl -o ca-bundle.crt <a href="https://www.php.cn/link/5fe4dadcdb001d8566cd20e6d8a20251">https://www.php.cn/link/5fe4dadcdb001d8566cd20e6d8a20251</a>
- 不要用
CURLOPT_CAPATH(目录模式),它依赖 OpenSSL 的哈希命名规则,Windows 和某些嵌入式环境不兼容,容易静默失败
CURLOPT_CAINFO 指向 PEM 格式的单个 CA bundle 文件,例如:curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-bundle.crt") /etc/ssl/certs/ca-certificates.crt(Debian/Ubuntu)、/etc/pki/tls/certs/ca-bundle.crt(RHEL/CentOS) curl -o ca-bundle.crt <a href="https://www.php.cn/link/5fe4dadcdb001d8566cd20e6d8a20251">https://www.php.cn/link/5fe4dadcdb001d8566cd20e6d8a20251</a> CURLOPT_CAPATH(目录模式),它依赖 OpenSSL 的哈希命名规则,Windows 和某些嵌入式环境不兼容,容易静默失败 注意:CURLOPT_CAINFO 路径必须是绝对路径或确保当前工作目录稳定;相对路径在 daemon 进程或 IDE 启动时极易失效。
OpenSSL 底层证书加载失败的典型表现
即使 libcurl 参数全对,底层 OpenSSL 若找不到证书,仍会报错,现象包括:SSL connect error、error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed。
- 检查 OpenSSL 版本是否太老(
- 调用
SSL_get_error() 获取具体错误码,比只看 curl_easy_strerror() 更准
- 在调用
curl_easy_perform() 前加一句:curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L),观察日志里是否出现 <em> ALPN, offering http/1.1</em> 和 Server certificate 区块,缺失说明握手根本没走到证书阶段
- 若程序静态链接 OpenSSL,确保编译时用的头文件和运行时 .so/.dll 版本一致,混用会导致
SSL_CTX_load_verify_locations 返回 0 却无提示
自签名证书或内网 CA 的处理方式
生产环境绝不能关验证,但测试或私有服务需要适配非公网 CA。
- 把内网根证书内容追加到你的 CA bundle 文件末尾(PEM 格式,以
-----BEGIN CERTIFICATE----- 开头),然后继续用 CURLOPT_CAINFO
- 不要用
CURLOPT_SSL_CTX_FUNCTION 手动调 SSL_CTX_load_verify_locations,它绕过 libcurl 内部状态管理,多线程下易出竞态
- 若证书带密码,libcurl 不支持自动解密,必须提前用
openssl pkcs12 -nodes 导出无密钥的 PEM,再合并进 CA bundle
SSL_get_error() 获取具体错误码,比只看 curl_easy_strerror() 更准 curl_easy_perform() 前加一句:curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L),观察日志里是否出现 <em> ALPN, offering http/1.1</em> 和 Server certificate 区块,缺失说明握手根本没走到证书阶段 SSL_CTX_load_verify_locations 返回 0 却无提示 - 把内网根证书内容追加到你的 CA bundle 文件末尾(PEM 格式,以
-----BEGIN CERTIFICATE-----开头),然后继续用CURLOPT_CAINFO - 不要用
CURLOPT_SSL_CTX_FUNCTION手动调SSL_CTX_load_verify_locations,它绕过 libcurl 内部状态管理,多线程下易出竞态 - 若证书带密码,libcurl 不支持自动解密,必须提前用
openssl pkcs12 -nodes导出无密钥的 PEM,再合并进 CA bundle
真正麻烦的不是加证书,而是证书更新后忘记同步到所有部署节点——尤其是容器镜像里硬编码的 bundle 文件,一两年不更新就可能因根证书过期导致批量连接失败。










