Caddy gets certificates automatically, so when it fails the cause is almost always outside Caddy: DNS points somewhere else, a firewall blocks the challenge, or a proxy such as Cloudflare answers instead of your server. This guide shows how to read Caddy's error, find which of these is the problem and fix it without running into rate limits.
Before you start
- SSH access to the VPS with sudo rights.
- Caddy installed as a systemd service, as in Install Caddy web server with automatic HTTPS.
- The domain names in your Caddyfile, and your server's public IPv4 (and IPv6, if it has one). Both are listed in your VPS provider's control panel.
How Caddy proves you own the domain
For each hostname, Caddy asks a certificate authority (CA) for a certificate. By default it uses Let's Encrypt and can fall back to ZeroSSL. The CA must confirm you control the name, using one of these challenges:
- HTTP-01: the CA fetches a file from
http://yourdomain/.well-known/acme-challenge/on port 80. - TLS-ALPN-01: the CA connects on port 443 and checks a special handshake.
- DNS-01: the CA reads a TXT record. Caddy only uses this if you configure a DNS provider plugin. It is needed for wildcard certificates.
Both of the default challenges need the CA to reach your server directly by the domain name. Keep that in mind for every check below.
Read Caddy's logs first
sudo journalctl -u caddy --no-pager -n 100To watch live while you retry:
sudo journalctl -u caddy -fLook for lines with "level":"error" and the words challenge, acme or obtain. The useful part is the problem or error field. Some common ones:
NXDOMAINorno valid A records found: DNS is missing or wrong.Timeout during connect (likely firewall problem): the CA cannot reach port 80 or 443.Invalid response from http://...: something other than Caddy answered on port 80.rateLimitedortoo many certificates: you hit a CA rate limit.
Check DNS points at this server
From the server, compare your public IP with what DNS returns:
curl -4 -s https://ifconfig.me; echo
dig +short A example.com
dig +short AAAA example.comThe A record must match your server's IPv4. Pay attention to AAAA: if an old IPv6 address is still listed, the CA may try it first and fail. Remove AAAA records that do not point at this server.
If you changed records recently, check what public resolvers see with How to check DNS propagation.
Check ports 80 and 443 are open
Confirm Caddy is the one listening:
sudo ss -tlnp | grep -E ':(80|443)\s'Both lines should show caddy. If you see nginx, apache2 or httpd, another web server holds the port. Stop and disable it, then restart Caddy.
Open the ports in the firewall.
Ubuntu and Debian (UFW)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw statusAlmaLinux and Rocky Linux (firewalld)
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reloadThen test from a different machine, such as your own computer:
curl -I http://example.com/.well-known/acme-challenge/testAny HTTP response from your server, even a 404, means port 80 is reachable. A timeout means it is still blocked somewhere.
Cloudflare and other proxies
If the domain uses Cloudflare with the orange cloud (proxied) turned on, visitors and the CA connect to Cloudflare, not to your server.
- TLS-ALPN-01 cannot work through the proxy, because Cloudflare terminates TLS itself.
- HTTP-01 can fail if Cloudflare redirects HTTP to HTTPS before the request reaches you, for example with Always Use HTTPS turned on.
Two reliable fixes:
- Set the record to DNS only (grey cloud) while Caddy gets the first certificate, then turn the proxy back on and use SSL/TLS mode Full (strict). Renewal over HTTP-01 usually keeps working if Cloudflare does not redirect
/.well-known/acme-challenge/. - Use the DNS-01 challenge with Caddy's Cloudflare DNS module. That needs a Caddy build that includes the module and an API token. It works no matter how the record is proxied.
Note: Never use Cloudflare's Flexible SSL mode with Caddy. Cloudflare talks plain HTTP to your server, Caddy redirects to HTTPS, and visitors get a redirect loop.
Rate limits and the staging CA
Let's Encrypt limits how many certificates and failed attempts you can make for the same names in a period. The exact limits change, so check the current list in the Let's Encrypt documentation. Caddy already backs off between retries, but restarting it over and over while DNS is wrong can still use up the allowance.
While you debug, switch to the staging CA. Its certificates are not trusted by browsers, but its limits are much higher. Add a global options block at the very top of the Caddyfile:
{
acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
email admin@example.com
}Reload, watch the logs and fix problems until the staging certificate is issued. Then remove the acme_ca line and reload again to get a real certificate.
If you are rate limited for one hostname, Caddy may still issue through its fallback CA. Otherwise wait for the window to pass. Do not delete Caddy's data folder to "start fresh", as that only causes more requests.
Other causes
- Typo in the site address: the name in the Caddyfile must match DNS exactly.
exmaple.comwill never validate. - Caddy cannot write its data directory: the service stores certificates in
/var/lib/caddy/.local/share/caddy. Look forpermission deniedin the log, and fix the ownership withsudo chown -R caddy:caddy /var/lib/caddy. - Internal or IP-only addresses:
localhost, private IPs and bare IPs get a certificate from Caddy's own local CA, which browsers do not trust. Use a public domain name instead. - CAA records: if your DNS has a CAA record, it must allow
letsencrypt.org(andsectigo.comif you rely on the ZeroSSL fallback). Check withdig +short CAA example.com.
Check it works
curl -vI https://example.com 2>&1 | grep -E "subject:|issuer:|expire date:"You should see your domain as the subject and a trusted issuer such as Let's Encrypt. For more detail on reading certificates, see Inspect SSL certificates with OpenSSL.
Troubleshooting
- Works for
example.combut notwww.example.com: thewwwrecord is missing or points elsewhere. Add it and reload. - Browser says "not secure" after a successful issue: you are still on the staging CA. Remove
acme_caand reload. - Errors only on IPv6: the AAAA record is wrong or IPv6 is not open in the firewall. Fix or remove the record.
- Everything looks right but it still times out: test port 80 from outside the server. Some networks block inbound traffic before it reaches your firewall. Open a ticket with the domain and the exact error if you need help.
Next steps
- Learn what happens during issuance and renewal: How Let's Encrypt works.
- Go back to configuring sites: Use Caddy as a reverse proxy for apps.
