Fix Cloudflare 525 and 526 SSL Errors for a Django Site
Fix Cloudflare Error 525 SSL handshake failed and Error 526 invalid SSL certificate in front of Django: match the SSL/TLS mode to the origin, install a valid or Origin CA certificate on Nginx, open port 443, and check SNI and expiry.
Problem statement
Visitors see a Cloudflare error page instead of your Django site:
Error 525 SSL handshake failed
Error 526 Invalid SSL certificate
Both concern the connection from Cloudflare to your origin server, not the visitor's connection to Cloudflare. With SSL/TLS mode Full or Full (strict), Cloudflare connects to your server over HTTPS on port 443. A 525 means that TLS handshake failed outright: nothing speaks TLS on 443, or the server and Cloudflare cannot agree on a protocol or certificate for the host name. A 526 means the handshake worked but, in Full (strict) mode, the origin's certificate is expired, self-signed, or for a different name.
- Check the SSL/TLS mode in the Cloudflare dashboard: Full (strict) is the right target.
- 525: make Nginx (or your proxy) listen on 443 with TLS for this host name; test with
openssl s_client -connect origin_ip:443 -servername example.com. - 526: install a valid certificate on the origin, either Let's Encrypt or a free Cloudflare Origin CA certificate.
- Make sure the certificate covers every proxied host name, including
www. - Allow Cloudflare's IP ranges to reach port 443 in your firewall.
Step-by-step solution
1. Test the origin directly
Bypass Cloudflare by connecting to the origin's IP with the right SNI name:
openssl s_client -connect 203.0.113.10:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
curl -sv --resolve example.com:443:203.0.113.10 https://example.com/ -o /dev/null
- Connection refused or no certificate printed: nothing serves TLS on 443, which gives a 525.
- A certificate that is expired, self-signed, or for another name: that gives a 526 in strict mode.
- A valid certificate and a 200 response: the origin is fine; check Cloudflare settings and firewall rules.
2. Fix 525: serve TLS on 443
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/cloudflare/origin.pem;
ssl_certificate_key /etc/ssl/cloudflare/origin.key;
location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
}
}
Other causes of 525: the firewall allows port 80 but not 443 from Cloudflare, the origin only supports old TLS versions, or a default server block without ssl answers the host name. Cloudflare sends SNI, so the server_name must match.
3. Fix 526: install a trusted certificate
Two good options:
- Cloudflare Origin CA certificate. Create it under SSL/TLS, Origin Server. It is free, valid up to 15 years, and trusted only by Cloudflare, which is exactly the client that connects. Install the certificate and key in Nginx.
- Let's Encrypt. Works with the DNS-01 challenge through Cloudflare's API, or HTTP-01 if port 80 reaches the origin. Make sure renewal works, or the certificate expires in 90 days and 526 returns.
Then set the mode to Full (strict).
4. Cover every proxied host name
If www.example.com and api.example.com are proxied (orange cloud), the origin certificate must include them. An Origin CA certificate for example.com and *.example.com covers one level of subdomains.
5. Keep Django aware of HTTPS
With TLS terminated at Nginx behind Cloudflare, Django still needs to know requests are secure:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
Avoid Flexible mode with SECURE_SSL_REDIRECT = True: Cloudflare would connect over HTTP, Django would redirect to HTTPS, and visitors loop. See Fix Django Too Many Redirects With HTTPS.
Explanation
Cloudflare's proxy terminates the visitor's TLS at its edge and opens a separate connection to your origin. The SSL/TLS mode controls that second connection. In Full modes it performs a normal TLS handshake with SNI set to the requested host name; failure of the handshake itself is 525. In Full (strict), Cloudflare additionally validates the certificate chain, expiry, and name, and a failed validation is 526. The visitor's browser never sees your origin certificate, which is why a Cloudflare-only Origin CA certificate is acceptable.
Edge cases / notes
- Origin rules and ports. If you route to a non-standard origin port, that port must serve TLS too.
- Authenticated origin pulls. Requiring Cloudflare's client certificate at Nginx adds protection; a misconfiguration there also shows as 525.
- Load balancers. If a cloud load balancer terminates TLS before Nginx, its certificate is the one Cloudflare validates.
Next steps
- Set up the whole chain with Deploy Django Behind Cloudflare.
- For origin unreachable errors, see Fix Cloudflare 521 and 522 Errors.
- Certificate files not loading? Read Fix Nginx Cannot Load Certificate.
FAQ
What is the difference between 525 and 526?
525 means the TLS handshake with the origin failed. 526 means it succeeded but the certificate was invalid in Full (strict) mode.
Can I use a self-signed certificate on the origin?
In Full mode, yes, but then Cloudflare does not verify the origin. Use a free Origin CA certificate with Full (strict) instead.
Should I switch to Flexible to fix it?
No. Flexible sends traffic from Cloudflare to your server unencrypted and often causes redirect loops with Django.
Does the visitor see my Origin CA certificate?
No. Visitors see Cloudflare's edge certificate. Only Cloudflare sees the origin certificate.