Python Ssl Certificate Expiration Checker

Written by

in

Keeping your website’s SSL certificate valid is a non‑negotiable part of maintaining trust, SEO rankings, and uninterrupted service. An expired certificate can trigger scary browser warnings, drive visitors away, and even cause search engines to demote your site. Fortunately, Python makes it easy to automate the tedious task of checking certificate expiry dates before they become a problem. In this guide you’ll learn how to build a robust Python SSL certificate expiration checker, scale it for dozens of domains, and schedule it to run automatically.

Why Monitoring SSL Certificate Expiration Matters

SSL/TLS certificates serve three critical purposes:

  • Encryption: Protects data in transit from eavesdropping.
  • Authentication: Confirms that users are connecting to the intended server.
  • Trust & SEO: Browsers display a padlock icon, and search engines reward secure sites with higher rankings.

When a certificate expires, browsers display a red “Your connection is not private” warning, which can:

  1. Reduce conversion rates by up to 30%.
  2. Trigger automated security alerts in monitoring tools.
  3. Result in a temporary loss of SEO value as Google flags the site as “not secure”.

Proactive monitoring eliminates these risks, giving you enough time to renew certificates before they lapse.

How Python Can Help You Stay Ahead

Python’s standard library already includes the ssl and socket modules, which are sufficient for a basic checker. For more advanced features—like handling SNI (Server Name Indication) or parsing complex certificate chains—you can add cryptography or pyOpenSSL. The language’s readability and extensive ecosystem make it perfect for both one‑off scripts and production‑grade monitoring tools.

Building a Simple SSL Expiration Checker

Step 1: Establish a Secure Connection

First, create a TCP socket and wrap it in an SSL context. The context should request the server’s certificate without performing full verification (we only need the metadata).

Step 2: Retrieve the Certificate

Once the handshake succeeds, the getpeercert() method returns a dictionary containing the certificate’s fields, including the notAfter date.

Step 3: Parse the Expiration Date

The notAfter value is a string in the format ‘Jun 30 12:00:00 2025 GMT’. Using datetime.strptime we can convert it to a datetime object.

Step 4: Calculate Days Until Expiration

Subtract the current UTC time from the parsed expiration date to get the remaining days. If the result is negative, the certificate has already expired.

Full Python Script

import socket
import ssl
from datetime import datetime, timezone

def get_ssl_expiration(host: str, port: int = 443) -> int:
    """Return the number of days until the SSL certificate expires."""
    context = ssl.create_default_context()
    # Disable certificate verification – we only need the dates
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE

    with socket.create_connection((host, port), timeout=10) as sock:
        with context.wrap_socket(sock, server_hostname=host) as ssock:
            cert = ssock.getpeercert()
    
    # Extract the 'notAfter' field and convert to datetime
    expires_str = cert.get('notAfter')
    expires_dt = datetime.strptime(expires_str, '%b %d %H:%M:%S %Y %Z')
    expires_dt = expires_dt.replace(tzinfo=timezone.utc)

    # Calculate remaining days
    remaining = (expires_dt - datetime.now(timezone.utc)).days
    return remaining

if __name__ == '__main__':
    domain = input('Enter domain (e.g., example.com): ').strip()
    days_left = get_ssl_expiration(domain)
    if days_left < 0:
        print(f'⚠️  The SSL certificate for {domain} has EXPIRED!')
    else:
        print(f'✅  {domain} certificate expires in {days_left} day(s).')

This script works for a single host and prints a clear status message. You can integrate it into larger workflows, send email alerts, or push metrics to monitoring platforms.

Scaling the Checker for Multiple Hosts

In production environments you often need to monitor dozens or hundreds of domains. Below are key enhancements you can add:

  • Batch processing: Read a list of hosts from a CSV or JSON file.
  • Parallel execution: Use concurrent.futures.ThreadPoolExecutor to speed up checks.
  • Alerting: Send Slack messages, email notifications, or create tickets when a certificate is within a threshold (e.g., 30 days).
  • Logging: Write results to a log file or a database for historical analysis.

Example: Checking a CSV List in Parallel

import csv
import concurrent.futures
from pathlib import Path

THRESHOLD_DAYS = 30   # Alert when less than this many days remain

def check_and_report(host):
    days = get_ssl_expiration(host)
    status = 'OK' if days >= THRESHOLD_DAYS else 'ALERT'
    return host, days, status

def main(csv_path: str):
    hosts = [row['domain'] for row in csv.DictReader(open(csv_path))]
    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
        results = list(executor.map(check_and_report, hosts))

    for host, days, status in results:
        print(f'{host}: {days} day(s) left – {status}')

if __name__ == '__main__':
    csv_file = Path('domains.csv')
    if csv_file.is_file():
        main(str(csv_file))
    else:
        print('❌ CSV file not found.')

Save your domains in a domains.csv file with a header named domain. The script will output each host’s remaining days and flag any that need immediate attention.

Automating Checks with Cron or Windows Task Scheduler

Running the script manually defeats the purpose of proactive monitoring. Schedule it to run at a convenient interval:

  • Linux/macOS (cron):
    # Edit crontab with: crontab -e
    0 9 * * * /usr/bin/python3 /path/to/ssl_check.py >> /var/log/ssl_check.log 2>&1

    This runs the script every day at 09:00 UTC and appends output to a log file.

  • Windows (Task Scheduler):
    1. Open Task Scheduler → Create Basic Task.
    2. Set trigger to “Daily” and choose a time.
    3. Action → “Start a program”, browse to python.exe, and add the script path as an argument.
    4. Finish and enable the task.

Common Pitfalls and Troubleshooting

1. Dealing with SNI (Server Name Indication)

Modern servers host multiple certificates on a single IP address. If you omit the server_hostname argument in wrap_socket, you might receive the default certificate instead of the one for your domain. Always pass the target host name to enable SNI.

2. Handling Self‑Signed or Expired Certificates

When verify_mode is set to ssl.CERT_NONE, the handshake succeeds even for self‑signed or expired certificates. This is intentional for an expiration check, but you should still log the issuer and subject fields if you need to differentiate between trusted and untrusted certs.

3. Network Timeouts and Firewalls

Some environments block outbound port 443 or restrict DNS resolution. Use a short timeout (e.g., 10 seconds) and catch socket.timeout and ssl.SSLError exceptions to avoid script crashes. Example:

try:
    days = get_ssl_expiration(host)
except (socket.timeout, ssl.SSLError) as e:
    print(f'⚠️  Could not retrieve cert for {host}: {e}')

4. Timezone Mismatches

The notAfter field is always expressed in GMT. Ensure you compare it with datetime.now(timezone.utc)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *