手里域名和站点多了之后,SSL 证书过期是最容易被忽略的故障点。分享一个只用 Python 标准库实现的证书过期检查脚本:不依赖任何第三方包,直连 443 端口读取证书有效期,剩余天数不足阈值就推送告警,配合 cron 每天跑一次即可。
原理
用 ssl + socket 直连目标域名的 443 端口,完成 TLS 握手后读取对端证书的 notAfter 字段,换算成剩余天数,小于设定的阈值就触发推送。
代码
import ssl
import socket
import datetime
import json
import urllib.request
import urllib.parse
DOMAINS = ["example.com", "blog.example.com"]
WARN_DAYS = 14
def cert_expire_days(host, port=443):
ctx = ssl.create_default_context()
with socket.create_connection((host, port), timeout=10) as sock:
with ctx.wrap_socket(sock, server_hostname=host) as ssock:
cert = ssock.getpeercert()
expire = datetime.datetime.strptime(cert["notAfter"], "%b %d %H:%M:%S %Y %Z")
return (expire - datetime.datetime.utcnow()).days
def bark_push(key, title, body):
url = "https://api.day.app/{}/{}/{}".format(key, urllib.parse.quote(title), urllib.parse.quote(body))
urllib.request.urlopen(url, timeout=10)
def tg_push(token, chat_id, text):
url = "https://api.telegram.org/bot{}/sendMessage".format(token)
data = json.dumps({"chat_id": chat_id, "text": text}).encode()
req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
urllib.request.urlopen(req, timeout=10)
if __name__ == "__main__":
warnings = []
for d in DOMAINS:
try:
days = cert_expire_days(d)
print("{}: 剩余 {} 天".format(d, days))
if days < WARN_DAYS:
warnings.append("{} 仅剩 {} 天".format(d, days))
except Exception as e:
warnings.append("{} 检查失败: {}".format(d, e))
if warnings:
msg = "\n".join(warnings)
# bark_push("你的 Bark Key", "证书告警", msg)
# tg_push("Bot Token", "Chat ID", "证书告警", msg)
定时运行
加一条 cron,每天早上 8 点检查一次:
0 8 * * * /usr/bin/python3 /root/ssl_check.py >> /var/log/ssl_check.log 2>&1
几点说明
- 只用标准库,不用装 requests,任何有 Python3 的机器上直接能跑。
- create_default_context 会校验服务端证书,遇到自签名或已过期证书会直接抛异常,本身也算一种告警。
- Bark 适合 iOS 推送,Telegram Bot 适合多平台,按需二选一即可。
- 域名多的话可以把 DOMAINS 改成从文本文件读取,方便维护。