Issuance and renewal
acme.sh "Verify error": read the detail and fix validation
example.com:Verify error:Invalid response from http://example.com/.well-known/acme-challenge/...: 404Verify error means the CA tried to validate your domain and failed; acme.sh prints the CA's explanation after the colon. Read that explanation: an HTTP status (404, 403), a timeout, NXDOMAIN or Incorrect TXT record each point to a different fix.
Quick fix
Rerun with debugging against staging so failures do not count against production:
acme.sh --issue -d example.com -w /var/www/html --staging --debug 2
Then map the detail to a cause:
Detail after Verify error: |
Most likely cause | Section |
|---|---|---|
Invalid response ...: 404 |
wrong webroot or server block | 1 |
Invalid response ...: 403 |
deny rule, WAF, permissions | 2 |
Timeout during connect, Connection refused |
port 80 closed, wrong IP | 3 |
Incorrect TXT record, NXDOMAIN looking up TXT |
DNS-01 record not visible | 4 |
CAA record ... prevents issuance |
CAA does not list the CA | 5 |
When it works on staging, run the same command without --staging and with --force once.
Causes
The detail after Verify error: maps to five causes: a webroot mismatch (404), access rules (403), connection problems, an invisible DNS-01 record, and CAA or other CA refusals.
1. HTTP-01 webroot mismatch (404)
acme.sh writes the token into <webroot>/.well-known/acme-challenge/. If the web server serves a different directory for that hostname, the CA gets 404.
- Confirm:
echo ok > /var/www/html/.well-known/acme-challenge/tandcurl http://example.com/.well-known/acme-challenge/treturns 404. - Fix: pass the real
rootof the server block (nginx -T | grep root), or use--nginx/--apachemode so acme.sh configures the server temporarily. A reverse proxy needs alocationthat serves the challenge directory locally.
2. Access denied (403)
- Fix: add an exception for
/.well-known/acme-challenge/before dotfile deny rules and WAF rules; see Certbot 403 unauthorized, the fixes are identical.
3. Connection problems
- Fix: open TCP 80 in every firewall layer, remove stale AAAA records and avoid geo-blocking; see timeout during connect. In
--standalonemode acme.sh needs port 80 free, and installs that ask forsocatneed it installed first.
4. DNS-01 record not visible
The DNS API hook created the record in the wrong zone, the API credentials lack permission, or the CA checked before all authoritative servers had the record.
- Confirm:
dig +short TXT _acme-challenge.example.com @<authoritative server>during the run shows nothing or an old value. - Fix: check the API environment variables (for example
CF_Tokenfor Cloudflare,Ali_Key/Ali_Secretfor Alibaba Cloud DNS,DP_Id/DP_Keyfor DNSPod), make sure the domain's NS records point at that provider, and add--dnssleep 120if propagation is slow. With CNAME delegation use--challenge-alias.
5. CAA or other CA-side refusals
- Fix: add the CA's identifier to CAA, for example
letsencrypt.orgor the one your ZeroSSL documentation gives; see CAA record prevents issuance.
Fixes by environment
Outside validation itself, repeat failures usually trace back to the default CA, a missing install step, lost Docker state or an old acme.sh version.
Switching CA:
acme.sh --set-default-ca --server letsencrypt
Installing the certificate (so renewals reach the web server):
acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/example.com.key \
--fullchain-file /etc/nginx/ssl/example.com.pem \
--reloadcmd "systemctl reload nginx"
Docker (the neilpang/acme.sh image): mount the /acme.sh data directory as a volume so accounts and certificates survive restarts; otherwise every run is a new order and may hit rate limits.
Wildcard certificates: require DNS-01 (--dns dns_cf etc.); HTTP-01 cannot validate *.example.com.
Upgrading: acme.sh --upgrade fixes many errors from changed CA APIs on old installations.
Don't do this
- Keep
--forcein cron jobs. It renews every run regardless of expiry and burns through limits. - Post
--debuglogs publicly without redacting. They contain API keys and account URLs. - Disable the firewall or WAF entirely to make validation pass. Exempt only the challenge path, or move to DNS-01.
FAQ
Which CA is acme.sh using?
Since acme.sh 3.0 the default CA is ZeroSSL, which requires an account email (acme.sh --register-account -m you@example.com). Run acme.sh --set-default-ca --server letsencrypt to use Let's Encrypt, or pass --server letsencrypt per command.
How do I see the full request and response?
Add --debug 2 (or --debug 3 for even more) to the command. Logs go to the terminal and to ~/.acme.sh/acme.sh.log if logging is enabled. Remove API keys before sharing logs.
Will retrying hurt?
Each failed validation counts toward the CA's limits; at Let's Encrypt that is 5 authorization failures per identifier per account per hour. Use --staging until it works, then remove it and add --force once to replace the staging certificate.