When a proxy terminates TLS, there are two separate encrypted connections: browser to edge, and edge to origin. Cloudflare’s encryption modes decide what happens on the second one, and one of the options leaves it unencrypted while the browser still shows a padlock.
That option is called Flexible, it is the one people land on because it works immediately with no origin configuration, and it is a genuinely bad idea on anything carrying a login or a payment.
This walks the modes, explains what each actually does on the wire, and gives the migration path to a properly encrypted setup without an outage in the middle.
Table of contents
- Two connections, not one
- The modes, in plain terms
- Why Flexible is worse than it looks
- Getting to Full (strict) without an outage
- Making the application aware it is behind a proxy
- When you do not need any of this
- How this fits the rest of the stack
- FAQ
Two connections, not one
The mental model that clears up most confusion: when a proxy sits in front of your server, the padlock in the browser describes only the first half of the journey.
Connection one runs from the visitor’s browser to the edge. The edge holds a certificate for your domain, decrypts the request, and can therefore read it, cache it, apply rules to it and rewrite it.
Connection two runs from the edge to your origin server. It is a completely separate connection with its own settings, and the browser has no visibility into it whatsoever.
This is why a site can show a valid certificate and still transmit passwords in plain text across the internet between the proxy and the origin. The padlock is honest about connection one and silent about connection two.
The modes, in plain terms
Off. No encryption at all, and requests over HTTPS are redirected to HTTP. Not appropriate for anything public.
Flexible. The browser connects over HTTPS to the edge. The edge connects to your origin over plain HTTP on port 80. Traffic between the proxy and your server crosses the public internet unencrypted.
Full. The edge connects to your origin over HTTPS, and does not check whether the origin’s certificate is valid. A self-signed or expired certificate is accepted. Encrypted, but not authenticated, so an attacker able to intercept the edge-to-origin path can present any certificate and be believed.
Full (strict). The edge connects over HTTPS and validates the origin certificate properly. This is the only mode that provides both encryption and authentication end to end, and it is the target.
There is also an automatic mode that selects a setting based on what your origin appears to support. It is a reasonable default for a new zone and it is not a substitute for deciding deliberately.
Why Flexible is worse than it looks
Beyond the obvious problem of unencrypted traffic, Flexible mode creates two failure modes that are genuinely hard to diagnose.
The redirect loop. Your origin is configured, sensibly, to redirect HTTP to HTTPS. The edge connects over HTTP. The origin responds with a redirect to HTTPS. The browser requests HTTPS, the edge again connects over HTTP, and around it goes until the browser gives up with a too-many-redirects error. The fix people usually reach for is disabling the origin’s HTTPS redirect, which entrenches the insecure setup.
Mixed protocol confusion. Your application sees a request that arrived on port 80 and concludes the visitor is on HTTP. It then generates absolute URLs with the wrong scheme, sets cookies without the Secure flag, and any framework logic branching on the request scheme takes the wrong path. The X-Forwarded-Proto header carries the truth, but only if your application reads it.
The security problem is the important one. Flexible mode means every password, session cookie and API key your site handles travels the public internet in clear text between the proxy and your server, while the visitor sees a padlock telling them it is private. That is worse than no HTTPS, because it is a false assurance rather than an absent one.
Getting to Full (strict) without an outage
The migration is straightforward if you do it in the right order. The mistake is switching the mode first and then discovering the origin cannot serve HTTPS.
- Get a certificate onto the origin. Either a publicly trusted certificate, or an origin certificate issued by the proxy provider, which is trusted by their edge but not by browsers. The latter is free, long-lived and designed for exactly this, and it only works because nothing but the edge ever connects directly.
- Configure the origin web server to serve HTTPS on 443 with that certificate, and confirm it works before changing anything at the edge.
- Verify from outside, bypassing the proxy, so you are testing the origin rather than the cache.
- Switch the mode to Full first, not straight to strict. If something is wrong, Full still works and you have isolated the problem to certificate validation rather than connectivity.
- Then switch to Full (strict). If it breaks here, the certificate is expired, self-signed, or does not cover the hostname the edge is requesting.
- Firewall the origin to the proxy’s address ranges, which is what makes the whole arrangement meaningful rather than merely encrypted.
Test the origin directly with an explicit resolve, so you know you are talking to your server rather than the edge:
curl -sv --resolve example.com:443:ORIGIN_IP https://example.com/ 2>&1 \
| grep -E 'subject|issuer|SSL certificate verify'
Making the application aware it is behind a proxy
Once the edge is terminating TLS, your application is receiving requests from the proxy rather than from users, and it needs to be told.
Three headers carry the original context, and ignoring them causes real bugs:
- X-Forwarded-Proto carries the scheme the visitor actually used. Without it, secure cookies are not set and scheme-dependent redirects go wrong.
- The visitor’s address header carries the real client address. Without it, every request appears to come from the proxy, which makes rate limiting, geolocation and audit logs meaningless.
- X-Forwarded-Host carries the hostname requested, which matters when your origin serves several sites.
Most frameworks have a trusted-proxy setting that reads these correctly. Enable it, and configure it to trust only the proxy ranges rather than any source, because a header from an untrusted client is spoofable and you would be letting a visitor claim any address they like.
One more thing worth setting deliberately: enable HSTS only once you are on Full (strict) and confident. HSTS instructs browsers to refuse HTTP for your domain for a fixed period, which is exactly what you want and is very hard to undo if you set a long duration and then need to roll back.
When you do not need any of this
This entire configuration surface exists because you are running an origin server that a proxy has to reach securely. If your application is deployed on a platform that terminates TLS at its own edge and routes to your service over its internal network, there is no origin-to-edge hop to configure and no mode to choose wrongly.
On RunxBuild, attaching a custom domain provisions and renews the certificate as part of the domain setup, and the route between the edge and your service is internal rather than a public hop you have to secure. The failure mode this article is about does not exist because the second connection is not yours to configure.
That is not an argument against using a proxy, which buys caching, filtering and denial-of-service absorption that a platform edge may not. It is an argument for knowing which of the two connections you are actually responsible for, and Flexible mode is what happens when nobody asks.
How this fits the rest of the stack
Certificate handling is cheap in money and expensive in attention, and a misconfigured proxy mode is the kind of mistake that stays invisible until an audit or an incident finds it. When you are sizing a deployment it is worth knowing which pieces come handled and which are yours, and the RunxBuild hosting calculator covers the priced parts of that picture while the domain and certificate handling comes with the service rather than as separate work.
Useful related references:
- TLS Handshake Failed: 7 Causes and the Right Fix
- Port 465: Implicit TLS, the Deprecation That Got Reversed, and When to Use It
- TCP Port Syslog: 514, 6514 (TLS), and the rsyslog/syslog-ng Setup
- Custom domains and certificates on RunxBuild
FAQ
What is the difference between Flexible and Full SSL mode?
Flexible encrypts only between the browser and the proxy edge; the connection from the edge to your origin is plain HTTP. Full encrypts both hops but does not validate the origin certificate. Full strict encrypts and validates both, which is the only mode providing genuine end-to-end protection.
Why does Flexible mode cause a redirect loop?
Your origin redirects HTTP to HTTPS, but the edge always connects over HTTP. The origin returns a redirect, the browser retries over HTTPS, the edge connects over HTTP again, and the cycle repeats until the browser gives up. The correct fix is switching to Full strict, not removing the origin’s redirect.
Do I need a publicly trusted certificate on my origin?
Not necessarily. An origin certificate issued by your proxy provider is trusted by their edge but not by browsers, which is sufficient because only the edge connects to your origin directly. It is free and long-lived. This only holds if you also firewall the origin so nothing else can reach it.
Why does my application think visitors are using HTTP?
Because the proxy connects to your origin on a separate connection, and your application is reading that connection’s scheme rather than the visitor’s. Read the X-Forwarded-Proto header instead, and enable your framework’s trusted-proxy setting scoped to the proxy address ranges.
Is the padlock in the browser meaningful behind a proxy?
It confirms the browser-to-edge connection is encrypted and the edge presented a valid certificate for your domain. It says nothing about the edge-to-origin hop, which is why a site in Flexible mode shows a padlock while transmitting passwords in clear text between the proxy and the server.