“I thought the whole point was to have them manage this for me.” That was the line I caught myself thinking halfway through putting Cloudflare in front of my legacy Windows/IIS/ASP app. Then I looked at the numbers: 17k mitigated events out of 1.98M requests in a 24-hour window, a 0.9% mitigation rate. The other 99.1% is sailing straight through to my origin, untouched. One example of where the gap shows up: the “Likely Automated” SBFM tier that catches ambiguous bot traffic is Enterprise-only. On Pro, that bucket does not exist.

If you are about to pay for a CDN tier expecting it to handle your traffic, read this first. Pro is not a managed service. It is a box of levers, on by default at a setting that almost never breaks legitimate traffic, which also means it rarely blocks anything. The marketing implies protection. What you actually buy is the toolkit to build protection. Those are different products.

The numbers that reframed it

Here is the 24-hour snapshot that did it for me: 1.98M requests, 64.14 GB transferred, 286.84k visits. Mitigated events: 17k. Cloudflare was not quietly doing its job behind the scenes there. That 0.9% is what gets stopped leaning on the defaults, and the defaults are deliberately timid. Every aggressive rule I added that day (a verified-bot skip, JS Detections, a geo block, an empty-user-agent custom rule) had to be built by hand, and even with those running the mitigated share stayed under 1%.

That timidity is a philosophy, not a bug. Cloudflare’s posture on Free and Pro is “neutral pipe plus opt-in protection.” They will not pre-block an abusive ASN, because their stance is don’t break a paying customer’s traffic on a guess. Which is reasonable, and also means the interesting traffic, the stuff actually worth caring about, is whatever is passing through right now while you assume the dashboard has it covered. It does not. The mitigation number is small precisely because the aggressive decisions are yours to make, not theirs.

The log product is the tell

The clearest example of dashboard-versus-managed came from a mistake I made myself. Partway through this work I claimed Log Search was available on Pro. It is not. Log Explorer is a paid add-on, separate from the Pro subscription. I had to correct myself mid-session.

That correction is the whole lesson in miniature. When you go looking for the trace surface to answer “who is actually hitting me and what is getting through,” the CDN’s answer is another line item. Here is the part that matters for anyone running a self-hosted app: I did not need it. My own origin logs were the better trace surface anyway, because by then they were capturing the real client IP from the CF-Connecting-IP header instead of the Cloudflare edge IP. The origin sees every request in full, with my own fields, on disk, free. The CDN’s paid log product would have told me less, later, for money.

What Pro does give you in-tier is the GraphQL Analytics API. It returns the aggregate verdict counts, the served-versus-mitigated breakdown, the per-action totals. For the analysis I actually needed, the 0.9% number and what was driving it, no upsell was required. That API plus my own origin logs covered it. The paid log add-on would have been a tax on a question I could already answer.

What Pro actually gates

Once you stop expecting management and start expecting tools, the next question is which tools you actually get. The Pro tier has real edges, and several of them are exactly where you reach for control and find a wall:

  • No “Likely Automated” SBFM tier. Super Bot Fight Mode on Pro gives you Definitely Automated (Allow or Block), Verified Bots (Allow), a JS Detections toggle, and Static Resource Protection (greyed out until you turn JS Detections on). The middle “Likely Automated” bucket, the one that catches the ambiguous traffic, is Enterprise only.
  • The Managed Ruleset is on by default and you cannot configure it. You can only add exceptions. If the managed ruleset is doing something you do not want, your single move is a higher-priority Skip rule. You do not get to tune the ruleset itself.
  • matches regex is Enterprise. On Pro, custom rule expressions give you contains, not matches. If your blocking logic needs a real regular expression, you are out of luck at this tier.
  • Rate-limit Period minimum is 10 seconds. Fine for most cases, worth knowing before you design around something tighter.

None of these are dealbreakers. They are the shape of the tool. The point is that you find out where the edges are by hitting them mid-incident, not by reading the pricing page, because the pricing page sells outcomes and the product ships levers.

A practical way to tell who served the 403

One trick from doing this against a self-hosted origin: when something returns a 403, you need to know whether Cloudflare blocked it at the edge or your origin returned it. The headers tell you. A Cloudflare-served block carries a cache-control header and is missing the origin’s fingerprint, while a response that actually reached the origin on an ASP.NET app carries X-Powered-By: ASP.NET. The CF-RAY header is on both (Cloudflare stamps every response that passes through it), so it is the presence or absence of the origin fingerprint that tells you who answered. If you are debugging “why did this request get a 403” with a CDN in front, that distinction is the first thing to check, because the two failure modes look identical from the browser and demand opposite fixes.