Skip to content
ssldays

Guide

Certbot deploy hooks and post-renewal reloads

Wire certbot deploy hooks to copy PEMs and reload nginx, Apache, or hooks—renew success is not live TLS until reload.

Updated Apr 2, 2026 824 words

Deploy hooks turn certbot renew success into live TLS—without them, PEMs update on disk while listeners keep old chains.

When this happens

letsencrypt.log shows deploy hook exit code 1 after otherwise successful renew. Hooks succeed on canary but production hook points at a path only root can read while the timer runs unprivileged. Hooks that call configuration management pull can fail when CM servers are down during expiry week, masking the underlying renew success.

You switched from nginx plugin to webroot and dropped hooks accidentally. Immutable infrastructure rebuilds may omit hook directories unless cloud-init recreates them. Blue/green deploys may run hooks only on the active color while passive color still serves TLS to a slice of anycast traffic.

First response

Wire certbot deploy hooks to copy PEMs and reload nginx, Apache, or hooks—renew success is not live TLS until reload.

Searches for certbot deploy hook reload usually mean automated renewal failed, succeeded locally but never reached the TLS listener, or hit Let's Encrypt rate limits. The fix is almost always permissions, challenge reachability, or a missing reload—not buying a longer-lived commercial cert as the first move.

For Certbot deploy hooks and post-renewal reloads, read the ACME or panel error verbatim, run a dry-run renewal if your stack supports it, then confirm what certificate strangers receive on port 443 after you deploy. Certificate files on disk lie; only live handshakes matter for users.

Renew hooks are the difference between a green letsencrypt.log and browsers that still see last month's serial. Certbot runs deploy-hook only when a certificate is actually replaced, while renew-hook runs every attempt. Miswired hooks that call reload on failure hide real errors behind noisy restarts. Triage should capture timestamps, account URLs, and the last Boulder error type before infrastructure changes widen blast radius.

See also ACME DNS-01 challenge propagation delays and ACME HTTP-01 challenge failed 403.

Why renewal failed

renew-hook runs every attempt; deploy-hook runs only when a new cert is written. Use idempotent hooks: copying with cmp -s before reload avoids flapping workers when renew legitimately makes no file changes. Hooks are production code: review, test, and roll back like application releases.

Hooks inherit a minimal environment—PATH and permissions differ from your shell. Secrets in hooks belong in root-only files with minimal commands in renewal configs—never inline API keys in world-readable renewal conf. Least privilege for hook users and scripts limits blast radius when renew misfires.

Copy-then-reload patterns must target the paths daemons actually read. Version hooks in git with renewal conf references pinned by commit hash comments. Escalate with timestamps, hostnames, and serial comparisons attached so the next engineer does not repeat already-failed renew attempts.

Hooks are shell snippets in renewal config or CLI flags. They must copy PEMs if services do not read live paths directly, then reload daemons. Permissions, shebangs, and PATH inside systemd timers differ from interactive shells—scripts that work manually often fail under cron. Clarify in tickets whether failure is validation, issuance, deploy, or listener presentment—teams too often mix these phases when reading logs. Treat hook failures as severity-one: users experience expiry even when Boulder returns success, because presentation is the product TLS customers negotiate.

Hooks are not optional when the web server does not read straight from /etc/letsencrypt/live. They are not the same as certbot's apache/nginx plugins unless you still use those authenticators exclusively. A green renew line without hook stderr review is incomplete triage; operators should grep letsencrypt.log for hook exit codes on every renewal ticket.

Common causes

These patterns drive most certbot deploy hook reload tickets. They explain why automation logs look green while browsers or APIs still break.

deploy-hook omitted after migrating from apache plugin to webroot.

Hook called docker restart without publishing mounts to the container nginx uses.

Quotes in INI renewal files broke bash -c one-liners. SELinux transitions on RHEL block hooks from writing to /etc/pki without fcontext updates.

Diagnose

Run dry-run and inspect hook stdout/stderr in letsencrypt.log. Include hook script exit code and duration metrics in change records so performance regressions in hooks are caught before expiry week.

certbot renew --dry-run
grep -E 'Hook|deploy-hook|renew-hook' /var/log/letsencrypt/letsencrypt.log | tail

Run renew with --deploy-hook verbose script logging to syslog. Compare hook user to file ownership on destination PEM paths. Capture echo $PATH and user id inside hook via logger for timer versus interactive diffs. If hooks call systemctl reload, capture systemctl show timestamps proving the unit reloaded after certbot exit.

Fix step-by-step

Follow this sequence for certbot deploy hooks and post-renewal reloads. Capture command output after each step so the next responder can pick up without rerunning destructive changes.

  1. Inventory /etc/letsencrypt/renewal/*.conf hook directives.
  2. Centralize scripts under /etc/letsencrypt/renewal-hooks/deploy/.
  3. Ensure scripts exit non-zero only on real failures.
  4. Copy fullchain and privkey if services do not read live paths.
  5. Run nginx -t or apachectl -t before reload in the hook.
  6. Dry-run then production renew.
  7. Compare external openssl serial.
  8. Version-control hook scripts in git.
  9. Archive redacted ACME logs and openssl transcripts in the ticket before closing.
  10. Schedule a follow-up dry-run before the next natural renewal window.

Verify the live certificate

OpenSSL

echo | openssl s_client -connect HOST:443 -servername HOST 2>/dev/null | openssl x509 -noout -dates -subject -issuer

Replace HOST with the public hostname on the certificate. Run the same command after any reload or deploy change; acceptance means notAfter moved forward and serial changed versus the incident-opening capture.

How to read the output

After hook execution, external openssl serial must change when certbot reports success. After hook execution, openssl should be run from CI located off-site to detect hooks that reload only loopback-facing nginx instances.

curl

curl -vI --connect-timeout 10 https://HOST/ 2>&1 | grep -Ei 'expire|SSL certificate|subject:|issuer:'

Replace HOST with the public hostname users request.

Roll out and verify

Inventory renewal configs in /etc/letsencrypt/renewal/*.conf and standardize hook scripts in /etc/letsencrypt/renewal-hooks/.

Test hooks with certbot renew --dry-run and inspect logs for hook exit code zero. Stage hook changes on a canary host sharing the same vhost layout before fleet-wide timer runs. Pair hook deploys with canary listeners if your platform supports weight shifting, validating serial on canary before fleet reload.

Version-control hook scripts and alert when dry-run fails before real expiry.

What to automate

CI should execute hook scripts against fixture PEMs on each change. Dry-run hooks from CI whenever deploy scripts change.

ssldays verifies strangers see the renewed notAfter after hook changes ship. ssldays validates post-hook TLS presentation on canary hosts first.

Frequently asked questions

Systemd environment?

Use absolute paths and EnvironmentFile if hooks need secrets. Paste hook script SHA256 in the change record.

Docker?

Hook may need docker exec reload inside the container mounting TLS volumes. Paste hook script SHA256 in the change record.

Leaked hook output?

Avoid echoing private keys; log serial and exit codes only. Paste hook script SHA256 in the change record.

What belongs in hooks versus configuration management?

Hooks should be thin: copy if needed, test config, reload. CM should own desired PEM paths and permissions. Thick logic in hooks without CI tests causes fragile renewals. Keep hooks idempotent and fast so timer jitter does not overlap long-running deploys.

Monitor this hostname on a schedule

Live certificate checks and renewal reminders — not guesswork from PEM files.