
证书过期是真实发生过的翻车
稍微折腾过建站的都见过这一幕:网站好好的,某天浏览器突然报"您的连接不是私密连接",证书过期了。Let's Encrypt 的免费证书只有 90 天有效期,自动续期配好了没问题,但哪天续期脚本挂了、cron 掉了、DNS 验证失败了,你不会第一时间知道——等用户来问,网站已经裸奔了好几天。
这篇写一个证书过期监控脚本:每天自动检查手里所有域名的证书还剩多少天,不足 15 天就告警。纯 Python 标准库,复制粘贴就能跑。
前置知识:会用 SSH 登录自己的 VPS,服务器上有 Python 3(Debian 12 / Ubuntu 24.04 默认自带)。
原理:握手第一步,服务器就把证书交出来了
不需要登录服务器、不需要读任何配置文件。TLS 握手刚开始,服务器就会把自己的证书明文发给客户端(握手阶段证书不加密,这本身就是公开信息),证书里有个 notAfter 字段,写着过期时间。我们要做的就是:连上去、把证书拿下来、算还剩多少天。Python 标准库 ssl 的 getpeercert() 直接返回解析好的证书字典。
完整代码(实测通过)
#!/usr/bin/env python3
"""SSL 证书过期监控:握手读证书 notAfter,剩余天数低于阈值告警。纯标准库。"""
import socket, ssl, datetime
WARN_DAYS = 15 # 剩余天数低于该值告警
DOMAINS = ["blog.yoyoip.top", "www.github.com"] # 换成你自己的域名
def cert_expiry(host, port=443):
ctx = ssl.create_default_context()
with ctx.wrap_socket(socket.socket(), server_hostname=host) as s:
s.settimeout(10)
s.connect((host, port))
cert = s.getpeercert()
not_after = datetime.datetime.strptime(cert["notAfter"], "%b %d %H:%M:%S %Y %Z")
not_after = not_after.replace(tzinfo=datetime.timezone.utc)
now = datetime.datetime.now(datetime.timezone.utc)
return (not_after - now).days, not_after.strftime("%Y-%m-%d")
for d in DOMAINS:
try:
days, exp = cert_expiry(d)
print(f"[{'ALERT' if days <= WARN_DAYS else 'OK'}] {d}: 剩余 {days} 天,过期日 {exp}")
except Exception as e:
print(f"[ERROR] {d}: {e}")
下面是真实运行输出(演示环境经代理连出,时间是北京时间 10 月 9 日 18:15):
[OK] blog.yoyoip.top: 剩余 82 天,过期日 2026-12-30
[OK] www.google.com: 剩余 63 天,过期日 2026-12-11
[OK] github.com: 剩余 51 天,过期日 2026-11-29
第一条是本站自己的证书,Cloudflare 自动续的,还剩 82 天,状态健康。拿自己博客当第一个被监控对象,还挺有仪式感的。
两个细节注意:
- 时区必须统一:
notAfter是 UTC 时间,datetime.now()取的是服务器本地时间(很多服务器时区就是 UTC)。两边都换算成 UTC 再相减,否则差出 8 小时,临界值那天会误报或漏报。 - 异常即告警:连不上、握手失败、证书解析异常都算
[ERROR]输出——监控脚本最忌讳静默失败。之前写过一篇 VPS 健康巡检 + Telegram 告警,讲过同样的道理:没数据比坏数据更危险。
部署:每天跑一次就够了
证书按天算,不需要 5 分钟一轮。crontab -e 加一行:
0 9 * * * /usr/bin/python3 /root/ssl_expiry.py >> /var/log/ssl_expiry.log 2>&1
每天上午 9 点跑一次,输出追加进日志。[ALERT] 行可以用 grep 挑出来,或者接下一步的推送。
往下走的四个方向
- 告警推送:只写日志适合手动看。接 Telegram Bot 推送手机提醒,方案可以照搬那篇 Telegram 告警 的写法,
requests发一条消息的事。 - 域名清单化:域名多了就把
DOMAINS挪到单独的配置文件或环境变量里,脚本本身不动。 - 连带查续期:如果用了 certbot,可以顺手检查
/etc/letsencrypt/renewal/下的配置文件和续期日志,把"续期失败"和"证书快过期"两个信号对上。 - 凑成监控套装:证书监控 + 服务器健康巡检 + 之前的 BSC 解锁监控脚本,三个脚本都是"定时轮询 + 阈值告警 + 落盘日志"的同构套路,放进同一个 cron 里,一台小鸡的运维监控就齐了。
