Skip to content

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

python
#!/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.

Set up SSL Checker → ← All recipes

Related recipes