Troubleshooting

Fix Caddy certificate errors and ACME failures

Diagnose why Caddy cannot get a TLS certificate. Check DNS, open ports 80 and 443, read Caddy's logs, handle Cloudflare proxying and rate limits, and test safely with the staging CA.

IntermediateLinux server6 min read

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

Bash
sudo journalctl -u caddy --no-pager -n 100

To watch live while you retry:

Bash
sudo journalctl -u caddy -f

Look for lines with "level":"error" and the words challenge, acme or obtain. The useful part is the problem or error field. Some common ones:

  • NXDOMAIN or no 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.
  • rateLimited or too 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:

Bash
curl -4 -s https://ifconfig.me; echo
dig +short A example.com
dig +short AAAA example.com

The 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:

Bash
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)

Bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

AlmaLinux and Rocky Linux (firewalld)

Bash
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload

Then test from a different machine, such as your own computer:

Bash
curl -I http://example.com/.well-known/acme-challenge/test

Any 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:

  1. 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/.
  2. 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.com will never validate.
  • Caddy cannot write its data directory: the service stores certificates in /var/lib/caddy/.local/share/caddy. Look for permission denied in the log, and fix the ownership with sudo 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 (and sectigo.com if you rely on the ZeroSSL fallback). Check with dig +short CAA example.com.

Check it works

Bash
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.com but not www.example.com: the www record 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_ca and 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

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.