网站突然打不开,浏览器提示”您的连接不是私密连接”,十有八九是 SSL(安全传输协议)证书过期了。Let’s Encrypt 证书有效期只有 90 天,虽然 certbot 会通过定时任务自动续期,但续期失败往往悄无声息——cron 日志没人看、邮件通知被忽略,直到用户投诉才发现问题。这篇文章教你如何搭建一套完整的续期监控方案:用脚本检测所有域名的证书剩余天数,在到期前 30 天、14 天、7 天分级告警,即使 certbot 静默失败也能提前收到通知。

为什么证书会”自动续期却仍然过期”
很多人以为装了 certbot 就万事大吉,实际上自动续期存在多个失效点。我们处理过的真实案例中,最常见的原因有三个:
一是 80 端口被占用导致 HTTP-01 验证失败。某客户在服务器上新增了一个占用 80 端口的服务后,certbot 的 renew 验证全部失败,持续了 60 天无人发现。二是 DNS(域名解析系统)记录变更后未同步,比如网站迁移到 VPS(虚拟专用服务器)后只迁移了网页文件,没检查证书配置,旧的续期配置指向已失效的验证路径。三是 Python 运行环境损坏,certbot 依赖的系统包升级后与现有环境冲突,续期命令直接报错退出。
这些问题共同的特点是:certbot 的 cron 日志里其实写了错误,但没有人主动去查。所以监控的重点不是”检查证书是否过期”,而是”在过期前足够早的时间点发现续期异常”。
检测脚本:读取证书剩余天数
核心思路是用 openssl 读取证书的实际到期时间,而不是信任 certbot 自己的记录。以下是完整的检测脚本,保存为 /usr/local/bin/check_cert_expiry.sh:
#!/bin/bash
# 检查指定域名证书的剩余有效天数
DOMAINS="www.example.com shop.example.com"
ALERT_EMAIL="admin@example.com"
WARN_DAYS=30
for domain in $DOMAINS; do
END_DATE=$(echo | openssl s_client -servername "$domain" \
-connect "$domain":443 2>/dev/null \
| openssl x509 -noout -enddate \
| cut -d= -f2)
if [ -z "$END_DATE" ]; then
echo "$(date '+%F %T') [ERROR] $domain 无法读取证书" >> /var/log/cert-check.log
continue
fi
END_TS=$(date -d "$END_DATE" +%s)
NOW_TS=$(date +%s)
DAYS_LEFT=$(( (END_TS - NOW_TS) / 86400 ))
echo "$(date '+%F %T') [INFO] $domain 剩余 ${DAYS_LEFT} 天" >> /var/log/cert-check.log
if [ "$DAYS_LEFT" -lt "$WARN_DAYS" ]; then
echo "$domain 证书剩余 ${DAYS_DAYS} 天,请检查续期状态" \
| mail -s "证书告警: $domain" "$ALERT_EMAIL"
fi
done
这个脚本有两个设计要点。第一,它走真实的 443 端口握手读取线上证书,能发现”本地证书续期成功但 nginx 没有重新加载”这类配置不同步问题。第二,日志写入 /var/log/cert-check.log,每次运行都留下时间戳记录,方便事后回溯证书是在哪一天开始没有续上的。
部署 cron 定时检测
脚本写好后,用 cron 让它每天自动运行一次。执行 crontab -e 添加:
0 8 * * * /usr/local/bin/check_cert_expiry.sh
每天早上 8 点检查一遍。如果你想更稳妥,可以在续期窗口(Let’s Encrypt 建议每 60 天续期一次)前后各加一次检查,改为每 12 小时运行一次:0 8,20 * * *。
部署后先手动跑一次验证:bash /usr/local/bin/check_cert_expiry.sh && tail /var/log/cert-check.log。确认日志中每个域名都有正常的剩余天数记录,再交给 cron。很多服务器没配置 mail 命令,告警会静默丢失,这一步可以用 echo test | mail -s test admin@example.com 验证邮件通道是否真的可用。

对于托管在虚拟主机上的网站,用户没有 root 权限也无法自行部署 cron,这类环境证书由服务商统一管理。如果你正在选型,Hostease 虚拟主机默认提供自动续期的 SSL(安全传输协议)证书,适合不想自己维护证书链路的中小站点。
分级告警:把”到期了”变成”快到期了”
单一阈值的告警有两个缺陷:设得太晚(比如 3 天)会来不及处理,设得太早(比如 45 天)会导致告警被反复触发、逐渐麻木。更好的做法是分级告警,在脚本中用阶梯逻辑实现:
if [ "$DAYS_LEFT" -le 7 ]; then
LEVEL="CRITICAL"
elif [ "$DAYS_LEFT" -le 14 ]; then
LEVEL="WARNING"
elif [ "$DAYS_LEFT" -le 30 ]; then
LEVEL="NOTICE"
fi
三级阈值对应的处理节奏是:NOTICE 级别(剩余 30 天以内)记录日志并每日提醒,此时正常执行一次 certbot renew 就能解决;WARNING 级别(14 天以内)说明上次自动续期可能已经失败,需要登录服务器检查 certbot renew --dry-run 的输出,重点看 80/443 端口和 DNS 验证是否正常;CRITICAL 级别(7 天以内)必须立即人工介入,手动续期并检查 nginx/apache 是否重新加载了新证书。
实际部署时建议把 CRITICAL 告警同时发到两个渠道(比如邮件加即时通讯 webhook),因为证书过期造成的访客流失是实打实的——浏览器拦截页面会让超过 80% 的访客直接离开。

进阶:用 certbot 钩子主动捕获续期失败
前面的方案是”事后检测”,还可以再往前走一步,在续期失败的第一时间就收到通知。certbot 提供了 --deploy-hook 和失败钩子机制,在 /etc/letsencrypt/renewal-hooks/ 目录下可以放置三类脚本:pre(续期前)、post(续期后)、deploy(新证书部署成功后)。
最实用的是 deploy hook,用来自动重载 web 服务器,避免”证书已续期但服务还在用旧证书”的问题:
# /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/bash
systemctl reload nginx
chmod +x 赋予执行权限后,每次 certbot 成功续期都会自动重载 nginx。而对于失败捕获,可以在 cron 中改为调用 certbot renew 2>&1 | mail -s "certbot 续期结果" admin@example.com,让每次续期无论成败都发一封邮件——成功时邮件是例行确认,没收到邮件或邮件里有报错就是需要关注的信号。
两种方案结合后的监控链路是:certbot 续期时钩子自动重载服务并邮件回报结果,独立的检测脚本每天核对线上证书剩余天数,服务器运维相关文章里还有更多 Web 服务器维护的细节可以参考。双层机制下,即使某一层失效,另一层仍能在证书过期前 30 天把问题暴露出来。
总结
Let’s Encrypt 续期监控的本质是给自动化流程加上人工可感知的安全网。核心动作有三步:用 openssl 脚本每天检测线上证书剩余天数,用分级阈值(30/14/7 天)区分告警优先级,用 certbot 钩子在续期失败的第一时间发出通知。整套方案只需要一个 shell 脚本加一条 cron 记录,10 分钟就能部署完成。
如果你需要现成的运维环境支持,可以考虑 Hostease 的 VPS 主机方案,root 权限完整,可以自由部署 certbot 和自定义监控脚本;管理多台机器时,这套检测脚本也可以统一跑在 独立服务器的运维流程里做集中告警;如果是企业站点,我们推荐把证书检查纳入每周例行巡检清单,与备份验证一起执行。建议现在就登录一台服务器,先手动跑一遍本文的检测脚本,看看你的域名证书还剩多少天。