Cloudflare was blocking every visitor to our site. No log showed it.
Browser Integrity Check 403'd real browser sessions with no Security Events entry, an IP skip rule hid it from us for a week — and every synthetic check we ran kept passing.
For about a week, real people could not reach our site. Browsers got a 403 before a single byte of our application ran. Every check we had said everything was fine, and the two facts are not in tension — they are the story.
The blocker was Cloudflare's Browser Integrity Check — a zone-level feature that inspects requests for "suspicious" browser signatures and challenges or blocks what it dislikes. Some combination of our visitors' clients and BIC's heuristics started scoring real browser sessions as non-browsers, and BIC answered them with a 403 at the edge. Not a challenge page they could click through. A block, upstream of everything we owned.
Two layers of invisibility
The block did not log where blocks log. The natural move when you suspect Cloudflare is blocking someone is the Security Events dashboard — the surface that records WAF hits, rate limits, firewall rules. BIC denials were not there. The feature sits outside the event stream you would audit, so "no security events" read as "nobody is being blocked" when it actually meant "this blocker does not file reports". You cannot clear a suspect by searching records the suspect does not appear in — absence of evidence, in a log that does not cover the mechanism, is evidence of nothing. It is the monitoring-shaped hole from the webhook that never delivered again: the truth lived in a surface nobody was reading, except this time there was no surface at all.
Our own checks were exempt. Months earlier, someone had added a skip rule for our office IPs — the reasonable kind of rule you add so internal tooling and uptime checks do not trip security features. That rule meant every check we ran, from every machine we habitually checked from, bypassed BIC entirely. The site was genuinely up for us. The allowlist did exactly what allowlists do: it made the problem invisible to precisely the people equipped to notice it. An exemption for yourself is a blindfold you fit yourself, and its cost arrives only on the day something breaks for everyone who is not you.
So the failure reached us the only way it still could: humans, off-network, saying "your site is broken", against a dashboard insisting it was not.
Diagnosing when curl works and browsers don't
The signature of an edge-layer block is the split: curl succeeds while a real browser fails, or your network succeeds while others fail. That split localises the fault above your application — nothing in your code distinguishes visitors that way, but security edges do, by fingerprint and reputation. The checklist that would have cut our week to an hour:
- Reproduce from an IP that is not yours. A phone off wifi, a cloud VM, a colleague in another country. If it fails there and passes for you, hunt for the skip rule that is protecting you from the truth — you will find it in your own config.
- Read the failing response body. A 403 from your app looks like your app; an edge block page names the edge and usually carries a Ray ID. The victim's screenshot identified the layer in one glance — it was never going to look like our stack, because it was not our stack.
- Enumerate the features that act before your origin, and for each ask: where do its decisions log, and do they log at all? BIC, managed challenges, bot scoring, super bot fight mode — several act silently or semi-silently. A blocker with no event stream can only be tested behaviourally, from outside.
- Distrust green dashboards that measure from inside the exemption. Synthetic monitoring is only worth what its vantage point sees. Ours saw a site that BIC never touched.
The fix itself was an anticlimax — adjust the feature, verify from an external, unexempted vantage point, done. The week was spent not knowing; the fix took minutes. That ratio is typical of this failure class, and it is why the checklist above is the post.
What monitoring from outside means now
The durable change was to our definition of "up": checks must traverse the same path a stranger's browser does. External vantage, no allowlisted IPs, executing the real page — not grepping HTML, because a grep passes on pages a browser chokes on in a dozen ways, of which this was only the most brutal. The same discipline applies one layer down in our own product: the proxy sits in front of AI providers the way Cloudflare sat in front of us, which is exactly why a detected failure names its layer instead of getting attributed to whoever happened to be furthest downstream. When you are an edge, the lesson reads in both directions.
What to do
Today, from a network you never use, load your site in a clean browser — not curl — and check any security-edge features you run for two things: where their block decisions are logged, and whether your own IPs are exempted from them. If a feature logs nowhere, schedule a behavioural check from outside; if you are exempt, understand that your uptime monitoring is measuring a different site than your visitors get. And when a report says "it's broken for me", ask for the response body before you trust any dashboard — the body names the layer, and the dashboard may be standing inside the exemption.