Scheduling TLS certificate renewal

Certbot/acme.sh already schedule themselves — here's what's left for you to actually own.

The problem

If you're using certbot, it already installed a systemd timer (certbot.timer) or a cron entry in /etc/cron.d/certbot during package install, running twice daily and renewing certificates only when they're within 30 days of expiry. acme.sh does the same via its own cron entry created during `--install-cron`. Adding a second, independent renewal cron job is the common mistake: two renewal processes can race for the same domain's HTTP-01 challenge or ACME account lock, and one fails.

The actual gap most teams have isn't renewal — it's the reload step. A cert renews fine but nginx or haproxy never reloads to pick up the new file, so the service keeps serving the old certificate until a manual restart, right up until it expires anyway.

Check for an existing certbot/acme.sh timer before adding one.

Run `systemctl list-timers | grep certbot` or `crontab -l` for the acme.sh user first. If one exists, don't add a second — instead make sure its `--deploy-hook` (certbot) or `--reloadcmd` (acme.sh) actually reloads the service that terminates TLS. That's the piece that's usually missing, not the renewal schedule itself.

Recommended schedule

0 3,15 * * *

Twice daily is certbot's own default cadence — frequent enough that a transient failure (rate limit, DNS hiccup) gets retried well before the 30-day renewal window closes. Only needed if you're not using the packaged certbot cron/timer at all, e.g. a custom ACME client.

Example crontab entry

# norc: cert-renew Certbot renewal with nginx reload
0 3,15 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx" >> /var/log/certbot-renew.log 2>&1

Failure modes to watch for

  • Duplicate renewal jobs (packaged cron/timer plus a hand-added one) racing for the same ACME account lock, causing one to fail with a lock-file error.
  • Renewal succeeds but no deploy-hook/reloadcmd reloads the terminating service — the new cert sits on disk unused until expiry takes down the old one.
  • HTTP-01 challenge fails silently behind a load balancer or CDN that doesn't route /.well-known/acme-challenge/ to the renewing host.
  • Renewal email notifications going to an inbox nobody reads, so a renewal that's been failing for weeks is only discovered when the site goes down.

How norc helps

norc shows renewal-job run history alongside every other scheduled task on the box, so a certbot cron failure surfaces in the same place as everything else instead of an easily-ignored renewal email.