Let's Encrypt with Certbot, Including Renewal That Doesn't Break
Introduction¶
Certbot issues a free 90-day certificate in seconds. The real task is making renewal — which runs unattended, forever — actually reload your server and survive config changes. This covers issuance and, more importantly, the renewal setup that lasts.
Concepts¶
Let's Encrypt proves you control a domain via a challenge:
- HTTP-01 (webroot) — you serve a token file at
http://domain/.well-known/acme-challenge/<token>. Simple, but needs port 80 reachable and can't do wildcards. - DNS-01 — you publish a TXT record at
_acme-challenge.domain. Works behind a firewall, works for wildcards, but needs API access to your DNS provider.
Certbot stores everything under /etc/letsencrypt/:
live/<domain>/ (symlinks to the current cert), archive/ (all versions),
renewal/<domain>.conf (how to renew this cert — the challenge method,
webroot path, plugin).
Hands-on¶
Issue with webroot (no downtime, no Certbot touching your config)¶
sudo certbot certonly --webroot -w /var/www/certbot \
-d example.com -d www.example.com \
--agree-tos -m [email protected] --no-eff-email
Your web server needs one location for the challenge (keep this in the HTTP
server {} even after redirecting everything else to HTTPS):
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
Then reference fullchain.pem and privkey.pem from
/etc/letsencrypt/live/example.com/ in your TLS config.
Issue a wildcard with DNS-01¶
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d 'example.com' -d '*.example.com'
cloudflare.ini holds a scoped API token; chmod 600 it. Plugins exist for
Route 53, DigitalOcean, Google, and generic --manual + a hook script.
Renewal — the part that matters¶
The certbot package installs a systemd timer (or cron job) that runs
certbot renew twice a day. renew only acts on certs within 30 days of
expiry, and it re-uses the method recorded in renewal/<domain>.conf.
systemctl list-timers | grep certbot
sudo certbot renew --dry-run # simulate — do this after ANY change
Make the server pick up the new cert. Renewal writes new files but your running Nginx/Apache still holds the old one in memory. Add a deploy hook:
# /etc/letsencrypt/renewal-hooks/deploy/reload-web.sh
#!/bin/sh
systemctl reload nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-web.sh
Anything in renewal-hooks/deploy/ runs only when a cert was actually renewed,
for each renewed cert. pre/ and post/ hooks run every attempt.
Verification and troubleshooting¶
sudo certbot certificates # every cert, its domains, expiry
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate -issuer
--dry-runfails with "Invalid response ... 404" — the challenge file isn't served. Test manually:curl http://example.com/.well-known/acme-challenge/testafterecho test > /var/www/certbot/.well-known/acme-challenge/test. Usually a redirect-to-HTTPS catching the ACME path too, or the wrongroot.too many certificates already issued— the rate limit is 5 duplicate certs (same exact domain set) per week. Don't loopcertbot certonly; userenew. During testing use--staging.unauthorized ... DNS problemon DNS-01 — the API token lacks edit permission on the zone, or DNS propagation is slower than certbot's wait. Add--dns-<provider>-propagation-seconds 60.- Renewal timer runs but cert never updates — the timer is disabled
(
systemctl enable --now certbot.timer), orrenewerrors silently. Run it by hand and read the output. - New cert issued, site still serves the old one — no deploy hook, or the
hook failed.
journalctl -u certbotand checkrenewal-hooks/deploy/. certbotrenewed but for the wrong domains — therenewal/*.confstill lists an old domain set. Re-runcertonlywith--cert-nameand the correct-dlist to rewrite it, or edit the conf and--dry-run.- Wildcard cert but
certbot renewwants HTTP-01 — the original issue used DNS-01 but the conf lost the plugin lines. Re-issue with the DNS flags.
Related tools and reading¶
- On-site: SSL/TLS Checker, Certificate Decoder, CSR Generator, HSTS Preload Checker.
- Related posts: An Nginx reverse proxy with TLS, The TLS 1.3 handshake.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Web & Mail Servers
Running BIND 9 as an Authoritative Server
BIND as an authoritative server: zone files, secured AXFR with TSIG, SOA serial rules, and DNSSEC inline-signing. Here's a working master/slave setup.
October 24, 2026Varnish Cache in Front of a Site
Varnish is a caching HTTP accelerator configured in VCL. Here's the request lifecycle, why you strip cookies, how bans work, and grace mode for serving stale during backend trouble.
October 24, 2026HTTP Basic Auth with htpasswd on Nginx and Apache
The right way to put a password in front of a staging site or an internal endpoint — bcrypt hashes, per-location rules, and an IP-or-password combination.
October 24, 2026