Automating certificate renewal without taking the service down

Let's Encrypt certificates are short-lived by design — 90 days — which means renewal isn't an occasional chore, it's a recurring operational requirement. If it's not automated and monitored, it will eventually fail silently and you'll find out when a browser shows a warning page to actual visitors.

Standalone vs webroot

Certbot's standalone authenticator spins up its own temporary web server on port 80 to answer the ACME HTTP-01 challenge. That's simple and works great for a first-time issuance on a box with nothing else running — but it requires port 80 to be free, which means stopping nginx (or whatever else is bound there) for the duration of the request:

systemctl stop nginx
certbot certonly --standalone -d example.com -d www.example.com
systemctl start nginx

That's fine for a one-off manual issuance. It's a bad idea for automated renewal, because it means your web server goes down briefly every renewal cycle, and if the certbot step fails for any reason, you've now got a service that's stopped and a script that may not reliably bring it back.

The webroot authenticator solves this by writing the challenge file to a path your already-running web server serves directly, so nothing needs to stop:

certbot certonly --webroot -w /var/www/certbot -d example.com

This needs a bit of nginx config so /.well-known/acme-challenge/ resolves to that directory before you request anything, but once it's there, renewal becomes a zero-downtime operation.

The renewal cron job isn't the whole story

Modern certbot installs typically ship a systemd timer that handles the renewal check automatically — systemctl list-timers | grep certbot will show it if so. It runs certbot renew roughly twice a day, which itself only actually renews certificates within 30 days of expiry, so most runs are no-ops. That part is usually fine out of the box.

The part people miss is the deploy hook — the step that reloads whatever's actually using the certificate after a renewal happens. Certbot renewing the certificate files on disk does nothing for a running nginx process, which has the old certificate loaded in memory and will keep serving it until reloaded.

# /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/sh
systemctl reload nginx

Make it executable (chmod +x), drop it in the deploy hooks directory, and certbot will run it automatically after any successful renewal — but only after a successful one. If a renewal fails silently for weeks (DNS changed, firewall rule got tightened, whatever), the hook never fires and you won't know until the old cert actually expires.

Actually monitoring it

A dry run tells you whether the current setup would succeed, without touching your real certificate:

certbot renew --dry-run

Running that manually every so often, or wiring it into whatever monitoring already pages you for other things, closes the gap a silent renewal failure leaves open. The whole point of automating this is removing it from your mental checklist — but automation you never verify is just a different way of hoping.

← Back to all posts