Guide
Certbot renew failed: troubleshooting guide
Step-by-step certbot renew failed triage: letsencrypt.log, dry-run, challenge type, hooks, and rate limits.
Updated Apr 2, 2026 810 words
certbot renew failed bundles auth, challenge, hook, and filesystem errors—triage logs before reinstalling packages.
When this happens
certbot.timer mails errors nightly after a config change. OS upgrade bumped Python underneath pip-installed certbot while systemd unit still points at an orphaned interpreter path. Intermittent renew failures on crowded shared hosts correlate with cron jitter overlapping peak standalone validation windows.
Manual certbot renew works as root but timer fails as another user. Leaked FULLCHAIN paths in config help tickets trace wrong webroot quickly. Package upgrades scheduled quarterly can break certbot plugins the same week notAfter crosses below thirty days.
First response
Step-by-step certbot renew failed triage: letsencrypt.log, dry-run, challenge type, hooks, and rate limits.
Searches for certbot renew failed 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 renew failed: troubleshooting guide, 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.
Certbot renew failed is the umbrella error when any step in the renewal config breaks—auth, challenge, deploy, or permissions. Start with /var/log/letsencrypt/letsencrypt.log tail, not a distro reinstall. Dry-run reproduces without burning rate limits. Triage should capture timestamps, account URLs, and the last Boulder error type before infrastructure changes widen blast radius. Pause destructive retries until you can cite the last Boulder error type and the challenge mechanism in use; random package reinstalls rarely fix presentation gaps.
See also ACME DNS-01 challenge propagation delays and ACME HTTP-01 challenge failed 403.
Why renewal failed
Renew reads per-lineage renewal conf; stale authenticator paths break silently until expiry nears. Archive old letsencrypt logs before reboot—they are the only record of which lineage failed first when timers fire twice overnight. Renew failures are multi-step; timelines in tickets should list auth, hook, and Boulder phases separately.
Disk, permission, and Python dependency issues surface first during renewal, not initial certonly. When multiple lineages renew in one timer run, fix failures in dependency order: shared webroot hosts block several lineages simultaneously. Package management discipline on certbot hosts prevents surprise plugin breakage during renew.
Mixed snap and apt certbot installs produce conflicting timers and libraries. Keep a known-good renew log snippet in the wiki for diffing against new failures. Escalate with timestamps, hostnames, and serial comparisons attached so the next engineer does not repeat already-failed renew attempts.
Renewal reuses account keys and lineage paths. Failures often differ from initial issuance because webroots moved, timers stopped, or plugins changed. --force-renewal is for testing, not daily cron. Clarify in tickets whether failure is validation, issuance, deploy, or listener presentment—teams too often mix these phases when reading logs. Renew logs are multi-attempt timelines; attach the last three attempts so responders spot flapping challenges versus one hard failure.
Renew failure is not automatically DNS propagation if HTTP-01 is configured—read the logged challenge type. It is not fixed by deleting /etc/letsencrypt without understanding rate limits on re-creation. Correlation matters: compare timer journal timestamps with letsencrypt.log so you know whether unattended renew failed versus a manual certonly leaving timers untested.
Common causes
These patterns drive most certbot renew failed tickets. They explain why automation logs look green while browsers or APIs still break.
Expired distribution packages shipped incompatible certbot and plugin versions.
Port 80 conflict during standalone attempts while nginx still bound.
Disk full under /var/lib/letsencrypt. Corporate proxy MITM on outbound Boulder traffic causes TLS errors misread as auth failures.
Diagnose
Start with dry-run at increased verbosity. Classify the failure phase in the ticket subject line so the next responder does not restart at dry-run when you already isolated a deploy-hook exit code.
certbot renew --dry-run -v
tail -n 80 /var/log/letsencrypt/letsencrypt.log
Correlate journalctl certbot timer with letsencrypt.log timestamp. Ensure renew configs reference still-valid authenticator paths. Attach certbot --version, plugin list, and renewal conf redacted copy to every escalation. When logs show multiple lineages failing, split attachments per lineage so Boulder rate limits are not worsened by duplicate bulk retries.
Fix step-by-step
Follow this sequence for certbot renew failed: troubleshooting guide. Capture command output after each step so the next responder can pick up without rerunning destructive changes.
- Run dry-run renew and save log excerpt.
- Fix the first hard error; ignore noisy warnings until then.
- Verify disk space and permissions on
/etc/letsencryptand logs. - Confirm authenticator webroot or plugin paths exist.
- Check rate limit messages; pause if needed.
- Repair hooks if deploy failed after validation.
- Enable and test systemd timer.
- External openssl after successful renew.
- Archive redacted ACME logs and openssl transcripts in the ticket before closing.
- 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
If renew fails, openssl may still show a cert nearing expiry—note days left to prioritize challenge type fixes. While renew is broken, openssl still informs urgency by showing remaining lifetime—attach that output even when the primary issue is ACME authorization.
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
Run certbot renew --dry-run with debug logging once.
Fix the specific logged Boulder error, then real renew. Pin certbot version in config management after a known-good renew; unexpected auto-upgrades break plugins. After one successful manual renew, immediately rerun via timer context to prove unattended path works before demoting severity.
Enable systemd timer and monitoring on dry-run success monthly. Publish a short post-incident note listing which hop served stale TLS and which command proved the fix so the next renewal inherits a checklist.
What to automate
Monthly dry-run from CI catches renew failures before the thirty-day cliff. Monthly dry-run renew from CI with uploaded logs compared to a golden baseline.
ssldays adds outsider perspective when logs claim success but TLS is still aging. ssldays catches nearing expiry when logs look inconclusive.
Frequently asked questions
Does --force-renewal help debug?
Yes once per incident, not in cron—it forces new orders. Attach redacted renewal conf authenticator lines.
Why different errors for root vs timer?
Environment, PATH, and permissions differ—run timer as the same user with same env. Attach redacted renewal conf authenticator lines.
Python import errors?
Often mixed package sources; pick apt, snap, or pip consistently. Attach redacted renewal conf authenticator lines.
Is reinstalling certbot from scratch safe?
Reinstalling packages without preserving /etc/letsencrypt destroys account keys and lineages. Backup that directory before package changes. Reinstall only after capturing configs and confirming you will not burn rate limits reissuing every hostname from scratch.
Monitor this hostname on a schedule
Live certificate checks and renewal reminders — not guesswork from PEM files.