Question
How to fix Cloudflare 525 with Caddy
If Cloudflare shows Error 525, it means the SSL handshake to your origin failed. In practice, this is usually a certificate, port, or SNI mismatch.
Fast recovery checklist
- Cloudflare SSL mode: keep
Full (strict)only when origin cert is valid for your hostname. - Verify origin HTTPS on port 443 is open in firewall and listening on server.
- Confirm Caddy site block includes the exact domain name and TLS configuration.
- Run one SNI test and one curl test from the server before changing more settings.
Commands that usually surface the root cause
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl status caddy --no-pager -n 20
sudo journalctl -u caddy --since '30 minutes ago' --no-pager | tail -n 80
curl -Iv --resolve yourdomain.com:443:ORIGIN_IP https://yourdomain.com/
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com -showcerts </dev/null | head -n 60
sudo ss -lntp | grep :443
Error signal to Caddy fix
| Signal | Likely Caddy issue | First fix |
|---|---|---|
curl --resolve returns a certificate for the wrong hostname | The Caddy site label does not include the hostname Cloudflare sends with SNI. | Use explicit labels such as example.com, www.example.com instead of only :443 or only the apex. |
openssl shows no peer certificate or handshake failure | Caddy is not completing TLS, or another service/firewall is interrupting before Caddy answers. | Check sudo ss -lntp | grep :443, Caddy logs, UFW, cloud firewall, and any load balancer in front of Caddy. |
Apex works, www fails | The certificate or site label covers only one hostname. | Add both hostnames to the Caddy site block and issue a cert that covers both names. |
| IPv4 works, IPv6 fails | The AAAA origin points to a host without the same Caddy/TLS config. | Mirror the Caddy config on IPv6 or remove the proxied AAAA record until it is ready. |
| Origin test works, Cloudflare still shows 525 | Cloudflare is reaching a different origin IP or edge-to-origin traffic is blocked. | Compare proxied DNS records, Cloudflare origin rules, security groups, and the exact origin IP used in the forced test. |
Example: Cloudflare 525 after switching to Full (strict)
This usually means Cloudflare can reach port 443, but Caddy does not present a certificate Cloudflare accepts for the requested hostname.
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# Check for three things:
# 1. The certificate has example.com or *.example.com in Subject Alternative Name.
# 2. The certificate is not expired.
# 3. Caddy is serving this certificate on the same hostname Cloudflare requests.
If the certificate is for a different host, fix the Caddy site label first. If the certificate is expired, renew or replace it before changing Cloudflare mode.
Example: origin IP test works, but hostname test fails
Testing https://ORIGIN_IP is not enough for Caddy because Caddy uses SNI to choose the site block. Test the hostname while forcing it to the origin IP.
curl -Iv --resolve example.com:443:203.0.113.10 https://example.com/
# Good result:
# SSL connection using TLSv1.3
# server certificate verification OK
# HTTP/2 200 or HTTP/1.1 200
# Bad result:
# SSL certificate problem, handshake failure, or default Caddy certificate.
This isolates the origin from Cloudflare. If this forced-origin request fails, the problem is on Caddy or the origin firewall. If it succeeds, check Cloudflare SSL mode, DNS target, and any origin rules.
Caddyfile patterns that cause 525
The most common Caddy-specific failure is a site label that does not match the hostname Cloudflare sends with SNI. A direct IP test can look fine while the real hostname still fails.
# Risky: catches requests but may not serve the cert Cloudflare expects
:443 {
reverse_proxy 127.0.0.1:3000
}
# Better: match the exact hostname Cloudflare proxies
example.com, www.example.com {
reverse_proxy 127.0.0.1:3000
}
If you use a Cloudflare Origin Certificate, make sure the certificate covers both the apex and www hostname when both DNS records are proxied. A certificate for example.com alone can still fail for www.example.com under Full (strict).
Caddyfile examples for common deployments
For a normal public site where Caddy obtains a public certificate, keep the site label explicit and let Caddy manage TLS.
example.com, www.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:3000
}
For an origin that should only be validated by Cloudflare, a Cloudflare Origin Certificate can be used, but the certificate must include every proxied hostname.
example.com, www.example.com {
tls /etc/ssl/cloudflare/example-origin.pem /etc/ssl/cloudflare/example-origin.key
reverse_proxy 127.0.0.1:3000
}
Do not combine a catch-all :443 site with hostname-specific sites unless you have tested which block answers each SNI name. The simplest recovery path is usually to remove the catch-all, make hostnames explicit, validate, and reload.
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -Iv --resolve example.com:443:203.0.113.10 https://example.com/
curl -Iv --resolve www.example.com:443:203.0.113.10 https://www.example.com/
Caddy automatic HTTPS checks behind Cloudflare
Caddy automatic HTTPS works well behind Cloudflare, but it still needs a valid site label, successful certificate storage, and a hostname Cloudflare can present through SNI. If Caddy never obtained or loaded a certificate for the proxied hostname, Cloudflare can reach port 443 and still return 525.
# Show whether Caddy is managing certificates for the expected names.
sudo journalctl -u caddy --since '24 hours ago' --no-pager \
| grep -Ei 'certificate|obtaining|renew|acme|tls|handshake' \
| tail -120
# Confirm Caddy's adapted config has the hostnames you expect.
sudo caddy adapt --config /etc/caddy/Caddyfile --pretty \
| grep -E '\"server_names\"|example.com|www.example.com' -n
# Check whether Caddy storage has certificates for the hostnames.
sudo find /var/lib/caddy/.local/share/caddy/certificates -maxdepth 4 -type f \
| grep -Ei 'example.com|www.example.com'
| Caddy signal | Meaning | Action |
|---|---|---|
| ACME rate limit or DNS challenge failure | Caddy could not issue a public certificate. | Fix ACME/DNS first, or install a Cloudflare Origin Certificate that covers every proxied hostname. |
No certificate files for www | The site label or certificate SANs do not include www. | Add www.example.com to the Caddyfile and reload after validation. |
| Handshake log mentions unknown SNI | Cloudflare requested a hostname Caddy does not serve. | Compare Cloudflare DNS records, redirects, and Caddy site labels. |
| Certificate exists but Cloudflare still fails | Cloudflare may be reaching a stale origin IP or IPv6 address. | Retest each A and AAAA origin with curl --resolve. |
Example: apex works but www returns 525
When example.com works but www.example.com fails, the origin often has a certificate or Caddy site block for only one hostname. Test both names with the same origin IP before changing Cloudflare SSL mode.
curl -Iv --resolve example.com:443:203.0.113.10 https://example.com/
curl -Iv --resolve www.example.com:443:203.0.113.10 https://www.example.com/
openssl s_client -connect 203.0.113.10:443 -servername www.example.com </dev/null \
| openssl x509 -noout -subject -issuer -ext subjectAltName
If the apex succeeds and www fails, add both hostnames to the Caddy site label and issue a certificate that covers both. Then retest the two forced-origin requests and only after that retest through Cloudflare.
Common operator mistake
People often switch many settings at once. Fix one failed check first, then retest through Cloudflare. This avoids long trial-and-error loops.
Post-fix checks for AI crawler pages
When 525 affects a site that depends on search and AI crawler access, verify more than the homepage. Test a crawl path and a representative content page through Cloudflare after the certificate fix.
curl -I https://example.com/robots.txt
curl -I https://example.com/sitemap.xml
curl -I https://example.com/questions/important-page/
# Expected: 200, 301, or 304.
# Not fixed: 525, 526, handshake failure, or intermittent timeouts.
If only one hostname fails, compare the apex and www Caddy site blocks and certificate names. Cloudflare can request either hostname depending on DNS and redirect rules.
For multi-site Caddy servers, also confirm no default catch-all site is answering before the intended hostname block. A catch-all can make direct IP tests look healthy while SNI-specific requests still fail.
If Search Console still reports 525 after the fix
Search Console and real crawlers may retest a different URL than the one you opened in your browser. Before requesting indexing again, prove that the canonical URL, apex, www, sitemap URL, and one deep article all succeed through Cloudflare and through a forced-origin SNI test.
curl -IL https://example.com/
curl -IL https://www.example.com/
curl -IL https://example.com/sitemap.xml
curl -IL https://example.com/questions/important-page/
curl -Iv --resolve example.com:443:203.0.113.10 https://example.com/
curl -Iv --resolve www.example.com:443:203.0.113.10 https://www.example.com/
If the public Cloudflare checks work but the forced-origin hostname check fails, Caddy is still serving the wrong certificate or site block for that SNI name. If forced-origin checks work but Search Console is stale, wait for the next live test and compare the inspection time with Caddy access logs before making more changes.
When to use a Cloudflare Origin Certificate
A Cloudflare Origin Certificate is a reasonable fix when Cloudflare is the only client that must validate origin TLS. Do not use it for direct browser access to the origin hostname, because public browsers will not trust that certificate chain.
Cloudflare settings to check before touching Caddy again
If the forced-origin Caddy test succeeds but Cloudflare still shows 525, stop editing the Caddyfile and compare the Cloudflare-side settings. Most repeat failures come from the edge reaching a different origin or validating a hostname that was not included in the certificate.
| Cloudflare setting | Expected value | Why it matters |
|---|---|---|
| DNS record | Proxied A/AAAA points to the tested origin IP. | A stale IP makes local tests pass while Cloudflare handshakes with another server. |
| SSL/TLS mode | Full or Full (strict), matched to the origin certificate. | Flexible hides origin TLS; Strict requires valid hostname coverage and chain. |
| Origin Rules | No rule rewrites the destination hostname or port unexpectedly. | A rule can send only some hostnames to the wrong backend. |
| IPv6 / AAAA | Same Caddy/TLS config as IPv4, or disabled until ready. | Crawler and edge traffic may prefer IPv6 even if your browser test used IPv4. |
When Cloudflare is not handshaking with the Caddy you tested
On small sites the origin is usually one VM, but many 525 cases involve one extra hop: a cloud load balancer, a floating IP, NAT, an IPv6 record, or a Cloudflare Origin Rule. In that setup, testing the expected server can be misleading. You need to prove which machine and port receive Cloudflare's TLS ClientHello.
# Run on the Caddy host while you request the public URL through Cloudflare.
sudo tcpdump -ni any 'tcp port 443 and (tcp[tcpflags] & tcp-syn != 0)'
# Check every DNS target Cloudflare could use.
dig +short A example.com
dig +short AAAA example.com
dig +short A www.example.com
dig +short AAAA www.example.com
# Test each suspected origin explicitly.
curl -Iv --resolve example.com:443:203.0.113.10 https://example.com/
curl -Iv --resolve example.com:443:2001:db8::10 https://example.com/
| What you observe | What it means | Next check |
|---|---|---|
| No packets arrive on the Caddy host during the 525 | Cloudflare is not reaching this host. | Review proxied A/AAAA records, load balancer target pools, and Cloudflare Origin Rules. |
| Packets arrive on IPv6 only | The AAAA path has different TLS or firewall behavior than IPv4. | Mirror the Caddy config on IPv6 or temporarily unproxy/remove AAAA while fixing it. |
| Packets arrive, but Caddy logs do not mention the request | Another service or firewall is answering before Caddy. | Check sudo ss -lntp, DNAT rules, Docker port publishing, and host firewall rules. |
| Caddy logs show unknown SNI from the public hostname | The request reached Caddy, but no matching site label or certificate is loaded. | Add the exact hostname to the Caddyfile, validate, reload, and retest with curl --resolve. |
If there is a cloud load balancer in front of Caddy, also verify whether it terminates TLS or passes TCP through. A TLS-terminating load balancer needs its own certificate for the Cloudflare-facing hostname; a TCP pass-through load balancer leaves certificate selection to Caddy.
When not to edit Cloudflare SSL mode
Changing from Full (strict) to Flexible can make the error page disappear while leaving the origin misconfigured. Treat it as a temporary diagnostic only if you have no other way to restore service. The durable fix is to make the forced-origin hostname test succeed with the same hostname, IP, and TLS certificate Cloudflare will use.
If this page is part of a search or AI visibility workflow, retest /robots.txt, /sitemap.xml, and one deep article URL after the TLS fix. A homepage-only test can miss crawler-impacting failures.
Running Nginx instead? Follow the Nginx-specific workflow: How to fix Cloudflare 525 with Nginx.
If Cloudflare shows 526 instead of 525, use this checklist: How to fix Cloudflare 526 invalid certificate.