系统学习 故障排查学习路径:从方法论到系统、网络、数据库与 K8s

incident-tls-chain

一次证书续期只部署叶子证书、导致部分客户端 TLS 校验失败的复盘(匿名化合成案例)。

现象

某业务集群完成 HTTPS 证书续期后,部分客户端开始报 certificate verify failed 或 unable to get local issuer certificate,同一接口在浏览器、内部健康检查和另一部分客户端上却正常。证书没有过期,域名匹配也正确,服务端没有返回 5xx。失败发生在大规模客户端更替或新连接建立时,老连接和缓存过证书链的客户端看起来仍然健康。

时间线

影响面

失败的是 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 自动补链也不是修复方案,服务端应主动提供完整链。

预防

相关命令

参考链接

读完自测

合上页面,先回答,再展开对照。

  1. 为什么浏览器和内部探针正常,不能证明服务端证书链完整?
  2. 哪个命令可以直接看服务端发送了几张证书?
  3. fullchain.pem 通常应该包含哪些证书,是否要包含根证书?
答案
  1. 浏览器可能缓存了中间证书,部分客户端会通过 AIA 自动补链,内部探针又可能使用包含中间证书的 CA 文件。它们成功不代表没有缓存、不能补链的客户端也能构建完整信任链。
  2. 使用 openssl s_client -connect <host>:443 -servername <host> -showcerts </dev/null 查看返回的证书,并统计 BEGIN CERTIFICATE 的数量;再用只包含根证书的 CA 文件加 -verify_return_error 验证能否构建链。
  3. 通常包含叶子证书和一个或多个中间证书,叶子在前、中间证书按签发关系排列。一般不需要包含根证书,根证书应在客户端信任库中。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令