Two connections, two certificates
When a hostname is proxied through Cloudflare (orange cloud), a visitor never talks to your server directly. There are two separate connections:
- The browser to Cloudflare’s edge. This uses Cloudflare’s edge certificate, normally the free Universal SSL certificate Cloudflare issues for you.
- Cloudflare’s edge to your server, the origin. This uses whatever your server is configured with, or nothing at all.
The SSL/TLS encryption mode in the dashboard (SSL/TLS → Overview) controls only the second connection. It decides whether Cloudflare talks HTTP or HTTPS to your origin, and whether it checks the certificate it gets back. The padlock in the visitor’s browser comes from the first connection and looks the same in every mode except Off. That’s exactly why a bad choice here goes unnoticed for years.
The modes
| Mode | Cloudflare → origin | Origin certificate check |
|---|---|---|
| Off | HTTP. HTTPS requests to the site are redirected to HTTP | None |
| Flexible | HTTP, port 80 | None, the origin needs no certificate |
| Full | HTTPS when the visitor used HTTPS | Any certificate is accepted |
| Full (strict) | HTTPS when the visitor used HTTPS | Must be valid: unexpired, trusted, right name |
Two details people miss:
- In Full and Full (strict), Cloudflare follows the visitor’s scheme. A visitor who types
http://is fetched from your origin over HTTP on port 80, not upgraded. Turn on “Always Use HTTPS” (SSL/TLS → Edge Certificates) so plain HTTP visitors are redirected at the edge and never reach your port 80. - Cloudflare also lists a mode called Strict (SSL-Only Origin Pull), which always uses HTTPS to the origin with full validation, even for HTTP visitors. At the time of writing it’s limited to Enterprise plans, so most sites will only see the four above.
On newer zones the overview page may show Automatic SSL/TLS instead of a fixed mode. In that setting Cloudflare probes your origin and picks the strictest mode it believes will work. If you want a predictable result, pick Custom and set the mode yourself.
Flexible vs Full
Flexible exists for origins that have no HTTPS at all. Cloudflare terminates TLS for the visitor and then fetches your page over plain HTTP. The visitor sees a padlock, but the second half of the trip is unencrypted. Anyone on the path between Cloudflare and your server, whether a hosting network, a compromised router or a shared VLAN, can read form posts, session cookies and passwords, and can change the page.
Flexible also breaks things functionally, because your application thinks every request is HTTP:
- If the origin redirects HTTP to HTTPS (most do, and WordPress does whenever its site URL starts with
https://), you get a redirect loop. More on that below. - Applications build absolute URLs from the request scheme, so pages come out with
http://links and assets. That’s mixed content. - Cookies set with the
Secureflag based on the request scheme may be set without it.
Full fixes all of that by talking HTTPS to the origin. It needs the origin to actually serve TLS on port 443. If nothing does, Full gives you error 525 (the port answers but not with TLS) or a connection-refused error (nothing listening on 443).
Full vs Full (strict)
In Full mode Cloudflare doesn’t validate the origin certificate at all. Self-signed, expired, issued for a different hostname: all accepted. The connection is encrypted, but Cloudflare has no idea whether it’s talking to your server or to something that intercepted the connection and presented its own certificate.
Full (strict) adds the check you’d expect. The origin certificate must be:
- unexpired,
- issued by a publicly trusted CA or by Cloudflare’s Origin CA,
- valid for the hostname, either the one the visitor requested or, for a proxied CNAME, its target. Cloudflare checks the SAN list, as browsers do.
If any of those fails, visitors get error 526 instead of the page. That’s the whole cost of strict mode: one more thing to keep valid. Since an Origin CA certificate can be valid for up to 15 years and a Let’s Encrypt certificate renews itself, the cost is small.
Which one to use
Full (strict), on every proxied hostname. The other modes are transitional states:
- Flexible is for an origin that can’t do HTTPS yet. Fix the origin, then leave.
- Full is for an origin whose certificate you can’t make valid right now, for example while a renewal is broken. It’s better than Flexible because the traffic is at least encrypted, but treat it as temporary.
- Off is almost never what you want for a public site.
One opinion worth stating: “I’ll use Flexible because it just works” usually means the site already has a redirect loop or mixed-content problem waiting to happen. Getting the origin a certificate takes ten minutes.
Symptoms of a mismatch
When the mode and the origin disagree, the error tells you which way:
| Setup | What visitors get |
|---|---|
| Flexible, origin redirects HTTP to HTTPS | ERR_TOO_MANY_REDIRECTS |
| Flexible, origin listens only on 443 | Error 521 (port 80 refused) |
| Full or strict, nothing listening on 443 | Error 521 |
| Full or strict, port 443 open but no TLS or no usable certificate | Error 525 |
| Full (strict), certificate expired, self-signed or wrong name | Error 526 |
Flexible, app generates http:// URLs | Mixed content warnings, broken styles |
If you’re a visitor and see one of these on someone else’s site, there’s nothing to fix on your side; clearing cookies won’t break a loop that happens between Cloudflare and the server. Only the site owner can change it.
Fixing the Flexible redirect loop
Confirm it’s a loop and see where it points:
curl -sIL --max-redirs 5 https://www.example.com/ | grep -iE '^(HTTP|location)'
HTTP/2 301
location: https://www.example.com/
HTTP/2 301
location: https://www.example.com/
...
curl: (47) Maximum (5) redirects followed
A redirect to the exact same HTTPS URL is the Flexible signature: the origin saw HTTP and redirected to HTTPS, which the browser then requested from Cloudflare again. The HTTP check shows the same chain from outside.
The real fix is to give the origin a certificate and switch to Full (strict). If you have to stay on Flexible for a while, make the application trust the scheme Cloudflare reports instead of its own connection. Cloudflare sends a CF-Visitor header with the visitor’s scheme, for example {"scheme":"https"}. In WordPress, for instance, in wp-config.php above the line that loads wp-settings.php:
if (isset($_SERVER['HTTP_CF_VISITOR']) && strpos($_SERVER['HTTP_CF_VISITOR'], '"scheme":"https"') !== false) {
$_SERVER['HTTPS'] = 'on';
}
Only do this if the origin accepts traffic from Cloudflare’s IP ranges alone. Otherwise anyone can send that header directly to your server.
Also check that there’s no second redirect layer fighting the first. A “Force HTTPS” plugin, an .htaccess rule and a Cloudflare redirect rule can each be correct alone and loop together. Redirect loop diagnosis covers the general case.
Moving to Full (strict) without an outage
Test the origin the way Cloudflare will reach it, before you flip the switch. You need the origin’s real IP, which is the A or AAAA value in the Cloudflare DNS dashboard (public DNS only shows Cloudflare’s addresses for proxied records).
curl -v --resolve www.example.com:443:203.0.113.10 https://www.example.com/ -o /dev/null
With a public certificate (Let’s Encrypt or a commercial CA), this should succeed without -k. If it fails with an expiry or name error, strict mode will fail the same way. With an Origin CA certificate your machine won’t trust it by default. Download Cloudflare’s Origin CA root certificate (RSA or ECC, matching your certificate) from Cloudflare’s documentation and pass it with --cacert; then the test checks what strict mode checks.
Then:
- If the origin has no usable certificate, create one: SSL/TLS → Origin Server → Create Certificate, with
example.comand*.example.com. Install the certificate and key in the HTTPS server block of every proxied hostname and reload (nginx -t && systemctl reload nginx). - Set the mode to Full (strict).
- Look for per-hostname overrides. A Configuration Rule, or a legacy Page Rule, can set a different SSL mode for one hostname or path, and the zone-wide setting won’t tell you.
- Load every proxied hostname, not only the main one. Subdomains pointing at other servers are where 526 shows up a week later.
You can confirm the zone setting through the API; the value is one of off, flexible, full or strict:
curl -s https://api.cloudflare.com/client/v4/zones/$ZONE_ID/settings/ssl \
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result.value'
Origin CA certificates and their catch
Origin CA certificates are free, issued in seconds from the dashboard, and accepted by Full (strict). You can pick a validity from days up to 15 years and cover your zone’s hostnames, including a one-level wildcard.
The catch is that only Cloudflare trusts them. Browsers, curl, uptime monitors and other CDNs will reject them. That’s fine while the hostname stays proxied. It bites when someone switches a record to DNS only “to test something” and visitors suddenly get a certificate error, or when an external monitor checks the origin directly. If either is likely, use a Let’s Encrypt certificate on the origin instead; it works behind Cloudflare in Full (strict) just as well.
What the SSL mode doesn’t control
- The visitor-facing certificate. Universal SSL covers
example.comand*.example.com, one level deep. A proxieda.b.example.comgets a browser name error no matter which mode you pick; see ERR_CERT_COMMON_NAME_INVALID. - HTTP to HTTPS redirects for visitors. That’s Always Use HTTPS or a redirect rule.
- HSTS. Set it in Cloudflare or on the origin, but only once every hostname works over HTTPS. See the HSTS preload checklist.
The SSL check shows the certificate visitors receive for a hostname. For a proxied hostname, that’s Cloudflare’s edge certificate, not your origin’s.
Related
- Cloudflare error 525: Full or strict mode, and the TLS handshake with the origin fails.
- Cloudflare error 526: Full (strict) rejects the origin certificate.
- Cloudflare error 522: Cloudflare can’t reach the origin at all.
- Redirect loop diagnosis: finding loops that aren’t caused by the SSL mode.
Check your own domain now
Free, no sign-up. Runs the exact check this guide describes and shows what to fix.