一次证书续期只部署叶子证书、导致部分客户端 TLS 校验失败的复盘(匿名化合成案例)。
某业务集群完成 HTTPS 证书续期后,部分客户端开始报 certificate verify failed 或 unable to get local issuer certificate,同一接口在浏览器、内部健康检查和另一部分客户端上却正常。证书没有过期,域名匹配也正确,服务端没有返回 5xx。失败发生在大规模客户端更替或新连接建立时,老连接和缓存过证书链的客户端看起来仍然健康。
openssl s_client -showcerts 检查服务端,发现只返回了一张叶子证书,没有中间证书。fullchain.pem,先在单个入口灰度加载并验证。失败的是 TLS 握手阶段,请求通常没有进入应用,因此不涉及业务鉴权和数据泄露。但部分客户端无法调用接口,重试会增加入口连接数和握手开销;同一服务在不同网络、不同客户端上的表现不一致,也增加了支持成本。浏览器和内部探针成功并不能代表全部用户正常,实际影响由客户端是否缓存中间证书、是否支持自动获取中间证书以及本地信任库决定。
第一个误导是“证书日期正常、浏览器能打开”,因此把责任推给客户端信任库。浏览器可能缓存过中间证书,部分客户端还会通过证书里的 AIA 地址自动获取中间证书;这次碰巧命中的客户端没有缓存、也不自动获取,所以失败。内部健康检查使用的 CA 文件又恰好包含中间证书,于是检查结果一直是绿色,掩盖了服务端未发送完整链的问题。
第二个误导是只检查叶子证书。openssl x509 -in cert.pem -noout -dates 只能证明这张证书本身有效,不能证明服务端把签发它的中间证书一起发了过来。推翻误判的证据是 s_client -showcerts 只显示一张证书,以及只信任根证书时验证链无法构建。这一步把问题从“客户端太旧”变成了“服务端证书链不完整”。
证书续期自动化在部署时从完整证书包中提取了叶子证书,Web 入口配置又指向这个只有叶子证书的文件,导致 TLS 握手时没有发送中间证书。根证书本来就在客户端信任库中,但中间证书不在,客户端无法把叶子证书连到可信根,握手因此失败。
部分客户端正常,是因为它们此前缓存了该中间证书,或者支持 AIA 自动补链。这两种机制都不可靠:缓存会过期,AIA 可能被网络策略阻断,不同客户端行为也不一致。问题不是证书过期、私钥错误或 TLS 版本不兼容,而是服务端提供的链不完整。
先确认服务端实际发送了什么,而不是只看磁盘上的文件:
openssl s_client -connect <host>:443 -servername <host> -showcerts </dev/null > /tmp/tls-chain.txt
grep -c -- '-----BEGIN CERTIFICATE-----' /tmp/tls-chain.txt
openssl s_client -connect <host>:443 -servername <host> \
-CAfile <root-ca-only.pem> -verify_return_error </dev/null
第一条命令应至少看到叶子证书和中间证书各一张。第二条只用根证书验证,能成功才说明服务端发送的链可以构建到客户端信任根。再检查本地文件关系和入口配置:
openssl x509 -in leaf.pem -noout -subject -issuer -dates -ext subjectAltName
openssl x509 -in intermediate.pem -noout -subject -issuer -dates
openssl verify -CAfile root-ca-only.pem -untrusted intermediate.pem leaf.pem
nginx -T | grep -n 'ssl_certificate'
修复时按“叶子证书在前、中间证书在后”的顺序拼接,通常不需要把根证书放进服务端链:
cat leaf.pem intermediate.pem > fullchain.pem
openssl verify -CAfile root-ca-only.pem -untrusted intermediate.pem leaf.pem
在 Nginx 中把 ssl_certificate 指向 fullchain.pem,保留私钥配置不变;先执行配置检查,再灰度重载:
nginx -t
systemctl reload nginx
如果证书由 Kubernetes Ingress 或负载均衡器终止,同样要确认 tls.crt 或证书包中包含完整的中间证书,而不是只有叶子证书。修复后从外部网络、仅使用系统根证书的客户端重新验证:
curl -v --cacert <root-ca-only.pem> https://<host>/health
openssl s_client -connect <host>:443 -servername <host> \
-CAfile <root-ca-only.pem> -verify_return_error </dev/null
curl -k 只能用于临时分离问题,不能作为验收。AIA 自动补链也不是修复方案,服务端应主动提供完整链。
合上页面,先回答,再展开对照。
fullchain.pem 通常应该包含哪些证书,是否要包含根证书?openssl s_client -connect <host>:443 -servername <host> -showcerts </dev/null 查看返回的证书,并统计 BEGIN CERTIFICATE 的数量;再用只包含根证书的 CA 文件加 -verify_return_error 验证能否构建链。
小程序版
微信扫码,随时查命令