Notes

Occasional write-ups on systems, networks, and tooling.

Automating certificate renewal without surprises

2026-09-30

Renewal that works when you test it and fails three months later is the classic failure mode. Two habits prevent it.

Test against staging first

Let's Encrypt rate limits are strict enough that a botched production attempt can block you for a week. Run the same command against staging:

certbot --nginx -d example.com --dry-run

--dry-run exercises the full challenge flow without consuming the real quota.

Make sure the reload actually happens

certbot renews the files on disk. Whether your web server notices is a separate question. Add a deploy hook:

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

Hooks run only when a certificate was actually renewed, so this is cheap.

Verify the timer is armed

systemctl list-timers | grep certbot
systemctl status certbot.timer

If certbot.timer is inactive, renewal depends on a cron entry that may not exist. Check both.

Watch the expiry

A one-line check that catches everything:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -dates

Thirty seconds now beats a two-hour outage at the wrong moment.