直接用Python标准库ssl和socket即可获取域名SSL证书过期时间,需创建SSL上下文、设置超时、调用getpeercert()提取notAfter字段,并用datetime.strptime()解析为UTC时间计算剩余天数,同时捕获DNS解析、连接超时及证书异常等错误。

直接用 ssl 模块 + socket 就能查,不需要装额外包,也不依赖 OpenSSL 命令行工具——但必须处理好超时、域名解析失败、证书不可达这三类常见阻断点。
如何用标准库获取单个域名的证书过期时间
核心是建立 TLS 连接后调用 getpeercert(),它返回一个字典,其中 'notAfter' 字段就是 PEM 格式的时间字符串。注意:必须显式启用 SSL 上下文并设置 check_hostname=False,否则遇到 IP 直连或 SAN 不匹配会抛 ssl.CertificateError。
实操建议:
- 用
ssl.create_default_context()创建上下文,避免手动配置协议版本 - 连接必须设
timeout=5,否则 DNS 卡住或服务无响应会导致脚本挂死 -
getpeercert()返回的时间是 UTC,需用ssl._ssl._parse_asn1_time()(私有函数,但稳定可用)或datetime.strptime()解析,推荐后者:用datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z') - 如果服务器不支持 TLS 1.2+,
create_default_context()可能握手失败,此时需降级:context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)改为ssl.PROTOCOL_TLS
批量巡检时如何避免一个域名失败导致整个脚本中断
每个域名必须独立 try/catch,且不能共用 socket 连接对象。常见错误是把所有域名塞进一个 for 循环却没包 try...except,结果遇到 socket.gaierror(DNS 失败)或 ConnectionRefusedError 就退出。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 对每个域名单独建
with socket.create_connection(...)和context.wrap_socket(),不要复用 socket 或 SSLSocket 实例 - 捕获的具体异常至少包括:
socket.gaierror(域名无法解析)、socket.timeout(连接超时)、ssl.SSLError(TLS 握手失败)、ValueError(证书时间格式异常) - 记录失败原因时,用
str(e)而非e.__class__.__name__,因为像ssl.SSLCertVerificationError的 message 里含具体验证失败项(如“hostname 'x' doesn't match ...”)
怎样把结果结构化输出并识别高风险证书
过期时间本身没意义,关键是比较当前时间。别用 datetime.now(),要用 datetime.utcnow(),否则时区错位会导致误判提前 8 小时过期。
实操建议:
- 计算剩余天数用
(cert_not_after - datetime.utcnow()).days,结果为负数即已过期 - 阈值建议分档:≤7 天标“紧急”,≤30 天标“预警”,其余标“正常”
- 输出格式优先选 CSV(方便 Excel 打开)或 JSON(方便后续接入告警系统),字段至少包含:
domain、expires_at、days_left、status、error(失败时填原因) - 避免在循环里实时写文件,先存 list,最后一次性写入,防止某次写入失败导致部分数据丢失
真正麻烦的是那些用 CDN 或反向代理的域名——你查到的可能是 CDN 提供商的证书,不是源站的。如果业务要求必须查源站,得先解析出真实 IP(比如跳过 CDN 的 A 记录),再对 IP + SNI 域名做直连,这部分需要额外处理 DNS 和 SNI 参数,容易被忽略。


















