错误信息
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
也可能输出为
- curl: (60) SSL certificate problem: self signed certificate in certificate chain
- curl: (60) SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)
OpenSSL 3.0 及更高版本打印“self-signed”;OpenSSL 1.1.1 打印不带连字符的“self signed”,因此日志和搜索结果中两种拼写都会出现。较新的 curl 版本(我们测试的是 8.22.0)会写作“SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)”。Windows 上基于 Schannel 构建的 curl 会把这种情况报告为“SEC_E_UNTRUSTED_ROOT”。
curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN (19)
含义
curl 沿着服务器发送的证书链一直追溯到一张自签名根证书,而这张根证书不在 curl 的 CA 证书包中。与“unable to get local issuer certificate”不同,这里的证书链是完整的:curl 能看到根证书,只是没有理由信任它。实际上,这个根证书几乎总是属于公司 CA 或拦截 TLS 的代理。
原因
执行 TLS 检查的代理或杀毒软件对连接重新签名
常见Zscaler、Netskope、Fortinet、Palo Alto 以及许多杀毒软件会终止 TLS,出示由它们自己的根证书签发的证书,并在证书链中发送该根证书。浏览器通过操作系统信任这个根证书,但 WSL、Docker、Git Bash 或 CI 运行器中的 curl 读取的是自己的证书包,因此会拒绝它。证书的颁发者是代理厂商或你所在公司的名称,而不是网站真正的 CA。
服务使用私有根证书,并在证书链中发送它
常见内部服务、Kubernetes Ingress 和专用设备常常使用由公司根证书或自建根证书签发的证书,并把该根证书放进所提供的证书链中。发送根证书并不会让它受信任:信任锚必须事先存在于客户端上。
私有根证书安装在了另一个证书存储中
偶见根证书被添加到了 Windows 证书存储、macOS 钥匙串或 Java 的 cacerts 中,但当前这个 curl 读取的是 OpenSSL 证书包,例如 /etc/ssl/certs/ca-certificates.crt,或者 Git for Windows、Homebrew、conda 自带的副本。
客户端的公共根证书库已过时
罕见少数情况下,服务器发送的是较新的公共根证书,而从未更新过的旧客户端没有这个根证书。更新 ca-certificates 软件包即可解决;如果该根证书确实是公共的,其他机器验证这个网站时不会有问题。
修复方法
- 1.
弄清这是谁的根证书
查看证书链中的最后一张证书。如果主体是代理或安全厂商,或者是你所在公司的名称,解决办法是在这台机器上信任该根证书。如果在你无法控制的网络上看到意料之外的名称,就停下来:有人正在拦截连接。
bash # Print the subject and issuer of every certificate the server (or the proxy) sends openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:' # The last entry has s: == i: — that self-signed root is what curl does not trust. - 2.
将公司或代理的根证书添加到系统证书存储
从 IT 部门获取根证书(或从受管机器的信任库中导出),并安装到 curl 使用的操作系统证书包中。容器需要在构建时安装;WSL 需要在 Linux 发行版内部安装,而不只是装在 Windows 中。
bash # Debian, Ubuntu, Alpine sudo cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt sudo update-ca-certificates # RHEL, Fedora, Amazon Linux sudo cp corp-root-ca.pem /etc/pki/ca-trust/source/anchors/ sudo update-ca-trust - 3.
只在单条命令或单个环境中信任该根证书
--cacert 和 CURL_CA_BUNDLE 会替换 curl 的默认证书包,因此如果同一个进程也要访问公共网站,就要让它们指向一个同时包含公共根证书和私有根证书的文件。
bash curl --cacert corp-root-ca.pem https://api.example.com/ # Keep public roots too: build one bundle with both cat /etc/ssl/certs/ca-certificates.crt corp-root-ca.pem > /opt/certs/bundle.pem export CURL_CA_BUNDLE=/opt/certs/bundle.pem - 4.
服务器所有者:不要依赖发送根证书
提供叶子证书和中间证书;私有根证书则改为通过配置管理分发到客户端。附带根证书只会浪费字节,永远不会让不受信任的根证书变得受信任。
bash # Serve leaf + intermediates only; clients must already have the root cat api.example.com.crt intermediate.crt > fullchain.pem - 5.
临时诊断:关闭验证以查看颁发者
-k 会禁用证书检查,让详细输出能够显示服务器出示的主体和颁发者。只用它来识别根证书。如果把 -k 留在脚本中,传输路径上的任何人都能读取和篡改流量,包括令牌和下载的安装程序。
bash # Diagnostic only: shows who issued the certificate even though curl distrusts it curl -sv -k -o /dev/null https://api.example.com/ 2>&1 | grep -E 'subject:|issuer:'
相关错误
curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)curl: (60) SSL certificate problem: self-signed certificatecurl (OpenSSL backend)verify error:num=19:self-signed certificate in certificate chainOpenSSL CLI (openssl s_client / openssl verify)self-signed certificate in certificate chainNode.js tls / https / fetch[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certifi…Python ssl module (OpenSSL)