Automated Let's Encrypt Deployment: The Certbot Renewal Workflow
Many site owners know this story: the first year, a one-year certificate meant "renew once manually"; the second year, they switched to Let's Encrypt and then forgot to renew — until the site suddenly showed "Your connection is not private." A free certificate with a 90-day lifetime only pays off when renewal is automated. Let's Encrypt certificates default to a 90-day lifetime, so "automatic renewal" is what makes them viable long-term in production.
The 90-day window is not arbitrary: a short lifetime forces regular rotation, so even if a private key leaks, the exposure window stays small. Paired with automation, a short lifetime stops being a burden and becomes a security design in itself.
The official docs stress that Certbot's renew command only renews certificates nearing expiry, so it is safe to run frequently (e.g. daily); it usually performs no actual renewal and does not hit CA rate limits. That means you can safely hand renewal over to a scheduler.
1. Installing Certbot
Most mainstream Linux distributions offer Certbot through their package manager:
# Ubuntu/Debian
apt install certbot python3-certbot-nginx
# RHEL/CentOS
dnf install certbot python3-certbot-nginx
Since Certbot 2.0, ECDSA private keys (secp256r1) are the default; you can explicitly choose --key-type rsa. For more setup details see the Let's Encrypt setup tutorial.
2. Choosing an Authentication Method
Certbot proves domain ownership through ACME challenges. The three main authentication methods are:
| Plugin | Challenge | Use case | Notes |
|---|---|---|---|
| webroot | HTTP-01 | Existing web server | Keeps the server running; recommended |
| standalone | HTTP-01 | No web server | Needs port 80 |
| DNS plugin | DNS-01 | Wildcard certificates | Needs DNS API credentials |
Webroot (keeps the web server running; recommended for existing sites):
certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
Standalone (requires port 80):
certbot certonly --standalone -d example.com
DNS plugin (the only way to issue wildcard certificates; Cloudflare example):
pip install certbot-dns-cloudflare
certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d example.com -d '*.example.com'
To cover multiple subdomains with a single certificate, just add more -d flags, e.g. -d example.com -d shop.example.com -d blog.example.com. The result is one certificate with multiple SAN (Subject Alternative Name) entries that every site can share from the same files.
3. Automatic Renewal
certbot renew inspects all installed certificates and renews those nearing expiry. The docs recommend running it twice daily via cron or a systemd timer:
# Test the renewal flow (does not actually issue)
certbot renew --dry-run
# List managed certificates
certbot certificates
On Debian/Ubuntu installed via apt, a systemd timer is usually preinstalled that runs certbot renew automatically. On other distros you can create one manually:
# /etc/systemd/system/certbot-renew.timer
[Unit]
Description=Run certbot renewal twice daily
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=3600
[Install]
WantedBy=timers.target
Then run systemctl enable --now certbot-renew.timer together with a matching service unit. Add a RandomizedDelaySec to the timer so runs are spread out instead of bunching at the hour. Also schedule --dry-run rehearsals (e.g. weekly) so that even if a real renewal fails, the problem shows up in the logs long before the certificate expires.
4. Making Renewal Effective with Hooks
Successful renewal is only the first step; the web server still needs to load the new certificate. Certbot offers three types of hooks:
--pre-hook: runs before renewal--post-hook: runs after renewal--deploy-hook: runs only after a successful renewal (most common)
certbot renew --deploy-hook "systemctl reload nginx"
For Docker deployments, the reload must target the container, e.g. docker compose exec nginx nginx -s reload. This pairs well with Nginx reverse proxying and Docker Compose production deployment.
Hook directory approach: place executable scripts in /etc/letsencrypt/renewal-hooks/deploy/ and Certbot runs them alphabetically after successful renewal, which is handy when multiple certificates share one deployment script. Newer protocols like ACME ARI automatic renewal make the renewal experience even smoother.
5. Certificate Files and Nginx Configuration
Issued certificates live in /etc/letsencrypt/live/<domain>/:
fullchain.pem: server certificate plus intermediate chain, used for Nginxssl_certificateprivkey.pem: private key, must stay secret, mapped tossl_certificate_keychain.pem: intermediate chain only, used for OCSP stapling viassl_trusted_certificate
The corresponding Nginx block looks like:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}
Never commit the private key to a Git repository or bake it into a container image; inject it via secret management instead, as covered in environment variables and configuration management.
6. Monitoring and Troubleshooting
Automatic renewal occasionally fails (DNS record changes, network issues, etc.). We recommend:
- Rehearsing periodically with
certbot renew --dry-run; - Configuring monitoring to alert when a certificate nears expiry (e.g. under 14 days), wired into monitoring and alerting;
- Watching the expiry reminder emails sent by Let's Encrypt;
- Knowing the rate limits: at most five certificates per week per registered domain, so repeated failed retries can hit the ceiling — another reason to let
renewrun on its own instead of hammering it manually.
Verify the live certificate is actually updated by checking whether the expiry has been pushed forward:
echo | openssl s_client -servername example.com \
-connect example.com:443 2>/dev/null | openssl x509 -noout -enddate
If notAfter shows a date about 90 days out, both renewal and deployment are working.
Common issues: if renew reports "Failed to connect," check whether port 80/443 is occupied (a standalone conflict); if DNS-01 fails, check the API token permissions and TXT record propagation delay; if the page still serves the old certificate after renewal, the deploy hook probably did not run successfully.
For certificate types and renewal protocol developments, see the SSL certificate types guide and ACME ARI automatic renewal.
16IDC Note
The "free + 90-day + auto-renew" model of Let's Encrypt turns certificate management from a once-a-year manual chore into "configure once, keep running". For independent sites and small teams, it drives both the cost and the mental overhead of HTTPS very low, and the hooks mechanism makes the whole issue-deploy-reload chain genuinely unattended. It stays this painless precisely because of the "short lifetime plus automation" design: the shorter the certificate lives, the more it forces you to run the pipeline and hang monitoring on it. As long as --dry-run rehearsals and expiry monitoring are part of daily operations, this workflow is dependable enough for production.
Reference: https://eff-certbot.readthedocs.io/en/stable/using.html , https://letsencrypt.org/docs/rate-limits/