错误信息
curl: (60) SSL certificate problem: unable to get local issuer certificate
也可能输出为
- curl: (60) SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)
- SSL certificate problem: unable to get local issuer certificate
基于 OpenSSL 的 curl(Linux、macOS Homebrew、Git for Windows)打印的是这段文本;较新的版本(我们测试的是 8.22.0,而 8.14.1 仍使用上面的文本)会写作“SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)”,附上 OpenSSL 的验证错误码。Windows 上用 Schannel 构建的 curl 会把同样的情况报告为“schannel: SEC_E_UNTRUSTED_ROOT (0x80090325)”,基于 GnuTLS 的 libcurl(Debian 和 Ubuntu 上的 git 使用它)则报告为“server verification failed: certificate signer not trusted”。基于 libcurl 构建的工具(例如 PHP 和 git)会打印“(60) ”之后的部分,并加上自己的前缀。
curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (20)
含义
curl 收到了服务器的证书,但在自己的 CA 证书包中找不到证书链最后一张证书的颁发者证书。这张证书本身可能完全有效;只是 curl 无法把它与任何受信任的根证书关联起来,因此拒绝连接(退出码 60)。缺失的环节要么是服务器忘记发送的中间证书,要么是你的机器上没有的根 CA。
原因
服务器只发送了自己的证书
常见公共 CA 用中间证书签发服务器证书,服务器必须把该中间证书连同叶子证书一起发送。配置了 cert.pem 而不是 fullchain.pem 的服务器在桌面浏览器中能正常工作,因为浏览器会缓存中间证书并获取缺失的中间证书;但 curl 两者都不做,会停在叶子证书处。
证书来自私有 CA 或企业 CA
常见内部服务、预发布环境和 Kubernetes Ingress 常常使用公司 CA 或自建 CA 签发的证书。该根证书不在 curl 使用的公共证书包中(Debian 和 Ubuntu 上为 /etc/ssl/certs/ca-certificates.crt),因此在安装该根证书之前,由它签发的任何证书都无法通过验证。
执行 TLS 检查的代理对流量重新签名
偶见企业代理和端点安全产品(Zscaler、Netskope、Palo Alto 等)会把每个网站的证书替换成由它们自己的 CA 签发的证书。浏览器通过操作系统的钥匙串信任这个 CA,而 curl、容器和 WSL 使用的是另一个不包含它的证书包,因此每一次 HTTPS 调用都会以同样的方式失败。
机器上没有可用的 CA 证书包
偶见精简版和 distroless 容器镜像不带 ca-certificates 软件包,旧系统自带的证书包则太旧,没有收录如今的根证书。证书包为空或过时时,即使是知名公共网站的证书,也会报出这个错误。
CA 被添加到了这个 curl 不读取的证书存储中
罕见每个 curl 构建版本只读取一个特定的证书包:Homebrew 的 curl、conda 环境和 Git for Windows 都各自附带自己的证书包,而 Windows 上既有 Schannel 构建版本,也有 OpenSSL 构建版本。添加到 Windows 证书存储或 macOS 钥匙串中的 CA,只对读取该存储的构建版本生效。
修复方法
- 1.
查看服务器实际发送的内容
列出握手中的证书。如果只出现一张(深度 0),且其颁发者是中间 CA,说明服务器配置不完整,必须在服务器上修复。如果证书链终止于公司根证书或自建根证书,说明客户端缺少该根证书。
bash # Which certificates does the server send, and who issued the last one? openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:' # 0 s:CN=api.example.com # i:C=US, O=Example Trust Services, CN=Example TLS Issuing CA <- leaf only: intermediate missing - 2.
修复服务器:提供完整证书链
为 Web 服务器配置一个证书包,其中先是叶子证书,后面依次跟着所有中间证书。这样可以一次性为所有客户端修复这个错误,包括你无法控制的移动应用和 API 客户端。
bash # Leaf first, then each intermediate in order; the root itself is optional cat api.example.com.crt intermediate.crt > fullchain.pem # nginx: ssl_certificate /etc/nginx/tls/fullchain.pem; # Apache: SSLCertificateFile /etc/apache2/tls/fullchain.pem # certbot: use live/<name>/fullchain.pem, never cert.pem - 3.
将私有根 CA 安装到系统证书存储
如果证书链本来就应该终止于公司根证书或内部根证书,就把该根证书添加到操作系统的证书包中。从 IT 团队或平台团队获取根证书,或者直接从 CA 获取;切勿从失败连接所出示的证书中获取,因为那正是你目前还无法信任的东西。
bash # Debian, Ubuntu, Alpine (the file must end in .crt) 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 - 4.
为单条命令或单个 shell 指定 curl 使用的证书包
--cacert 只为单次调用添加信任,不改动系统;CURL_CA_BUNDLE 为该环境中的每次 curl 调用设置证书包。该文件可以包含多张 PEM 证书。
bash curl --cacert corp-root-ca.pem https://api.example.com/ # or for every curl command in this shell (a file may hold several PEM certificates) export CURL_CA_BUNDLE=/opt/certs/corp-and-public-roots.pem - 5.
在精简容器中安装 CA 证书包
如果在容器内连公共网站都失败,说明镜像中没有证书包。在镜像中安装 ca-certificates(或从某个构建阶段复制 /etc/ssl/certs/ca-certificates.crt),而不是关闭验证。
bash # Debian/Ubuntu-based images apt-get update && apt-get install -y --no-install-recommends ca-certificates # Alpine apk add --no-cache ca-certificates - 6.
临时诊断:确认是信任问题
-k(--insecure)会禁用证书验证,因此成功的响应只能证明网络路径是通的,失败与信任有关。切勿把它留在脚本中:使用 -k 时,传输路径上的任何人都能读取和篡改流量,包括凭证和下载的代码。
bash # Diagnostic only: if this works, the problem is trust, not the network. curl -sS -k -o /dev/null -w '%{http_code}\n' https://api.example.com/
相关错误
curl: (60) SSL certificate problem: self-signed certificate in certificate chaincurl (OpenSSL backend)curl: (60) SSL certificate problem: self-signed certificatecurl (OpenSSL backend)verify error:num=20:unable to get local issuer certificateOpenSSL CLI (openssl s_client / openssl verify)fatal: unable to access 'https://git.example.com/team/app.git/': SSL certificat…git over HTTPS with libcurl + OpenSSL (Git for Windows default, macOS Homebrew, Alpine, RHEL)x509: certificate signed by unknown authorityGo crypto/x509