We spent four hours on May 30 chasing a Cloudflare-for-SaaS setup that returned no errors and behaved wrong anyway. No 5xx, no cert warning, no broken padlock in the browser. The TLS handshake completed cleanly. Depending on which customer domain we tested, the page came back as the wrong site or with subtly wrong routing. Neither the Cloudflare docs nor the IIS docs connect the cause: SNI and the HTTP Host header are two different values, and Cloudflare-for-SaaS lets them disagree.
If you front a Windows/IIS app with Cloudflare-for-SaaS so each customer can bring their own domain, this gotcha is waiting for you. Nothing throws, which is what makes it dangerous.
What Cloudflare-for-SaaS actually sends
Cloudflare-for-SaaS (the custom-hostnames product) lets clientcompany.com resolve to Cloudflare’s edge. Cloudflare terminates TLS for that hostname with a cert it manages. The trap is set on the leg from Cloudflare back to your origin.
When Cloudflare connects to your origin over the fallback origin certificate, the SNI on that origin-facing handshake is the custom hostname. Your origin sees a TLS ClientHello with SNI=clientcompany.com. That looks right, and you assume the rest of the request matches.
It doesn’t have to. SNI is a TLS-layer hint sent during the handshake, before any HTTP exists. The Host header is an application-layer value sent inside the request after TLS is up. Different parts of the stack set them, and on Cloudflare-for-SaaS they can carry different values. The SNI follows the custom hostname, while the Host header follows whatever Cloudflare’s origin rules, host-header-override, or your DNS target resolves to.
Why IIS stays quiet
IIS routes by Host header. Site bindings match on hostname:port, and that match decides which site serves a request. SNI matters to IIS only for picking a certificate when you have SNI-enabled HTTPS bindings. Certificate selection and site selection answer two different questions.
So the handshake goes like this:
- IIS selects a cert using the SNI (
clientcompany.com), succeeds, and the handshake completes. - IIS then routes the decrypted request using the Host header, which is not
clientcompany.com, and lands it on whatever binding matches that header.
Cert selection passes and routing goes somewhere else. No layer in the chain holds both halves at once, so none of them raises an alarm.
Why Full hides it
Your zone’s SSL mode decides how strict the origin-facing validation is, and that is what turns a loud failure into a silent one.
On Full (strict), Cloudflare validates the origin certificate against the hostname, so a cert/hostname mismatch tends to surface as a 526 (invalid SSL certificate). You get an error, and errors are findable.
On Full, Cloudflare encrypts to the origin but does not strictly validate that the origin cert matches the hostname. The mismatch passes. The connection is encrypted, the handshake completes, and the request lands on the wrong IIS binding with no signal that anything is off. TLS is healthy, the byte stream is intact, and the application behavior is wrong. Every dashboard says it is fine.
If your zone is on Full and the symptom is “it works but serves the wrong thing for some custom hostnames,” stop looking at Cloudflare’s analytics. From Cloudflare’s perspective the request succeeded, so nothing there will tell you.
The fix lives in the IIS binding
The instinct is to fix this in Cloudflare by changing the SSL mode, adding an origin rule, or tweaking the custom hostname config. That is the wrong layer. Cloudflare is doing exactly what it advertises, and the disagreement between SNI and Host is the product working as designed.
The fix is to make IIS route correctly for the Host header it actually receives. Match the bindings to the traffic Cloudflare delivers for each custom domain, instead of assuming the SNI in the handshake is the value IIS will route on. Host-header routing is decided in the binding, so the correction belongs there.
Even when the handshake used the right hostname, the request may still route to the wrong site. Verify the Host header your origin receives, explicitly, and configure the IIS binding against that observed value.
The origin firewall comes first
One prerequisite sits ahead of all the SNI-versus-Host debugging, and the Cloudflare-for-SaaS docs don’t mention it. Cloudflare reaches your origin from its own egress IP ranges. If your origin sits behind a managed firewall, that firewall has to allow inbound traffic from those ranges, or the edge can never reach you.
For us this was its own vendor ticket and a critical-path blocker. Until that firewall rule existed, nothing downstream could work. From the Cloudflare side the symptom looks nothing like a firewall problem. You see connection failures or timeouts that are easy to misread as DNS or cert issues.
So the order is: open the origin firewall to Cloudflare’s egress ranges, confirm the edge can reach the origin, then debug routing. Do it the other way around and you burn hours on a TLS mystery that is actually a closed port.
What to check
If you put customer domains on Cloudflare-for-SaaS in front of IIS, check which SSL mode the zone uses. On Full, a Host header mismatch serves the wrong site with no error. Ask whoever runs the origin to confirm the IIS bindings match the Host header actually received instead of the SNI, and to open the origin firewall to Cloudflare’s egress ranges before any routing work starts.
Related
- Cloudflare Error 1014: The Cert Issued. The Page Didn’t Load., another silent Cloudflare-for-SaaS failure discovered only by loading the page
- An Off-Origin Caddy Relay for Bare-Domain SSL (Because Cloudflare Pro Won’t Proxy Your Apex), the adjacent apex-SSL problem on the same stack
- The Migration Flip That Took Down a Live Customer (and the Blast Radius We Could Measure in 12 Seconds), what breaks when you flip the cutover switch
- Your IIS Logs Start Lying the Moment Cloudflare Goes Live, the origin-log distortion that comes with proxying
- The False 200: When Removing a Binding Reveals Your Verification Was Lying, IIS binding behavior that returned wrong answers without errors