Issuance and renewal

acme.sh "Verify error": read the detail and fix validation

By · Last verified

Error messageexample.com:Verify error:Invalid response from http://example.com/.well-known/acme-challenge/...: 404

Verify 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/t and curl http://example.com/.well-known/acme-challenge/t returns 404.
  • Fix: pass the real root of the server block (nginx -T | grep root), or use --nginx / --apache mode so acme.sh configures the server temporarily. A reverse proxy needs a location that 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 --standalone mode acme.sh needs port 80 free, and installs that ask for socat need 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_Token for Cloudflare, Ali_Key/Ali_Secret for Alibaba Cloud DNS, DP_Id/DP_Key for DNSPod), make sure the domain's NS records point at that provider, and add --dnssleep 120 if 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.org or 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 --force in cron jobs. It renews every run regardless of expiry and burns through limits.
  • Post --debug logs 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.

Sources

  1. acme.sh wiki
  2. acme.sh wiki: How to debug acme.sh
  3. Let's Encrypt: Challenge Types
  4. Let's Encrypt: Rate Limits