Monitor SSL Certificate Expiry with a Free API
SSL certificates expire. When they do, your site shows a browser warning and users leave. Most teams discover expired certificates from customer complaints. Here's how to discover them before that happens.
Quick Check
curl -s "https://hermesforge.dev/api/ssl?domain=example.com" | python3 -m json.tool
Response:
{
"domain": "example.com",
"valid": true,
"issuer": "DigiCert Inc",
"subject": "*.example.com",
"not_before": "2025-01-15T00:00:00Z",
"not_after": "2026-02-15T23:59:59Z",
"days_remaining": 335,
"protocol": "TLSv1.3",
"cipher": "TLS_AES_256_GCM_SHA384"
}
The days_remaining field tells you exactly how long you have.
Python: Multi-Domain Monitoring
import requests
import json
from datetime import datetime
DOMAINS = [
"yoursite.com",
"api.yoursite.com",
"staging.yoursite.com",
"admin.yoursite.com",
]
ALERT_THRESHOLD_DAYS = 30
def check_ssl(domain):
"""Check SSL certificate for a single domain."""
resp = requests.get(
"https://hermesforge.dev/api/ssl",
params={"domain": domain},
)
return resp.json()
results = []
alerts = []
for domain in DOMAINS:
data = check_ssl(domain)
results.append(data)
days = data.get("days_remaining", 0)
valid = data.get("valid", False)
if not valid:
alerts.append(f"EXPIRED: {domain} — certificate is invalid")
elif days < ALERT_THRESHOLD_DAYS:
alerts.append(f"EXPIRING: {domain} — {days} days remaining")
print(f"{domain}: {'valid' if valid else 'INVALID'}, {days} days remaining")
if alerts:
print("\n--- ALERTS ---")
for alert in alerts:
print(f" {alert}")
Scheduled Monitoring with Cron
Run checks daily and log results:
#!/usr/bin/env python3
"""ssl_monitor.py — daily SSL certificate monitoring"""
import requests
import json
from datetime import datetime
DOMAINS = ["yoursite.com", "api.yoursite.com"]
LOG_FILE = "/opt/monitoring/ssl.jsonl"
ALERT_DAYS = 14
for domain in DOMAINS:
data = requests.get(
"https://hermesforge.dev/api/ssl",
params={"domain": domain},
).json()
entry = {
"domain": domain,
"checked_at": datetime.utcnow().isoformat() + "Z",
"valid": data.get("valid", False),
"days_remaining": data.get("days_remaining", 0),
"issuer": data.get("issuer", "unknown"),
}
with open(LOG_FILE, "a") as f:
f.write(json.dumps(entry) + "\n")
if not entry["valid"]:
print(f"CRITICAL: {domain} SSL certificate INVALID")
elif entry["days_remaining"] < ALERT_DAYS:
print(f"WARNING: {domain} SSL expires in {entry['days_remaining']} days")
Crontab:
0 8 * * * python3 /opt/monitoring/ssl_monitor.py
Slack Alerts
Send notifications when certificates are close to expiring:
import requests
SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
def send_slack_alert(domain, days_remaining):
emoji = "🔴" if days_remaining < 7 else "🟡"
text = f"{emoji} SSL Alert: *{domain}* expires in *{days_remaining} days*"
requests.post(SLACK_WEBHOOK, json={"text": text})
# After checking each domain:
if days_remaining < 30:
send_slack_alert(domain, days_remaining)
CI/CD Integration
Block deployments if SSL certificates are about to expire:
#!/bin/bash
DOMAIN="yoursite.com"
MIN_DAYS=7
DAYS=$(curl -s "https://hermesforge.dev/api/ssl?domain=${DOMAIN}" | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('days_remaining',0))")
echo "SSL certificate for ${DOMAIN}: ${DAYS} days remaining"
if [ "$DAYS" -lt "$MIN_DAYS" ]; then
echo "FAIL: SSL certificate expires in ${DAYS} days (minimum: ${MIN_DAYS})"
exit 1
fi
GitHub Actions Workflow
name: SSL Certificate Check
on:
schedule:
- cron: '0 8 * * 1' # Every Monday at 8am
workflow_dispatch:
jobs:
check-ssl:
runs-on: ubuntu-latest
strategy:
matrix:
domain: [yoursite.com, api.yoursite.com]
steps:
- name: Check SSL Certificate
run: |
RESULT=$(curl -s "https://hermesforge.dev/api/ssl?domain=${{ matrix.domain }}")
DAYS=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('days_remaining',0))")
VALID=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('valid',False))")
echo "Domain: ${{ matrix.domain }}"
echo "Valid: $VALID"
echo "Days remaining: $DAYS"
if [ "$VALID" != "True" ]; then
echo "::error::SSL certificate for ${{ matrix.domain }} is INVALID"
exit 1
fi
if [ "$DAYS" -lt 14 ]; then
echo "::warning::SSL certificate for ${{ matrix.domain }} expires in $DAYS days"
fi
Tracking Expiry Over Time
Parse the JSONL log to spot trends:
import json
from collections import defaultdict
def ssl_report(log_file):
"""Generate SSL expiry report from monitoring log."""
latest = {}
with open(log_file) as f:
for line in f:
entry = json.loads(line)
domain = entry["domain"]
latest[domain] = entry
print("SSL Certificate Status Report")
print("=" * 50)
for domain, data in sorted(latest.items()):
days = data["days_remaining"]
status = "VALID" if data["valid"] else "EXPIRED"
if days < 7:
indicator = "CRITICAL"
elif days < 30:
indicator = "WARNING"
else:
indicator = "OK"
print(f" {domain}: {status}, {days} days remaining [{indicator}]")
ssl_report("/opt/monitoring/ssl.jsonl")
Rate Limits
| Tier | Limit | Cost |
|---|---|---|
| Anonymous | 5/min, 20/day | Free |
| API Key | 50/day | Free |
| Pro | 1000/day | $9 / 30 days |
Get a free API key:
curl -X POST "https://hermesforge.dev/api/keys" -H "Content-Type: application/json" -d '{}'
The SSL endpoint has a higher anonymous daily limit (20/day) than other APIs because certificate checks are lightweight and commonly needed for monitoring.