Let's Encrypt — warn before you hit the rate limit
Auto-issuance for 200 subdomains — eventually you hit the Let's Encrypt rate limit (50 certs/week per registrable domain) and sit without HTTPS.
Recipe
#!/usr/bin/env python3
# /opt/le-ratelimit-watch.py — daily cron
# Counts certs issued in the last 7 days from CT logs (crt.sh)
import urllib.request, json, sys, time, os
DOMAIN = os.environ['ROOT_DOMAIN'] # e.g. example.com
LIMIT = 50 # LE limit
WARN = 40 # alert when 80% used
WEBHOOK = os.environ.get('HEARTBEAT_URL', '')
url = f'https://crt.sh/?q=%25.{DOMAIN}&output=json'
req = urllib.request.Request(url, headers={'User-Agent': 'le-ratelimit-watch/1.0'})
data = json.loads(urllib.request.urlopen(req, timeout=30).read())
now = time.time()
recent = sum(1 for r in data
if (now - time.mktime(time.strptime(r['not_before'][:19], '%Y-%m-%dT%H:%M:%S'))) < 7*86400)
if recent >= WARN:
msg = f'LE rate limit: {recent}/{LIMIT} certs issued for {DOMAIN} in 7d'
if WEBHOOK:
urllib.request.urlopen(
urllib.request.Request(WEBHOOK,
data=json.dumps({'text': msg, 'count': recent}).encode(),
headers={'Content-Type': 'application/json'}),
timeout=10)
sys.exit(2)
print(f'OK ({recent}/{LIMIT} certs in 7d)')
Same thing in Enterno.io
Load all 200 hostnames into Enterno SSL Checker — see per-host expiry and a unified dashboard instead of an aggregate via crt.sh that does not tie back to a specific cert.
Related recipes
Caddy usually renews on its own, but once a Let's Encrypt rate-limit broke the cycle and we found out 2 days before expiry. Want a belt-and-braces daily check.
Minimal script that checks an SSL certificate and alerts 14 days before expiry.
Site is on the HSTS preload list, but after an nginx refactor the header is gone. In 3 months the domain will be removed from the preload list. Need a daily check.