What 525 means
With Cloudflare in front, an HTTPS request has two separate legs: the visitor’s browser to Cloudflare, and Cloudflare to your server (the origin). Each leg has its own TLS handshake and its own certificate.
Error 525 is about the second leg. The visitor reached Cloudflare without trouble, Cloudflare opened a TCP connection to your origin, and then the TLS handshake on that connection failed. The Cloudflare error page shows it in three boxes: browser working, Cloudflare working, host error.
525 only happens when Cloudflare talks HTTPS to the origin, which means SSL/TLS mode Full or Full (strict). In Flexible mode Cloudflare uses plain HTTP to the origin and never attempts a handshake.
If you’re just visiting the site
There’s nothing to fix on your side. The broken connection is between Cloudflare and the site’s server. Clearing the cache, changing browsers or switching networks won’t help. Try again later, or let the site owner know, ideally with the time and the Ray ID shown at the bottom of the error page, which identifies the failed request.
Which Cloudflare error is it?
Cloudflare’s 52x codes describe how far the connection to the origin got. Knowing the neighbours saves time, because each one points at a different layer:
| Code | Where it stopped |
|---|---|
| 521 | The origin refused the TCP connection (nothing listening, or a firewall rejecting it) |
| 522 | The TCP connection timed out (packets dropped, origin unreachable or overloaded) |
| 525 | TCP connected, but the TLS handshake failed |
| 526 | The handshake finished, but in Full (strict) the origin certificate was invalid |
| 520 | A connection was made, but the origin’s response was empty or something Cloudflare couldn’t parse |
So 525 tells you something useful: the network path is fine and the port is open. The problem is what the origin does once TLS starts.
One consequence that surprises people: in Full mode, Cloudflare doesn’t validate the origin certificate at all. Self-signed, expired, issued for another name, all accepted. An expired origin certificate therefore never causes 525. It causes 526, and only in Full (strict). The modes are compared in detail in Cloudflare SSL modes.
Test the origin yourself, without Cloudflare
You need the origin’s real IP. It’s the value of the A or AAAA record in the Cloudflare DNS dashboard; public DNS only returns Cloudflare’s addresses for proxied records.
Then do what Cloudflare does: connect to that IP and ask for your hostname via SNI.
openssl s_client -connect 203.0.113.10:443 -servername www.example.com </dev/null
curl -v --resolve www.example.com:443:203.0.113.10 https://www.example.com/ -o /dev/null
--resolve makes curl connect to that IP while still sending the correct hostname, so the test matches what Cloudflare sends. Add -k to behave like Full mode, which ignores certificate validity. Without -k, an Origin CA certificate will fail verification on your machine; that’s expected, since only Cloudflare trusts it.
How to read the result:
- Certificate chain printed,
New, TLSv1.3, Cipher is …or similar: the origin handshakes fine for that name. If Cloudflare still shows 525, look at causes 4 and 5 below. wrong version numberorpacket length too long: the port answers, but not with TLS. Usually plain HTTP is running on 443 (cause 1).alert handshake failure, or the connection closes withno peer certificate available: the origin refused the handshake (causes 2 and 3).- Connection refused or timed out: that’s a 521/522 situation from your location. If your origin firewall only allows Cloudflare’s IP ranges, this is expected. Run the same test on the server itself against
127.0.0.1instead.
If the origin has a public hostname that isn’t proxied (DNS only), the SSL check shows its certificate and negotiated TLS version from outside. The port check tells you whether port 443 on the origin IP is reachable at all, which separates “firewall” from “TLS”.
The usual causes
1. Nothing is speaking TLS on port 443
The port is open but the server listening there doesn’t do HTTPS. Common ways to get here:
- nginx with
listen 443;instead oflisten 443 ssl;. It happily serves plain HTTP on port 443. - Apache with a
<VirtualHost *:443>block that lacksSSLEngine on, or withmod_sslnot loaded. - No HTTPS site configured on the origin at all. This is common right after moving a site behind Cloudflare: it used to serve only HTTP, and someone switched the mode from Flexible to Full.
- On shared hosting, SSL being a separate (often paid) option that was never enabled for this domain.
Check what’s listening with sudo ss -ltnp | grep ':443', and try curl -v http://203.0.113.10:443/: if plain HTTP gets an answer on 443, you’ve found it.
Fix: configure a proper HTTPS server block with a certificate. If you don’t have one, use a Cloudflare Origin CA certificate (see below).
2. The origin has no certificate for this hostname
Cloudflare sends the visitor’s hostname as SNI. The origin picks a configuration based on that name, and if no server block matches, falls back to its default for port 443.
In Full mode it doesn’t matter which certificate the default returns, since Cloudflare accepts anything. The handshake fails only when the fallback can’t produce a certificate at all:
- An nginx default server on 443 with no
ssl_certificate. nginx logs something likeno "ssl_certificate" is defined in server listening on SSL port while SSL handshaking. - A default server configured to reject unknown names (nginx’s
ssl_reject_handshake on;, a common hardening snippet) when the new hostname was never added toserver_name. - A hosting panel where the domain was added but the subdomain wasn’t.
The tell: openssl s_client -servername works for one hostname and fails for another against the same origin IP.
Fix: add the hostname to the right server block (server_name, ServerAlias, or the hosting panel’s domain list) with a certificate that covers it.
3. No protocol version or cipher in common
The origin only offers things Cloudflare won’t use for origin connections, such as very old TLS versions or obsolete cipher suites. Old appliances, legacy Windows servers and software built against ancient OpenSSL are the usual suspects. Cloudflare documents the cipher suites it offers to origins; compare that list with what your origin accepts:
nmap --script ssl-enum-ciphers -p 443 203.0.113.10
The same logic applies as for browsers in ERR_SSL_VERSION_OR_CIPHER_MISMATCH: enable TLS 1.2 and 1.3 with ECDHE and AES-GCM or ChaCha20 suites and the problem goes away.
4. A firewall or security tool cuts the handshake
Your test from the server works, but Cloudflare still gets 525. Something treats Cloudflare’s connections differently from yours.
- fail2ban, CrowdSec or a hosting firewall banning Cloudflare IPs. Behind Cloudflare, all visitor traffic arrives from Cloudflare’s addresses, so a rule that bans “the IP sending too many requests” ends up banning a Cloudflare edge. Visitors routed through that edge get errors; everyone else is fine. How it shows up depends on how the block works: a dropped connection gives 522, a refusal 521, a reset mid-handshake 525.
- An IPS or WAF on the origin side that inspects TLS and resets connections it doesn’t like.
Fix: allow Cloudflare’s published ranges (https://www.cloudflare.com/ips-v4 and https://www.cloudflare.com/ips-v6) and exclude them from automatic bans. Configure your web server to log the real visitor IP from the CF-Connecting-IP header, so bans target visitors, not Cloudflare.
5. Only some origins are broken
If 525 comes and goes, check whether the hostname has more than one origin: several proxied A records, a Cloudflare load balancer pool, or a load balancer of your own in front of several servers. One server with a missing certificate or a different TLS configuration produces 525 for a share of requests.
Test each origin IP separately with the openssl command above. Intermittent errors under heavy load can also come from an origin that runs out of workers or connections and stops completing handshakes; the origin’s error log will show it.
6. The origin requires a client certificate
If the origin is set to demand a client certificate (mutual TLS) and Cloudflare doesn’t present one, the connection fails. Depending on the server this can be a handshake failure (525), or the handshake completes and the server returns an error page instead; nginx with ssl_verify_client on does the latter, with a 400 “No required SSL certificate was sent”.
Fix: if you meant to restrict the origin to Cloudflare this way, turn on Authenticated Origin Pulls in Cloudflare so it presents its client certificate. If you didn’t mean to, remove the client-certificate requirement.
Should you switch to Flexible?
It hides the error and creates two problems. The leg between Cloudflare and your server becomes plain HTTP, so anyone on that path can read and change traffic, while visitors still see a padlock. And if your origin redirects HTTP to HTTPS, which most do, Cloudflare’s HTTP request gets redirected to HTTPS, which Cloudflare fetches over HTTP again, and visitors get ERR_TOO_MANY_REDIRECTS.
As a five-minute stopgap during an outage, fine. As a fix, no.
Also check whether one hostname runs in a different mode from the rest of the zone. A Configuration Rule or an old Page Rule can set the SSL mode per hostname, and that’s easy to forget when the zone setting looks correct.
The clean fix: Origin CA plus Full (strict)
For most sites the simplest robust setup is:
- In the Cloudflare dashboard, SSL/TLS → Origin Server → Create Certificate. Include the apex and the wildcard (
example.com,*.example.com). You can choose a long validity, up to 15 years. - Install the certificate and private key on the origin, in the 443 server block for every hostname you proxy.
- Reload the web server:
nginx -t && systemctl reload nginx. - Test with the
openssl s_client -servernamecommand above, then set the mode to Full (strict).
Remember that browsers don’t trust Origin CA certificates. If you ever switch a record to DNS only, visitors will see a certificate error for that name. In that case use a public certificate (Let’s Encrypt works fine behind Cloudflare too).
After the fix
- Reload the page. Every error page carries a new Ray ID, so if you still see 525 with a fresh Ray ID, the origin is still failing.
- Test every proxied hostname, not just the one that was reported.
- Run the SSL check on the public hostname to confirm the visitor-facing side presents a valid edge certificate as well.
Related errors
- Cloudflare error 526: the handshake works, but Full (strict) rejects the origin certificate.
- Cloudflare error 522: Cloudflare couldn’t establish a TCP connection to the origin at all.
- Cloudflare SSL modes: Off, Flexible, Full and Full (strict) compared.
- ERR_SSL_VERSION_OR_CIPHER_MISMATCH: the same kind of handshake failure, between the browser and Cloudflare instead.
Check your own domain now
Free, no sign-up. Runs the exact check this guide describes and shows what to fix.