把 /usr/local/nginx/conf 替换成你的证书实际存放地址,识别pem crt 过滤根证书和中间件
#!/usr/bin/env python3
# 查看证书到期时间
import subprocess, unicodedata
from pathlib import Path
from datetime import datetime, timezone
CERT_DIR = Path("/usr/local/nginx/conf")
def display_width(text):
width = 0
for char in str(text):
if unicodedata.combining(char): continue
width += 2 if unicodedata.east_asian_width(char) in ("W", "F") else 1
return width
def pad(text, width, align="left"):
text = str(text)
spaces = max(0, width - display_width(text))
return (" " * spaces + text) if align == "right" else (text + " " * spaces)
def get_cert_blocks(file):
try: content = file.read_text(errors="ignore")
except Exception: return []
start, end, blocks, pos = "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----", [], 0
while True:
begin = content.find(start, pos)
if begin == -1: break
finish = content.find(end, begin)
if finish == -1: break
finish += len(end)
blocks.append(content[begin:finish])
pos = finish
return blocks
def parse_cert(cert_pem):
try:
result = subprocess.run(
["openssl", "x509", "-noout", "-subject", "-dates", "-ext", "subjectAltName"],
input=cert_pem, text=True, capture_output=True, timeout=5)
if result.returncode != 0: return None
output, domains = result.stdout, []
for line in output.splitlines():
if "DNS:" not in line: continue
for part in line.split(","):
part = part.strip()
if part.startswith("DNS:"):
domain = part[4:].strip()
if domain and domain not in domains: domains.append(domain)
if not domains: return None
expire_str = next((line.split("=", 1)[1].strip() for line in output.splitlines() if line.startswith("notAfter=")), None)
if not expire_str: return None
expire_time = datetime.strptime(expire_str, "%b %d %H:%M:%S %Y %Z").replace(tzinfo=timezone.utc)
days = int((expire_time - datetime.now(timezone.utc)).total_seconds() / 86400)
return {"domain": ", ".join(domains), "expire": expire_time.strftime("%Y-%m-%d %H:%M:%S UTC"), "days": days, "expired": days < 0}
except Exception: return None
def main():
if not CERT_DIR.exists():
print(f"目录不存在: {CERT_DIR}"); return
files = sorted(f for f in CERT_DIR.iterdir() if f.is_file() and f.suffix.lower() in [".pem", ".crt"] and "dhparam" not in f.name.lower())
W_FILE, W_DOMAIN, W_EXPIRE, W_DAYS, W_STATUS = 36, 48, 25, 10, 12
header = pad("文件", W_FILE) + " " + pad("域名", W_DOMAIN) + " " + pad("到期日期", W_EXPIRE) + " " + pad("剩余天数", W_DAYS, "right") + " " + pad("状态", W_STATUS)
print(header)
print("-" * display_width(header))
for file in files:
for cert_pem in get_cert_blocks(file):
info = parse_cert(cert_pem)
if not info: continue
if info["expired"]: status = "❌ 已过期"
elif info["days"] <= 30: status = "⚠️ 30天内到期"
else: status = "✅ 有效"
print(pad(file.name, W_FILE) + " " + pad(info["domain"], W_DOMAIN) + " " + pad(info["expire"], W_EXPIRE) + " " + pad(info["days"], W_DAYS, "right") + " " + pad(status, W_STATUS))
if __name__ == "__main__":
main()
效果如下
root@rockylinux:~# python3 ssl.py
文件 域名 到期日期 剩余天数 状态
-------------------------------------------------------------------------------------------------------------------------------------------
123.pem 123.com, *.123.com 2026-12-11 04:59:59 UTC 66 ✅ 有效