§ Host operations — keeping the box alive
Renewed certs that never get served — the certbot deploy hook
The wildcard *.oaoisme.top certificate renews automatically. But a quiet gap
meant a freshly renewed cert would have sat on disk unused — nginx would keep
serving the old one until something reloaded it by hand.
The gap
Renewal here uses a DNS-01 manual hook (Namecheap) — see the cert/DNS notes:
# /etc/letsencrypt/renewal/oaoisme.top.conf
authenticator = manual
manual_auth_hook = /usr/local/bin/namecheap-dns-hook.py auth
manual_cleanup_hook = /usr/local/bin/namecheap-dns-hook.py cleanup
Those hooks handle issuance (prove domain control via a TXT record). Nothing in that path tells nginx to pick up the new certificate. And the deploy-hook directory was empty:
ls /etc/letsencrypt/renewal-hooks/deploy/ # → (empty)
So the sequence after a successful certbot renew would have been: new
fullchain.pem written → nginx still holding the old cert in memory → clients
served the old cert until the next unrelated reload or reboot. For a cert that
renews roughly every 60 days, that window is not hypothetical.
The fix: a deploy hook that reloads nginx
certbot runs everything in renewal-hooks/deploy/ after each successful
renewal, once per renewed lineage, with $RENEWED_LINEAGE set. Validate before
reloading so a bad edit can never take nginx down on renewal:
# /etc/letsencrypt/renewal-hooks/deploy/10-reload-nginx.sh (chmod 755)
#!/bin/sh
if nginx -t 2>/dev/null; then
systemctl reload nginx
echo "deploy-hook: reloaded nginx after cert renewal ($RENEWED_LINEAGE)"
else
echo "deploy-hook: nginx -t FAILED, skipped reload" >&2
exit 1
fi
Test it without waiting 60 days for a real renewal:
RENEWED_LINEAGE=/etc/letsencrypt/live/oaoisme.top \
/etc/letsencrypt/renewal-hooks/deploy/10-reload-nginx.sh
# → deploy-hook: reloaded nginx after cert renewal (/etc/letsencrypt/live/oaoisme.top)
deploy vs pre vs post
renewal-hooks/ has three sibling dirs and they fire at different times:
pre/— before every renewal attempt (even if nothing renews).post/— after every attempt (even if nothing renewed).deploy/— only when a cert was actually renewed, per lineage. This is the one that should reload services — it won't churn nginx on the many no-opcertbot.timerruns that renew nothing.
Generalise it
Any service that caches a certificate in memory (nginx, most reverse proxies,
mail servers) needs a deploy hook to reload it after renewal — issuance and
service-reload are separate concerns, and the DNS-01/manual path only covers the
first. If you use certbot --nginx, the installer handles reload for you; with
authenticator = manual (as here) it does not, and the empty deploy/ dir is
a silent trap.