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 能看到下一次触发时间。
相关推荐
2501_916007472 天前
苹果商店iOS应用上架费用全面解析:开发者计划与审核费用计算
android·ios·小程序·https·uni-app·iphone·webview
Lsetea2 天前
证书没到期却报certificate has expired:OpenSSL定位中间证书与系统时间
运维·https·ssl证书·openssl·证书链
2501_916008892 天前
如何实现iOS App代码混淆:SwiftShield使用指南
android·ios·小程序·https·uni-app·iphone·webview
SDWAN_Cheap2 天前
HTTPS到底加密了什么?
https·数据加密
2501_916008892 天前
怎么用 Godot 导出 iOS 应用并上架 App Store?签名字段该填什么?
android·ios·小程序·https·uni-app·iphone·webview
Lsetea3 天前
Nginx报No required SSL certificate was sent:mTLS客户端证书排查
运维·nginx·https·ssl·openssl
ZeroNews内网穿透3 天前
内网部署 Halo 建站,用 ZeroNews 免费内网穿透安全发布到公网(无公网 IP 也能 HTTPS 访问)
docker·https·内网穿透·nas·反向代理·halo·zeronews
szephyr4 天前
HTTPS 证书申请与自动续期:acme.sh + Let‘s Encrypt 完整记录
nginx·https·acme.sh·证书续期
Lsetea4 天前
部署证书后提示“证书名称与输入不匹配”:Safari 报错的三层排查
nginx·https·safari·ssl证书·openssl
其实防守也摸鱼5 天前
DeepSeek Harness 开源贡献手记:从入门到合入主线
服务器·数据库·windows·https·ssl