Rotate TLS Certificates with an Automated Hook
Issue a certificate with an ACME client, install it behind a load balancer, and wire a post-renewal hook that reloads the server so rotation needs no human step.
Task
Set up automatic TLS certificate renewal so the server reloads the new certificate within minutes of issuance.
When to use this
A certificate expired on Sunday morning because nobody owned the renewal email. The team wants renewal automatic and the reload wired to the renewal event, not a calendar reminder.
Prerequisites
- A domain with DNS pointing at your server
- An ACME account with certbot or lego
- A reload command that does not drop in-flight requests
Steps
1. Request the first certificate with a deploy hook that reloads the server.
certbot certonly --webroot -w /var/www -d example.com --deploy-hook "systemctl reload nginx"Expected output: A certificate is issued under /etc/letsencrypt/live/example.com and nginx reloads without dropping connections.
2. Schedule the renewal timer so it checks twice a day and runs the deploy hook on every successful renewal.
[Timer]
OnCalendar=*-*-* 00,12:00:00
Persistent=true
Expected output: systemctl list-timers shows certbot.timer active, and the next renewal triggers the reload hook automatically.
3. Test the hook by forcing a renewal in dry-run mode and confirming the reload command runs.
certbot renew --dry-runExpected output: The dry run reports a simulated successful renewal and the deploy hook logs a reload event without changing the live certificate.
Troubleshooting
Problem
The reload command drops in-flight requests
Solution
Use nginx -s reload or the equivalent graceful reload for your server, which rebinds without closing active connections.
Problem
The ACME challenge fails because the webroot is not served
Solution
Serve the .well-known/acme-challenge directory from the same server that certbot runs on, or switch to the DNS-01 challenge.
Limitations
- Rate limits on the ACME server cap how often you can request a real certificate; test with --dry-run.
- A failed renewal that nobody notices still leads to an expiry; monitor the certificate expiry as a separate alert.
Renew, then reload
The certificate is the easy part; the reload is what people forget. Wire the reload into the deploy hook so every successful renewal triggers it, and schedule the timer to check twice a day.
[Timer]
OnCalendar=*-*-* 00,12:00:00
Persistent=true
Test without spending a rate limit
Run certbot renew –dry-run to simulate the renewal and confirm the hook fires. The dry run does not change the live certificate, so you can run it as often as you like while you tune the reload command.
certbot renew --dry-run
Expected outcome
The timer renews the certificate before it expires, the deploy hook reloads the server without dropping connections, and a dry run confirms the whole path works. Monitor the certificate expiry separately, so a failed renewal alerts you well before the deadline.