常见场景:证书面板显示还有几个月才到期,客户端却报 certificate has expired。先拆开“证书没过期”:你看的是哪一张证书,又是哪台机器在判断时间?HTTPS 验证的是一条信任路径,不是只看到期日。
本文用离线测试证书复现中间证书过期和叶子尚未生效,不修改系统时钟也不装测试根;命令中的域名与 CA 文件均为示例,执行前替换成已授权目标与可信材料。

一、先确认谁在说“过期”
浏览器、curl、Java 和反向代理都可能充当 TLS 客户端:浏览器直连看本机时间,Nginx 回源看回源进程环境。服务端时钟正常,不能替另一台客户端作证。
先保存完整错误、目标域名、发生时间、客户端版本与部署位置。OpenSSL 的 depth=0 指目标叶子证书,depth=1 是它的签发者;继续往上是什么,要结合真实验证链看。
这里别急着重新签发。只换叶子、却继续搭配过期的中间证书,有点像换了新车票,仍拿着过期证件进站。
二、核对客户端时间,而不只是时区
date -u
openssl version
openssl x509 -in leaf.pem -noout -subject -issuer -dates
notBefore 是生效时间,notAfter 是到期时间。当前时间晚于后者可能报过期,早于前者可能报 certificate is not yet valid。两者不是同一个故障,不能一律归因于续期失败。
显示为 CST 还是 UTC 不会凭空让证书失效,要核对的是实际时间点。检查宿主机、虚拟机的时钟同步;容器共享宿主机内核时钟,重启容器也不会自动校准时间。
时钟确实异常就按环境的时间同步规范修复,别把生产机器日期往回拨——数据库、日志和鉴权都不喜欢这种“时间旅行”。
三、从线上拿证据,不只看磁盘文件
openssl s_client -connect api.example.com:443 \
-servername api.example.com -verify_hostname api.example.com \
-showcerts -verify_return_error </dev/null
-servername 发送 SNI,-verify_hostname 指定验证名称;用途不同,别省成一个。-verify_return_error 让验证错误终止握手,避免仅凭“连上了”误判成功。企业私有 CA 场景应额外指定经过核验的 -CAfile。
-showcerts 展示服务端发来的证书列表,并不等于客户端最终构建、验证通过的链。保存输出中的证书块及错误行,分别查看 subject、issuer、日期和指纹,不要直接把输出中的证书导入信任库。
服务端发送列表通常不含根证书;客户端还可能用本地信任库构建另一条路径,所以“服务器发了三张”不等于“验证恰好用了这三张”。有 CDN 或多节点时,在相同 SNI 下分别确认命中端点。
四、把链分开,定位具体哪一张过期
准备三个来源明确的文件:leaf.pem 只放目标叶子证书,intermediates.pem 放需要的中间证书,trusted-roots.pem 放从可信渠道取得的根证书——它不是从故障网站随手下载就能信任的东西。
openssl verify -show_chain -purpose sslserver \
-verify_hostname api.example.com \
-CAfile trusted-roots.pem \
-untrusted intermediates.pem leaf.pem
-untrusted 在这里表示“可以帮助建链、但不是信任锚”,不表示中间证书一定有问题。成功看 OK 和退出码;失败把错误码、depth 与证书主体一起记录,再对应到单张文件。
| 看到的现象 | 优先检查 | 不要直接下的结论 |
|---|---|---|
| error 10,depth 0 | 叶子到期日及验证时间 | 一定是中间证书问题 |
| error 10,depth 1 | 路径中叶子的签发者 | 网站叶子证书过期 |
| error 9,depth 0 | 叶子notBefore与客户端时间 | 只需补全证书链 |
| unable to get local issuer certificate | 签发链材料与信任库 | 与过期错误完全相同 |
五、离线实验:叶子有效,链仍失败
本次实测用 OpenSSL 1.1.1k 构造同一测试中间 CA 公钥的两张证书:一张有效、一张已过期,叶子仍在有效期内,签名关系不变。有效中间证书参与验证时返回 OK,换成过期版本后,真实错误为 error 10 at 1 depth lookup: certificate has expired。
另一个样本把叶子生效日设在未来,得到 error 9 at 0 depth lookup: certificate is not yet valid。两个样本执行 x509 -checkend 0 时都返回 0:它只回答到期检查,不验证 notBefore、主机名或整条链。
实验还确认,OpenSSL 会检查本次路径中根 CA 证书的有效期;不能把这条实现行为泛化成所有浏览器、所有 TLS 库都完全一致。测试私钥只在内存中生成,证书临时文件已清理;这些是可控样本,不是生产故障统计。
六、用指定时间复现,不改机器日期
# incident_epoch 是已核对的故障时刻 Unix 秒数
openssl verify -attime "$incident_epoch" \
-purpose sslserver -verify_hostname api.example.com \
-CAfile trusted-roots.pem \
-untrusted intermediates.pem leaf.pem
-attime 只改变这次验证使用的参考时刻,不改操作系统时间。本次实验把参考时刻移到中间证书到期前、且叶子已生效的时间段,同一组文件恢复 OK。这能解释“为什么昨天可以,今天不行”。
但它不是生产修复开关,也不是完整历史重放:今天的信任库未必等于故障当时,吊销状态、路径选择与客户端版本都可能变化。留存故障时的公开证书、错误日志与信任库版本,结论才有边界。
七、修复应跟着错误对象走
叶子到期就更新叶子并核验线上指纹;中间证书过期,从 CA 官方取得与当前叶子匹配的可用链,验证后按部署流程更新。名字不能当依据,公钥与签名关系也必须匹配。
只有旧设备失败时,比较它的信任库、TLS 库版本与实际建链结果;多链场景按目标客户端兼容性选择,并让旧客户端真实验证,别在新电脑上点开网页就宣布收工。
别用 curl 的 -k、关闭验证或跳过时间检查当修复,它们只是让报警器不响。改部署配置前先备份、做配置测试,再按既有变更流程执行,本文不要求直接 reload 任何生产服务。
八、验收清单:查日期,也查实际路径
- 错误来自哪个客户端、哪个端点,已明确。
- 客户端真实时间可信,notBefore与notAfter均核对。
- 错误depth对应证书已定位,不只看叶子日期。
- 信任根来源可靠,中间证书未误作信任锚。
- 域名、用途、链与时间验证保持开启,命令返回成功。
- 受影响客户端已复测,多节点没有遗漏,证据脱敏留存。
监控建议分成两项:单张证书到期预警,加上真实客户端的完整握手验证。前者提醒何时换证,后者发现链与环境问题,别让一个绿色到期数字替整条链路作保。
官方参考:OpenSSL验证选项与建链规则、s_client参数说明、x509日期与checkend说明。文档以3.0系列为参考,核心验证行为另以本机1.1.1k实测,实际客户端仍须按自身版本验收。

310

被折叠的 条评论
为什么被折叠?



