浏览器能打开、curl 却报 unable to get local issuer:中间证书链断了
同样的 HTTPS 站点,浏览器正常而 curl、Java 客户端却报证书错误,多半是服务器没发完整证书链。本文解释中间证书的作用、为什么会漏发,以及如何排查和修复。
本文目录

运维群里很经典的一幕:同事说"网站证书坏了,curl 报 unable to get local issuer certificate",你打开浏览器一看,绿色小锁,一切正常。两边都没说谎,问题出在中间证书上。
证书链是怎么回事
CA 一般不用根证书直接签你的网站证书,而是通过一张或多张中间证书来签。链是这样的:
根证书(预置在客户端的信任库里)
└─ 中间证书(需要服务器在握手时发出来)
└─ 你的服务器证书(leaf / end-entity)TLS 握手时,服务器应该把自己的证书连同中间证书一起发给客户端。客户端拿着叶子证书,逐级验证签名,直到撞上自己信任库里的一张根证书,链就接上了。
为什么浏览器"看起来没事"
浏览器比命令行客户端宽容得多,主要靠两招:
- 缓存:你之前访问过同一家 CA 的其他站点,浏览器很可能已经把中间证书存进缓存了,这次直接拿来用。
- AIA fetching:很多证书里带一个 Authority Information Access 扩展,里面写着中间证书的下载地址。Chrome 会自己去把它抓回来补上。
curl、OpenSSL、Go、Java 这些客户端大多不做 AIA fetching,也没有那种缓存。服务器少发一张中间证书,它们就验证失败。所以你会看到"浏览器全绿、API 调用全红"的诡异分裂——不是客户端矫情,是它们照章办事。
什么时候会遇到
- 刚换新证书:运维只把
cert.pem(叶子证书)传了上去,忘了拼接fullchain.pem。Let's Encrypt、Certbot 输出的fullchain.pem就是叶子 + 中间打包好的,用它就对。 - 配置 SSL termination 的设备或网关:F5、某些负载均衡、API 网关上传证书时,中间证书是单独一个字段,很容易漏。
- Java 服务互相调用:JVM 的信任库管理严格,缺中间证书立刻报
PKIX path building failed,这行报错几乎可以当成"链不完整"的同义词。

怎么排查和修
先看服务器实际发了什么:
openssl s_client -connect example.com:443 -servername example.com输出里 Certificate chain 一段会列出服务器发了几张证书。如果只有一张(编号 0,即叶子证书),中间证书就是漏了。也可以看 Verify return code,21 (unable to verify the first certificate) 基本就是这个病。
修复方式取决于组件:
- Nginx:
ssl_certificate指向完整链文件(叶子在前,中间在后拼接),不要用只含叶子的cert.pem。 - Apache:新版本把链并到
SSLCertificateFile里,老版本用SSLCertificateChainFile。 - 负载均衡 / 网关:找到"中间证书"或"CA bundle"字段单独上传。
在 CertbotX 中实践
用 CertbotX 做 ACME 自动签发时,平台取回的是完整链并在部署到 Agent、EdgeOne 或 CDN 时一并下发,不会出现"只部署了叶子证书"这种半截子情况。每次部署都有记录可查,出问题时能回看当时推下去的是哪份链文件,排查链不完整这类问题会省掉不少猜的时间。
一个容易踩的小坑
链的顺序有讲究:叶子证书必须在最前,后面按签发顺序排中间证书。顺序拼反了,有的客户端容错,有的直接报错——又是"我这边好的你那边坏的"的经典剧本。另外注意别把根证书塞进链里,没必要,有的客户端还会因此报错。
---