HackBlue

Tools · Python and Bash for security automation · 08 / 20

Monitoring TLS certificate expiry with a Python script

Connect to your hosts with Python’s ssl module, read certificate expiry and SANs, warn before expiry, and run the check on a schedule.

8 min read · practice only with authorization

Why it matters

Expired certificates cause outages and train users to click through warnings. With shorter maximum certificate lifetimes now in place for public certificates and automation through ACME, monitoring is the safety net that catches renewals that silently failed.

The script

ssl.create_default_context verifies the chain and hostname. getpeercert returns parsed fields including notAfter and subjectAltName. A non-zero exit code makes it easy to alert from cron or CI.

Snippet
#!/usr/bin/env python3
"""Warn when TLS certificates expire soon."""
import socket, ssl, sys, datetime as dt

WARN_DAYS = 21
ctx = ssl.create_default_context()
bad = 0
for target in sys.argv[1:]:
    host, _, port = target.partition(':')
    try:
        with socket.create_connection((host, int(port or 443)), timeout=10) as s:
            with ctx.wrap_socket(s, server_hostname=host) as tls:
                cert = tls.getpeercert()
        expires = dt.datetime.fromtimestamp(ssl.cert_time_to_seconds(cert['notAfter']), dt.timezone.utc)
        days = (expires - dt.datetime.now(dt.timezone.utc)).days
        sans = [v for k, v in cert.get('subjectAltName', []) if k == 'DNS']
        status = 'OK' if days > WARN_DAYS else 'WARN'
        bad += status != 'OK'
        print(f'{status:4} {host:30} {days:4} days  {expires:%Y-%m-%d}  SAN={",".join(sans[:3])}')
    except (ssl.SSLError, ssl.CertificateError, OSError) as e:
        bad += 1
        print(f'FAIL {host:30} {e}')
sys.exit(1 if bad else 0)

Lab: test the edge cases

Run the script against a few sites you operate. Then test failure cases using a lab server: a certificate issued for a different name, an expired lab certificate (create one with openssl req -days 0 or use a public test site intended for this, such as badssl.com), and a self-signed certificate. Confirm each prints FAIL with a clear reason.

Snippet
python3 certwatch.py example.com expired.badssl.com wrong.host.badssl.com self-signed.badssl.com
echo exit=$?

Schedule it

Run daily from a systemd timer, cron, or a CI scheduled workflow and send failures to chat or email. Keep the host list in a file under version control. Monitoring from outside your network also catches cases where an internal proxy hides a problem.

Diagram

Certificate monitoring

  1. Connect

    TLS connection with hostname verification.

  2. Read

    notAfter and SANs from getpeercert().

  3. Decide

    WARN under N days; FAIL on verification errors.

  4. Alert

    Non-zero exit triggers a notification.

Check yourself

1. Which call creates a context that verifies certificate chains and hostnames?

2. Which certificate field gives the expiry date?

3. Why exit with a non-zero code on problems?