Automating Let's Encrypt So You Never Think About Certificates Again
Advertisement
Let's Encrypt certificates last exactly 90 days. That is short enough to make manual renewals unrealistic, meaning automation is effectively mandatory. Most servers I take over already have certbot renew tucked away in a cron job or a systemd timer (Certbot's installer does this by default).
But that's only half the job. What is almost always missing is the rest of the picture: ensuring the renewed certificate actually gets picked up by the web server, properly handling wildcard certificates, and finding out about silent renewal failures before the certificate expires.
The reload step people forget
When Certbot renews a certificate, it writes the new files to /etc/letsencrypt/live/example.com/. It does not, on its own, tell Nginx, Postfix, or whatever else is using that certificate to start serving the new file.
The fix is a deploy hook—a script Certbot runs immediately after a successful renewal:
certbot renew --deploy-hook "systemctl reload nginx"
For a setup where multiple services share certificates, the deploy hook can handle everything at once:
#!/bin/bash
# /etc/letsencrypt/renewal-hooks/deploy/reload-services.sh
systemctl reload nginx
systemctl reload postfix
systemctl reload dovecot
Anything dropped in /etc/letsencrypt/renewal-hooks/deploy/ runs automatically after every successful renewal. It's a one-time setup step, not a sticky note on your monitor every 90 days.
Wildcard certificates need DNS-01
The default HTTP-01 challenge (where Certbot temporarily serves a file over port 80) does not work for wildcard certificates. Wildcards require the DNS-01 challenge, which proves domain ownership by creating a temporary TXT record. For DNS providers with a Certbot plugin, this is completely automatable:
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d example.com -d '*.example.com'
The credentials file holds an API token scoped only to DNS edits. Certbot creates the TXT record, waits for it to propagate globally, and removes it once validation completes—zero manual steps. Set this up even if you don't need a wildcard right now. The moment a client asks for a wildcard, the alternative is a manual DNS-01 challenge every 90 days, which is exactly the kind of recurring chore that eventually gets forgotten.
Catching failures before the browser does
Silent renewal failure is the actual risk here. Certbot might fail for weeks before a certificate finally expires. Since it retries on every run, if nothing is watching for that failure, the very first sign of trouble is a full-screen browser warning on a production site.
A simple bash check, run weekly, catches this early:
#!/bin/bash
# cert-expiry-check.sh
for domain in example.com mail.example.com; do
expiry=$(echo | openssl s_client -servername "$domain" -connect "$domain:443" 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
days=$(( ($(date -d "$expiry" +%s) - $(date +%s)) / 86400 ))
if [ "$days" -lt 14 ]; then
echo "WARNING: $domain certificate expires in $days days"
fi
done
Certbot's default renewal threshold is 30 days before expiration. A certificate sitting at 14 days is a massive red flag that renewals have been silently failing. The culprit is almost always a new firewall rule blocking the HTTP-01 challenge, an expired DNS API token for DNS-01, or a domain that was repointed elsewhere without anyone updating Certbot's config.
What "automated" actually means
A cron job that exists but doesn't reload services, can't handle wildcards, and has no way to alert you on failure isn't automated. It is a fragile process with a human still implicitly in the loop—you just won't know it until a customer screams.
True automation means the certificate renews, the service picks it up, and you get alerted if anything breaks along the way, all without anyone needing to remember a 90-day cycle. Once you set it up right, SSL certificates become one of the very few things on a server that genuinely take care of themselves.
Advertisement