Keeping your web services secure means staying ahead of SSL/TLS certificate expirations. An expired certificate can trigger browser warnings, break API integrations, and damage customer trust—all within seconds of the deadline passing. Fortunately, Python makes it easy to automate the tedious task of checking certificate lifespans across dozens or hundreds of hosts. In this guide you’ll learn how to build a robust Python SSL certificate expiration checker, integrate it with monitoring tools, and schedule regular scans so you never get caught off guard.
Why You Need an SSL Expiration Checker
- Prevent downtime: Browsers block connections to sites with expired certificates, causing immediate service interruptions.
- Maintain compliance: Many regulatory frameworks (PCI‑DSS, HIPAA, GDPR) require timely renewal of TLS certificates.
- Boost customer confidence: A valid padlock icon reassures visitors that their data is protected.
- Scale with growth: Manual checks become impossible as the number of domains and sub‑domains grows.
Core Concepts Behind SSL Certificate Validation
What is an SSL/TLS certificate?
An SSL/TLS certificate binds a public key to a domain name, enabling encrypted communication between a client and a server. The certificate contains metadata such as the Not Before and Not After dates, which define its validity period.
How does Python retrieve certificate data?
Python’s standard library provides two main ways to fetch a certificate:
sslmodule combined withsocket– low‑level, full control over TLS handshake.urllib3orrequests– higher‑level HTTP clients that expose the peer certificate via a response object.
For a dedicated expiration checker, the ssl + socket approach is preferred because it avoids the overhead of an HTTP request and works for any service that speaks TLS, not just HTTP.
Step‑by‑Step: Building the Checker
1. Set up the project environment
# Create a virtual environment (optional but recommended)
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install any third‑party packages (we’ll use tqdm for progress bars)
pip install tqdm
2. Core function to fetch certificate dates
The following function opens a TLS connection to a host, extracts the certificate, and returns the notAfter field as a datetime object.
import socket
import ssl
from datetime import datetime
from typing import Tuple
def get_ssl_expiry(host: str, port: int = 443, timeout: int = 5) -> Tuple[datetime, str]:
"""
Returns the expiry datetime and the raw certificate string for a given host.
Raises socket.timeout or ssl.SSLError on failure.
"""
context = ssl.create_default_context()
with socket.create_connection((host, port), timeout=timeout) as sock:
with context.wrap_socket(sock, server_hostname=host) as ssock:
cert = ssock.getpeercert()
# cert['notAfter'] format: 'Jun 30 12:00:00 2025 GMT'
expiry_str = cert['notAfter']
expiry_date = datetime.strptime(expiry_str, '%b %d %H:%M:%S %Y %Z')
# Return both for optional logging
return expiry_date, ssl.DER_cert_to_PEM_cert(ssock.getpeercert(binary_form=True))
3. Helper to calculate days remaining
from datetime import datetime, timezone
def days_until_expiry(expiry: datetime) -> int:
now = datetime.now(timezone.utc)
delta = expiry - now
return delta.days
4. Main script that processes a list of hosts
Below is a ready‑to‑run script that reads a plain‑text file hosts.txt (one host per line), checks each certificate, and prints a color‑coded report. It also writes JSON output for integration with monitoring platforms.
import json
from pathlib import Path
from tqdm import tqdm
HOSTS_FILE = Path('hosts.txt')
OUTPUT_JSON = Path('ssl_report.json')
WARNING_DAYS = 30 # Threshold for "expiring soon"
def load_hosts() -> list:
return [line.strip() for line in HOSTS_FILE.read_text().splitlines() if line.strip()]
def check_all_hosts(hosts: list) -> list:
results = []
for host in tqdm(hosts, desc='Checking SSL certificates'):
try:
expiry, _pem = get_ssl_expiry(host)
days_left = days_until_expiry(expiry)
status = 'OK'
if days_left < 0:
status = 'EXPIRED'
elif days_left <= WARNING_DAYS:
status = 'EXPIRING_SOON'
results.append({
'host': host,
'expiry': expiry.isoformat(),
'days_left': days_left,
'status': status
})
except Exception as exc:
results.append({
'host': host,
'error': str(exc),
'status': 'ERROR'
})
return results
def print_report(results: list):
for r in results:
if r['status'] == 'OK':
print(f"\033[92m{r['host']}: OK – {r['days_left']} days left\033[0m")
elif r['status'] == 'EXPIRING_SOON':
print(f"\033[93m{r['host']}: EXPIRING SOON – {r['days_left']} days left\033[0m")
elif r['status'] == 'EXPIRED':
print(f"\033[91m{r['host']}: EXPIRED!\033[0m")
else:
print(f"\033[95m{r['host']}: ERROR – {r['error']}\033[0m")
def main():
hosts = load_hosts()
results = check_all_hosts(hosts)
print_report(results)
OUTPUT_JSON.write_text(json.dumps(results, indent=2))
print(f"\nReport saved to {OUTPUT_JSON}")
if __name__ == '__main__':
main()
5. Sample hosts.txt file
example.com
api.myservice.io
mail.google.com
expired.badssl.com
Enhancing the Checker for Production Use
- Parallel execution: Use
concurrent.futures.ThreadPoolExecutorto check dozens of hosts simultaneously, reducing total runtime. - Alerting integration: Push the JSON report to Slack, Microsoft Teams, or PagerDuty via webhook when
statusisEXPIRING_SOONorEXPIRED. - Certificate pinning support: Store the PEM hash of a known good certificate and compare it on each run to detect unauthorized replacements.
- Support for non‑standard ports: Extend the host list to include
host:portpairs for services like SMTP (port 587) or LDAP (port 636). - Logging & persistence: Write daily snapshots to a time‑series database (InfluxDB, Prometheus) for trend analysis.
Scheduling the Checker with Cron (Linux) or Task Scheduler (Windows)
Linux (cron)
# Edit the crontab for the user that runs the script
crontab -e
# Run the checker every day at 02:00 AM
0 2 * * * /usr/bin/python3 /path/to/ssl_checker.py >> /var/log/ssl_check.log 2>&1
Windows (Task Scheduler)
- Open Task Scheduler and create a new task.
- Set the trigger to “Daily” and choose a convenient time.
- In the “Action” tab, point to
python.exeand add the script path as an argument. - Enable “Run whether user is logged on or not” and store the password.
Integrating with Popular Monitoring Platforms
Prometheus Exporter
Expose each certificate’s days_left as a gauge metric. A minimal Flask exporter could look like this:
from flask import Flask, Response
from prometheus_client import Gauge, generate_latest, CollectorRegistry
app = Flask(__name__)
registry = CollectorRegistry()
days_gauge = Gauge('ssl_certificate_days_left',
'Number of days until SSL certificate expires',
['host'], registry=registry)
@app.route('/metrics')
def metrics():
for result in check_all_hosts(load_hosts()):
if result['status'] in ('OK', 'EXPIRING_SOON'):
days_gauge.labels(host=result['host']).set(result['days_left'])
return Response(generate_latest(registry), mimetype='text/plain')
Slack Notification Example
import requests
SLACK_WEBHOOK = 'https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX'
def send_slack_alert(message: str):
payload = {'text': message}
requests.post(SLACK_WEBHOOK, json=payload)
# Inside the main loop, after checking:
if r['status'] == 'EXPIRING_SOON':
send_slack_alert(f":warning: *{r['host']}* expires in {r['days_left']} days.")
elif r['status'] == 'EXPIRED':
send_slack_alert(f":x: *{r['host']}* has *expired*!")
Testing and Debugging Tips
- Use
openssl s_client -connect example.com:443 -servername example.comto manually inspect a certificate and compare with the script output. - Run the script with
PYTHONVERBOSE=1to see low
Leave a Reply