Guide
acme.sh renewal failures and logs
Read acme.sh renewal logs and cron output when renew failed appears—validation, deploy, and reload hooks fail separately.
Updated Apr 2, 2026 840 words
acme.sh renew failed in logs means validation, install, or
--reloadcmdbroke—read the domain log, not only cron mail.
When this happens
Cron emails show non-zero exit but the site still works until two weeks later. Hardware migration copies /etc but not the unprivileged user cron that owned acme.sh account registration.
You cloned a VM without copying crontab entries or ~/.acme.sh account keys. Unprivileged cron cannot bind port 80 for standalone mode on shared hosting.
First response
Read acme.sh renewal logs and cron output when renew failed appears—validation, deploy, and reload hooks fail separately.
Searches for acme.sh 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 acme.sh renewal failures and logs, 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.
acme.sh cron output is easy to ignore until mail spool fills with renew failures three weeks after a harmless DNS edit. Treat each line in ~/.acme.sh/*.log as a contract: the CA response, deployed cert path, and reload hook exit code must all succeed. Before you reinstall acme.sh, confirm whether the failure is validation, deploy, or reload by reading the last twenty lines of the domain log. 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
acme.sh stores certs under its home and renews with acme.sh --cron. Exit 0 requires both Boulder success and hook success. Ship acme.sh exit codes and last twenty log lines into your SIEM with the domain label; grep-only triage does not scale when dozens of microsites share one edge host. Treat acme.sh as three services: ACME client, file publisher, and process reloader—monitor each step.
Deploy hooks copy PEMs to paths nginx or haproxy read—skipping hooks yields successful issuance with unchanged live serial. When reloadcmd wraps docker, capture stderr separately—non-zero docker exit often masquerades as renew failure in cron mail subject lines. Ownership of account keys should be documented because loss equals ability to mint for every listed domain.
DNS API credentials in per-domain config expire silently; HTTP mode breaks when port 80 moves to another host. Map each domain in acme.sh to an owning team before you rotate API keys organization-wide.
acme.sh stores certificates per domain under its home directory and renews via a daily cron calling acme.sh --cron. A renew failed message means the ACME order did not complete or the --reloadcmd returned non-zero. Unlike certbot systemd units, many acme.sh installs depend on root crontab entries that vanish when images are cloned without the cron job. Clarify in tickets whether failure is validation, issuance, deploy, or listener presentment—teams too often mix these phases when reading logs.
acme.sh renewal failure is not fixed by deleting ~/.acme.sh unless you have backed up account keys and can re-issue without hitting rate limits. It is not the same as nginx expiry if deploy hooks never copied PEMs. It is not proof the CA banned your domain when only one SAN failed DNS. Logs distinguish challenge errors from reload failures.
Common causes
These patterns drive most acme.sh renew failed tickets. They explain why automation logs look green while browsers or APIs still break.
Cron runs as a user without permission to write the web server's TLS directory after a permission hardening. The deploy hook calls service nginx reload but systemd rejects the unit name typo. DNS API credentials in account.conf expired silently.
Upgrade from acme.sh git pulled a new default cipher but left --keylength ec-256 incompatible with an old Windows target. Multiple clones of the same cron on clustered nodes raced on DNS-01 TXT updates. Docker bind mounts hid renewed certs from the host nginx process. Git-deployed hook scripts lose executable bit on CI checkout, yielding exit 127 in cron.
Operators disabled cron on staging and never re-enabled after cloning the playbook to production. --force renew loops burned duplicate certificates without updating listeners. Log rotation deleted the only copy of Boulder error bodies needed for support tickets.
Diagnose
Tail the per-domain log and rerun one forced renew with debug enabled.
acme.sh --renew -d example.com --force --debug 2
tail -n 50 ~/.acme.sh/example.com/example.com.log
Read ~/.acme.sh/<domain>/<domain>.log for the Boulder error type and the deploy hook exit status. Run the same renew command with --debug 2 once during a maintenance window. If validation passes and deploy fails, fix paths; if validation fails, fix DNS or HTTP reachability first. If renew succeeds but serial unchanged, instrument reloadcmd with set -x redirected to a dedicated log file with rotation.
Fix step-by-step
Follow this sequence for acme.sh renewal failures and logs. Capture command output after each step so the next responder can pick up without rerunning destructive changes.
- Confirm
crontab -lor systemd timer runs the same user that owns~/.acme.sh. - Inspect domain log for Boulder type versus hook exit code.
- Validate DNS API keys or webroot paths in domain.conf.
- Run interactive forced renew during maintenance.
- Fix
--reloadcmdto reload the daemon that terminates customer TLS. - Compare openssl serial to
~/.acme.sh/.../fullchain.cer. - Ship logs to central monitoring.
- Document which host may run issuance to avoid duplicate account keys.
- 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.
How to read the output
After a successful acme.sh reload, run openssl against the live name and compare serial to ~/.acme.sh/domain/domain.cer. If cron claims success but serial is unchanged, the reload hook did not run or pointed at the wrong fullchain path nginx references.
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
Reproduce with acme.sh --renew -d domain --force interactively and capture stdout. Fix API keys, webroot, or DNS flags in the domain config. Ensure cron exists in crontab -l for the user that owns account keys.
Update --reloadcmd to a script that copies fullchain and key then reloads the correct daemon. Test hook manually before trusting cron. Align certificate paths with what openssl and nginx ssl_certificate directives use.
Monitor cron mail or redirect acme.sh logs to centralized logging. Add external TLS checks on the hostname independent of acme.sh exit codes. Document which server owns the acme.sh account to avoid split-brain issuance. Pair centralized logging with a weekly openssl serial check stored next to acme.sh domain metadata for drift detection.
What to automate
Alert when acme.sh cron output contains Renew failed or when serial on 443 diverges from acme.sh fullchain. Ship cron stderr and domain log tails to logs with domain and exit code labels.
ssldays external expiry checks catch hook failures certbot or acme.sh gloss over as success. ssldays confirms customer-visible serial moves after acme.sh claims success.
Frequently asked questions
Is --force safe?
Use sparingly—it burns issuance quota. Good for one debug window after fixing hooks. Distinguish Boulder errors from reload exit codes in summaries.
Can two servers share one acme.sh account?
Possible but risky; DNS races on TXT updates break renewals. Prefer one issuer host or use cert-manager. Distinguish Boulder errors from reload exit codes in summaries.
Does acme.sh reload nginx automatically?
Only if you configured --reloadcmd or install hooks; default renew does not touch listeners. Distinguish Boulder errors from reload exit codes in summaries.
Is migrating off acme.sh worth it?
If you run Kubernetes or standardized certbot timers, migrating can reduce snowflake cron hosts. Plan account key transfer or reissuance carefully to avoid rate limits. Until migration completes, monitor acme.sh as production-critical software with the same rigor as a database backup.
Monitor this hostname on a schedule
Live certificate checks and renewal reminders — not guesswork from PEM files.