Caddy 中间证书过期 HTTPS 报错排查:3 个方案从救急到根治
【免费下载链接】caddyFast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS项目地址: https://gitcode.com/GitHub_Trending/ca/caddy
线上 Caddy 站点突然弹出"您的连接不是私密连接",openssl一看叶子证书明明还有 80 天有效期,服务却就是发不了正常的 HTTPS 请求。问题不在证书本身,而在证书链里那张不起眼的中间证书——CA 签发的"介绍信"到期了。下面按"确诊 → 病因 → 分级修复"的路径走一遍,全部命令和参数都取自 Caddy 仓库可查的实现。
一条命令确诊:证书链里哪张证书到期了
先用下面这条命令连上你的站点,把服务器实际吐出来的证书链导出来,再查每张证书的到期天数(-days负数就是已过期):
# 导出 Caddy 对外提供的前两张证书(0=叶子, 1=中间证书),查过期剩余天数 openssl s_client -connect 你的域名:443 -showcerts 2>/dev/null \ | openssl x509 -noout -days -checkend 0 -nameopt multiline结果判读分两种:
- 中间证书(第 2 张)天数是负数,而叶子证书还有几十天 → 确诊"中间证书过期",就是本文要解决的问题;
- 叶子证书本身就是负数,或者输出里
unable to get local issuer certificate但各证书日期都正常 → 是证书缺失/不匹配的问题,别走本文路线,先检查tls指令指向的文件和域名是否对得上。
为什么会这样:三张"介绍信"的链条
把证书链想成一串介绍信:浏览器查你的服务器,服务器出示"身份证"(叶子证书),但身份证得由"派出所"(中间证书)盖的章才算数,派出所还得有"公安厅"(根证书)的授权。浏览器手里只预置了根证书,中间那张就是缺的那一环。
链条上任何一张过期、缺章或没被出示,验证都直接失败——哪怕你的身份证还在有效期内。Caddy 默认通过 ACME 协议自动申请证书,正常情况下续签时会顺带拿到 CA 当前有效的中间证书,基本无感。所以这个故障基本只出现在两种情况:
- 你手动写了
tls 证书.crt 密钥.key,静态链文件里的中间证书过期了; - 证书是多年前签的、一直复用,而 CA 提前退役了旧中间证书。
具体哪张证书什么时候失效,可以拿openssl x509 -enddate -noout对链文件逐张确认。
方案 A:救急——重签一次证书(约 5 分钟)
先恢复服务:删掉静态证书配置,让 Caddy 走 ACME 重新申请,CA 会把新的中间证书和叶子证书一起发下来。前提是你的域名能解析到这台机器,且 80、443 端口可达。
# Caddyfile:注释掉原来的静态证书行,恢复自动管理 example.com { tls internal # 对外域名用 ACME(删掉本行即可),内网域名用 internal root * /var/www/html file_server }改完执行caddy reload --config Caddyfile(reload 会零中断热加载新配置),再用确诊那条openssl s_client命令复核:链里每张证书-days都应为正数。
方案 B:彻底修复——把证书交回 Caddy 自动管理
救急之后别手动维护文件了。Caddy 的 TLS 模块默认每 10 分钟扫描一次所有受管证书的到期时间(见 modules/caddytls/automation.go),到期前按证书总寿命的约 1/3 提前续签,续签成功自动热切换,中间证书跟着一起换新。
如果你确实需要调整扫描频率,全局选项renew_interval是真实存在的配置项(默认 10m):
{ renew_interval 10m }注意:别去写auto_https interval 72h这种组合——auto_https只接受off、disable_redirects、disable_certs、ignore_loaded_certs,带 interval 会直接报错。另外storage_clean_interval(默认 24h)控制多久清理一次存储里的过期资产,一般不用动。证书和密钥默认存在/var/lib/caddy,备份这个目录即可。
验证生效:看 Caddy 日志出现obtaining certificate字样,或再跑一次确诊命令确认链已更新。
方案 C:长期预防——30 天到期预警
自动管理不等于不用管,加一道独立探测最稳。下面脚本每天跑一次(crontab 里放0 3 * * * /opt/check_chain.sh),链上任何一张证书剩余不足 30 天就把域名、剩余天数写进告警日志(可接邮件或 IM 推送):
# /opt/check_chain.sh:检测整条证书链,最短剩余天数 < 30 天则告警 certs=$(openssl s_client -connect 你的域名:443 -showcerts 2>/dev/null \ | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p') min=9999 while read -rd '' c <&3; do d=$(echo "$c" | openssl x509 -noout -checkend 0 -days 2>/dev/null \ | openssl x509 -noout -days 2>/dev/null) # 逐张算剩余天数 [ -n "$d" ] && [ "$d" -lt "$min" ] && min=$d done 3<<<"$(echo "$certs" | awk 'BEGIN{RS="-----END CERTIFICATE-----"}')$(echo)" [ "$min" -lt 30 ] && logger -t certwarn "你的域名 证书链最短剩余 ${min} 天"验证生效:手动跑一次脚本,正常情况下无输出、echo $?为 0;可以临时把阈值改大到 9999 观察告警分支是否按预期触发。
常见误区
- 只看叶子证书就下结论:浏览器报"连接不安全"时,链上三张证书任何一张过期都会挂,必须逐张看。
- 改了 Caddyfile 不 reload:配置文件只是模板,
caddy reload之后才生效;改完不执行等于没改。 - 以为自动管理就能彻底不管:Caddy 只在"续签成功"后换新链。如果 80/443 被防火墙挡住、DNS 不解析,续签会静默失败,旧链到期照样挂。
- 手动给的链文件顺序错了:
tls指令里链文件要"叶子在前、根在后",少了中间证书就会出现unable to get local issuer certificate。 - 给内网域名申请 ACME 证书:内网域名用不了 ACME,应写
tls internal,由 Caddy 内置 CA 签发,链天然自洽。
收尾检查清单
- 用
openssl s_client确认链上每张证书剩余天数为正 - Caddyfile 里没有手写的
tls 证书 密钥(除非证书确实由外部系统维护并定期轮换) - 80、443 端口对 CA 可回拨,DNS 解析正确
/var/lib/caddy存储目录已纳入定期备份- 到期预警脚本跑通,且告警通道真实收到过一条测试消息
链上每一环都在线,浏览器就不再拦路。
【免费下载链接】caddyFast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS项目地址: https://gitcode.com/GitHub_Trending/ca/caddy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考