6 min read Grounded in docs/architecture.md Renewal

Auto-renewal and key rotation

A certificate is a 90-day commitment, every 90 days, forever. CertMate's scheduler handles the renewal arithmetic, but the operational discipline around it — monitoring failures, rotating keys, surviving a DNS API outage — is yours. Here's the contract.

Let's Encrypt certificates are valid for 90 days. The community consensus, baked into every ACME client, is to renew at 60 days — giving you a 30-day buffer before expiry to notice and fix a broken renewal. CertMate follows that consensus. The interesting questions aren't "when does it renew" but "what happens when it doesn't, and how do you know."

The scheduler

CertMate's background scheduler runs an APScheduler job. Every certificate has a per-record renewal_threshold_days (default 30, override per cert). The scheduler wakes regularly — frequently enough that a freshly expiring certificate is picked up within hours, not days — walks the certificate list, and for each one whose days_until_expiry <= renewal_threshold_days AND auto_renew == true, fires a renewal.

Renewals always preserve the shape that was in effect at creation time: certbot persists --key-type, --rsa-key-size and --elliptic-curve into its own renewal/<domain>.conf during the first issuance, and certbot renew --cert-name <domain> reuses those values automatically. You don't have to remember whether you originally asked for RSA 4096 or ECDSA P-256; certbot does.

The per-certificate knobs

FieldDefaultPurpose
auto_renewtrueMaster switch per certificate.
renewal_threshold_days30Renew when expiry is within N days. Some operators set this lower (14) for low-traffic certs to minimize unnecessary issuance; some set it higher (45) when the deploy hook is heavy.
dns_provider + dns_account_idat-creation valuesRenewals use the same DNS path that issued the cert. Switch providers by PATCHing the record before the next renewal window.

Force a renewal

If you need a renewal now — most commonly because a key compromise was suspected, or because a deploy hook bug means the live cert is out of sync with the on-disk cert — POST to the per-cert renew endpoint:

curl -X POST http://localhost:8000/api/certificates/example.com/renew \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "force": true }'

Without force, CertMate refuses to renew a certificate whose expiry is still far enough away — Let's Encrypt has rate limits (50 certs / domain / week, 5 duplicates / week) and a renewal loop bug is one of the few ways to hit them.

Key rotation: same name, new private key

Every renewal in CertMate is what certbot calls a "true renewal" — it generates a fresh private key, builds a new CSR, and gets a new certificate from the CA. The certificate's serial number changes, the public key changes, the private key changes. The CN and SAN list don't.

This means you don't need a separate "rotate the key" workflow. You rotate the key by forcing a renewal:

curl -X POST http://localhost:8000/api/certificates/example.com/renew \
  -H "Authorization: Bearer $TOKEN" \
  -d '{ "force": true, "reason": "scheduled-key-rotation" }'

The reason field lands in the audit log alongside the new serial number, which is helpful when an auditor asks "when was the key for this cert last rotated."

When auto-renewal fails

The four failure modes you'll actually see, in rough order of frequency:

DNS API credential expired or rotated

Someone rotated the Cloudflare token in the Cloudflare UI without updating CertMate. The next renewal attempt returns 401 from Cloudflare's API. CertMate writes a failed audit row with the upstream message and stops retrying for an exponential back-off window.

The only way you notice this in time is by watching the audit log or wiring a deploy hook on a synthetic revoked event. CertMate is honest about this — the v2.5.5 release notes call out audit log monitoring as the operator's responsibility.

CAA record drift

Someone added a CAA record that excludes Let's Encrypt. Renewal worked last week. This week, it doesn't. CertMate surfaces the CA's exact error message in the audit log — the words "CAA record prevents issuance" are unambiguous enough that the fix is obvious.

Let's Encrypt rate limit

Usually a sign of a renewal loop, not legitimate traffic. The audit log will show many failed renewals for the same domain in a short window. Stop the loop, wait out the week-long rate-limit window, and configure stricter renewal thresholds going forward.

DNS provider outage

Rare but real. CertMate's behavior is to retry with the back-off schedule baked into the certbot plugin (typically 3 attempts over a few minutes). If the outage outlasts that, the renewal fails and is retried on the next scheduler pass — usually several hours later. For a sub-30-day expiry buffer this is fine; for a tight renewal window you'll want to pin to a DR account (multi-account DNS) or delegate validation to a more reliable provider (CNAME delegation).

What you should monitor

Two queries against the audit log give you most of the operational signal you need:

  • Recent failed renewals. Anything with operation=renew and status=error in the last 24 hours. A single failure is recoverable on the next pass; the same cert failing three times running is a paging event.
  • Certs within 14 days of expiry that haven't renewed. A renewal that hasn't fired by then is almost always a config issue (auto_renew off, or threshold misconfigured). Query the cert list and filter on days_until_expiry < 14 && auto_renew.

The conversational sidecar's /expiring 14 slash command runs the second query for you. The first one is just a ranged audit-log read against /api/audit?operation=renew&status=error.

Disabling auto-renewal

Sometimes you don't want a certificate auto-renewed — a one-off cert for a migration, a deprecated service, a cert that's being moved off CertMate. PATCH the record:

curl -X POST http://localhost:8000/api/certificates/example.com/auto-renew \
  -H "Authorization: Bearer $TOKEN" \
  -d '{ "enabled": false }'

CertMate keeps the certificate on disk (you still control when to delete it) but stops touching it. The audit log records who turned auto-renewal off and when.