命令行工具

error 20:unable to get local issuer certificate 的修法

作者: · 最后核实

报错信息error 20 at 0 depth lookup: unable to get local issuer certificate

错误 20 的意思是:OpenSSL 找不到证书链中某张证书的签发者证书。 报错里的 depth 说明断在哪一环:depth 0 表示没提供站点证书的签发者(中间证书);depth 更大表示链条终止在一个不在你信任库里的 CA。

快速修复

depth 0 报错就补中间证书,depth 1 及以上报错就把正确的根证书加进信任库。先测试服务器,看 depth 和它发出的证书链:

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>&1 \
  | grep -E "depth=|verify error|^ *[0-9] s:|Verify return"

验证本地文件时,显式提供中间证书和根证书:

openssl verify -CAfile root.pem -untrusted intermediates.pem leaf.pem
  • s_client 只显示一张证书,depth 0 报错误 20: 服务器必须发送中间证书,见缺少中间证书。
  • depth 1 或 2 报错误 20: 链顶那张证书不在你的信任库里,加入正确的根证书或更新 CA 证书包。

原因

最常见的是服务器没提供中间证书(depth 0),其次是根证书不在信任库里。

1. 没提供中间证书(depth 0)

  • 确认: s_client 输出里只有一行 s:;或者本地验证时 openssl verify leaf.pem 失败,而 openssl verify -untrusted intermediates.pem leaf.pem 成功。
  • 修复: 服务器上部署“站点证书 + 中间证书”(fullchain.pem)。阿里云、腾讯云下载的 Nginx 格式证书通常已经包含中间证书,宝塔粘贴时要粘贴完整内容。本地验证文件时始终用 -untrusted 传入证书链。证书链补全工具能为站点证书找到正确的中间证书。

2. 根证书不受信任(depth ≥ 1)

证书链是完整的,但终止于你的信任库里没有的 CA:私有 CA、企业代理,或者老系统上的较新公网根证书。

  • 确认: -showcerts 最后一行 i: 显示的 CA 不在 /etc/ssl/certs/(或你使用的证书包)里。
  • 修复: 把私有根证书加入系统信任库,测试时用 -CAfile 指定。公网根证书缺失就更新 ca-certificates。

3. 用错了信任库

自定义的 -CAfile 或 SSL_CERT_FILE 会替换默认信任锚。

  • 确认: 去掉 -CAfile 或环境变量后,同样的命令就成功了。
  • 修复: 使用同时包含所需公网根证书和私有根证书的证书包。

4. 验证了错误的文件

验证的是中间证书或 CA 证书包,而不是站点证书;或者站点证书和中间证书不是同一条链,也会报错误 20。

  • 确认: openssl x509 -in file.pem -noout -subject -issuer 显示的不是你的服务器证书。
  • 修复: 把证书包拆开,验证站点证书。

各环境修法

Linux 上把私有根证书放进系统信任库再刷新;macOS 和 Windows 上要先确认 OpenSSL 实际读取的是哪个 CA 文件。

Debian / Ubuntu: 私有根证书放到 /usr/local/share/ca-certificates/*.crt,执行 sudo update-ca-certificates。

CentOS / RHEL / Rocky / Alibaba Cloud Linux: 放到 /etc/pki/ca-trust/source/anchors/,执行 sudo update-ca-trust。

macOS: 系统自带的 LibreSSL openssl 和 Homebrew 的 OpenSSL 用的是不同的 CA 文件,用 openssl version -d 查看各自的目录。

Windows: Windows 版 OpenSSL 不读 Windows 证书库,用 -CAfile 指定从证书库导出的 PEM 证书包,或从 curl.se 下载的证书包。

其他工具里的同一个错误: curl 报错 60、Python 的 CERTIFICATE_VERIFY_FAILED、Node.js 的 UNABLE_TO_GET_ISSUER_CERT_LOCALLY,底层都是 OpenSSL 的错误 20,见 curl 报错 60。

不要这样做

这些绕过办法只会掩盖证书链问题。

  • 因为 s_client 还能连上就忽略错误 20。 s_client 默认在验证出错后继续连接,真实客户端不会。加 -verify_return_error 让它像真实客户端一样失败。
  • 把中间证书当作受信任根证书加入信任库来消除报错。 信任锚应该是根证书;这样做只是让你自己看不到服务器证书链的问题,你的用户照样会报错。
  • 把站点证书放进信任库。 能“解决”到下次续期,然后又坏了。

常见问题

浏览器里正常的证书,openssl verify cert.pem 却失败,为什么?

openssl verify 只用文件里的第一张证书和信任库,不会把同一文件里的其他证书当作中间证书。用 openssl verify -untrusted chain.pem cert.pem(chain.pem 放中间证书)测试完整链。

错误 20 和错误 21 有什么区别?

错误 20 是“本地找不到签发者”;错误 21(unable to verify the first certificate)通常出现在 s_client 里,服务器只发了一张证书且签发者未知。两者大多都意味着缺中间证书。

s_client 会用系统 CA 库吗?

会,默认加载 OpenSSL 的默认验证路径,大多数 Linux 发行版就是系统证书包。-CAfile 增加或覆盖信任锚,-no-CAfile、-no-CApath 关闭默认路径。

资料来源

  1. OpenSSL openssl-verify
  2. OpenSSL X509_STORE_CTX_get_error (verification error codes)
  3. OpenSSL openssl-verification-options
  4. OpenSSL openssl-s_client