Troubleshooting
HTTPS, TLS & DNS

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.

Where TLS breaksVisitorHTTPS to CloudflareCloudflareHTTPS to originOrigin TLSmissing or invalid525 or 526error pageBoth errors are about the Cloudflare-to-origin hop, configured by the SSL/TLS mode.
  1. Check the SSL/TLS mode in the Cloudflare dashboard: Full (strict) is the right target.
  2. 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.
  3. 526: install a valid certificate on the origin, either Let's Encrypt or a free Cloudflare Origin CA certificate.
  4. Make sure the certificate covers every proxied host name, including www.
  5. 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.

what the direct test showsOrigin test showsCause and fixconnection refused on 443525; no TLS listeneror firewall; open 443handshake error525; no cert for this SNIname; fix server_nameexpired or self-signed cert526 in strict; renew or use Origin CAcert for another name526; add the host to the certificateTest the origin with the right SNI name before changing Cloudflare settingsdjango-deployment.com
Switching to Flexible mode hides these errors but sends origin traffic unencrypted.

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.

mode versus origin requirementsOrigin connectionOrigin certificateFlexible!plain HTTPnoneFullHTTPSany, even self-signedFull (strict)HTTPSvalid or Origin CAFull (strict) with an Origin CA certificate is the safe defaultdjango-deployment.com
Error 525 can occur in Full and Full (strict); 526 only in Full (strict).

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

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.

Keep going