SSL证书快过期怎么自动检查:用OpenSSL checkend做7天预警与线上证书验收

证书这东西很安静。它快过期的时候,Nginx 不会弹提醒,只会默默把访客那条 HTTPS 请求挡在门外。等你发现,往往已经是用户先来报障了。与其靠回忆续证,不如让系统在到期前自己盯一遍。核心就是 OpenSSL 的两个命令:查本地文件的 x509,和从线上拉真实证书的 s_client

证书快过期,Nginx 不会提前告诉你

证书的有效期写在 notAfter 字段,浏览器和 Nginx 都只认这个时间。更稳妥的做法,是把「还剩几天」变成定时检查,同时看本地和线上两处,因为这两边真不一定一致。

先看清楚:本地证书文件还剩几天

本地证书一般是服务器上的 PEM 文件,先读出有效期:

复制代码
openssl x509 -in /etc/nginx/ssl/example.com/fullchain.pem -noout -dates

输出会给出 notBefore(生效)和 notAfter(到期),注意别指向错误目录里的旧证书。

checkend 的退出码:0 和 非0 分别是什么意思

脚本不能只读日期,x509-checkend 可直接用退出码判断,详见OpenSSL 官方文档:指定秒数内到期返回非零,否则返回零。

复制代码
openssl x509 -in /etc/nginx/ssl/example.com/fullchain.pem -checkend 604800 -noout; echo $?

也就是说 -checkend 返回 0 代表「安全」,返回非 0 代表「该动手了」,和「0 表示成功」的习惯相反,写脚本判断时要取反。这是新手最容易看走眼的点,建议先手动跑一次把退出码看清楚。

604800 从哪来:7 天阈值怎么定

为什么是 604800?因为检查以「秒」为单位,7 天就是 7 × 24 × 60 × 60 秒:

复制代码
echo $((7 * 24 * 60 * 60))   # 604800

阈值 604800 代表「提前 7 天」,想更保守就改成 14 天 1209600。7 天能少打扰,又留出处理时间。

从线上看:s_client 拿到真实下发的证书

检查本地文件有个漏洞:线上真正生效的,未必就是你服务器上那份文件。域名前面挂了 CDN、负载均衡、WAF 时,真正下发给访客的是它们那份证书。所以还得「从访客的视角」连一次线上。这个用 s_client,参数见OpenSSL 官方 s_client 文档

复制代码
echo | openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts 2>/dev/null \
  | openssl x509 -noout -dates

-servername 会在 ClientHello 中带上 SNI,让服务器按域名选证书;少了它,多域名服务器可能返回默认站证书。注意用 echo | 或重定向收尾,避免 s_client 一直等输入。

把检查写成脚本:本地 + 线上一起跑

把两步合成脚本,同时检查本地文件和线上证书:

复制代码
#!/usr/bin/env bash
HOST="www.example.com"; PORT="443"; THRESHOLD=604800
CERT="/etc/nginx/ssl/example.com/fullchain.pem"

# 1) 本地文件
if [ -f "$CERT" ]; then
  if openssl x509 -in "$CERT" -checkend $THRESHOLD -noout 2>/dev/null; then
    echo "OK   本地剩余有效期超过 7 天"
  else
    echo "WARN 本地将在 7 天内到期,需要续证"
  fi
else
  echo "WARN 找不到本地证书:$CERT"
fi

# 2) 线上真实下发
REMOTE=$(echo | openssl s_client -connect "$HOST:$PORT" -servername "$HOST" -showcerts 2>/dev/null \
  | openssl x509 -noout -dates 2>/dev/null)
[ -n "$REMOTE" ] && echo "线上 $HOST 有效期:$REMOTE" \
  || echo "WARN 无法从线上取到证书(检查 SNI / 端口 / 网络)"

脚本里 if openssl ... -checkend; then 正是利用前面的退出码:只有剩余时间还够才进「OK」分支。重点是「文件不存在」「证书损坏」「网络取不到」这些异常都要当 WARN 处理,不能静默退出。

用 systemd 定时触发:service + timer 配对

接下来让它每天自动跑。systemd 用「service + timer」配对,timer 到点触发 service。OnCalendarPersistentRandomizedDelaySec官方手册

复制代码
# /etc/systemd/system/cert-check.service
[Unit]
Description=Check certificate expiry
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/check_cert.sh

# /etc/systemd/system/cert-check.timer
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=30min

[Install]
WantedBy=timers.target

OnCalendar=*-*-* 03:00:00 是每天凌晨 3 点。Persistent=true 会存上次触发时间,机器断电漏跑了下次开机补上。RandomizedDelaySec=30min 在 3 点基础上随机偏移最多 30 分钟,避免一堆定时任务整点挤一起。先 daemon-reload 再 enable:

复制代码
sudo systemctl daemon-reload
sudo systemctl enable --now cert-check.timer
systemctl list-timers cert-check.timer    # 看下一次触发时间

常见误判与验收清单

这套东西跑起来不难,容易踩的是「看似正常、其实没对」。整理成一个表:

你看到的 背后真实原因 怎么验
本地证书还早,线上却提示快过期 CDN / 负载均衡 / WAF 用的是另一张证书 s_client -servername 对比域名和源站的 notAfter
证书能打开,但某个子域名报错 单域名或 SAN 里没带这个域名 查证书 Subject Alternative Name 是否包含该域名
域名解析到 CDN,拿到的是回源证书 握手对象不对,SNI 指到了别处 指定 -servername,确认连的是对外那一侧

最后核对一遍,都满足才算真做对了:

  • 本地证书能正常 openssl x509 -in ... -dates 读出 notAfter,且 -checkend 604800 未到期返回 0。
  • 线上能通过 s_client -servername 拿到目标域名真实证书,不是默认站证书。
  • 脚本在本地证书缺失、文件损坏、网络取不到时都能给出非 0 或 WARN,不假装成功。
  • systemd service 用 Type=oneshot,timer 带 Persistent=true,手动 systemctl start cert-check.service 能执行,list-timers 能看到下一次触发时间。
相关推荐
MetaLite2 小时前
HTTPS之外的响应加密-GCM序列化与处理器顺序
网络协议·http·https
Neighbor_OldY3 小时前
云上证书过期与HTTPS安全治理实战:证书到期没人管、自动化续期与证书链排查
安全·https·自动化
美酒没故事°3 小时前
是tls、https、waf
网络协议·http·https
hzxpaipai4 小时前
企业官网 Nginx 部署完整流程:HTTPS、301、静态缓存与日志配置
nginx·缓存·https
2501_9151063218 小时前
安卓抓包软件2026,免证书抓包 应用层抓包 代理抓包全解析
网络协议·计算机网络·网络安全·ios·adb·https·udp
Sagittarius_A*1 天前
协议与应用基础(三):HTTPS、证书链与中间人攻击
网络协议·http·信息安全·https·密码学·中间人攻击·证书链
MyFreeIT2 天前
Let‘s Encrypt SSL 製作免費證書
网络协议·https·ssl
00后程序员张2 天前
Android证书绑定抓包失败?Android SSL Pinning绕过实战指南
android·网络协议·计算机网络·网络安全·adb·https·ssl
唐古乌梁海2 天前
传输协议https-TCP
网络协议·tcp/ip·https