A customer in San Juan emailed support the exact minute their site went dark for them. My new geo-block was denying their own team every time they tried to load their assets, over and over, because to Cloudflare, Puerto Rico is not US. Every one of my customers is a US or Canadian youth-sports league. There is no league in Hanoi signing up to run a basketball schedule on my platform. So when I looked at a day of Cloudflare traffic and saw roughly 144,000 requests from outside the US and Canada, the right move looked obvious: block all of it. The trap is that the obvious rule, the one almost everyone writes first, also blocks Googlebot when Google crawls you from a datacenter in Germany, and you will not notice until your rankings quietly fall off a cliff.

This is the rule that fixes it, and the way to verify it actually does what you think.

I had treated the customer geography as the whole traffic model. I wrote the first country block without making room for verified crawlers outside that list, then saw the same blind spot when a customer in a territory could not load their assets. The traffic count supported a block, but it did not make that first expression complete.

The traffic that justified the block

I put Cloudflare in front of the production app, a legacy Windows/IIS/classic-ASP platform, and once the orange cloud was on I finally had clean per-country numbers. The non-US/CA breakdown for a single day looked like this:

  • Germany: 74k
  • Singapore: 15k
  • Brazil: 14k
  • Vietnam: 13k
  • France: 12k
  • China: 6k
  • Hong Kong: 5k
  • India: 5k

None of that is funnel value for US youth sports. I checked the referrers to be sure I was not about to block a real customer base I had forgotten about, and they were exactly what you would expect for the business: domestic youth-sports league sites. The foreign traffic was scrapers, residential-proxy swarms, and bots, not prospects.

When 100% of your revenue comes from two countries, blocking the rest simply matches the rule to where your customers actually are.

The naive rule, and why it deindexes you

The first expression you reach for is this:

(not ip.geoip.country in {"US" "CA"})

Action: Block. Done, right?

No. Googlebot does not always crawl you from a US IP. Google runs crawlers out of datacenters in multiple countries, and so do Bing, the AI-search bots, and link-preview fetchers. The moment one of them hits you from a German or Singaporean datacenter, your “block everyone outside US/CA” rule blocks the crawler, you serve it a 403, and over time you fall out of the index. The failure is silent. Nothing errors. Your site is up. Your humans are fine. Your search traffic just erodes, and by the time you connect it to a WAF rule you wrote weeks ago, you have lost rankings that take months to win back.

The fix is one extra clause in the same expression:

(not ip.geoip.country in {"US" "CA"}) and (not cf.client.bot)

That and (not cf.client.bot) is the load-bearing part. It says: block foreign traffic, except when the request is a Cloudflare-verified good bot, in which case let it through regardless of where it is coming from.

Why cf.client.bot and not a User-Agent check

You might think you could allow crawlers by matching the User-Agent string for Googlebot. Do not do that. A User-Agent is a request header. Anyone can type Googlebot/2.1 into theirs, and scrapers do exactly that to slip past lazy allow-lists.

cf.client.bot is different. It is Cloudflare’s own verified-bot signal, validated by reverse DNS and ASN checks against the published IP ranges of known good crawlers. An attacker forging Googlebot in the UA does not match cf.client.bot, because their IP does not reverse-resolve to Google. So this one clause lets real Google, Bing, and the other verified crawlers through from anywhere on the planet while still blocking the spoofer claiming to be them. You get the SEO protection without poking a hole a scraper can climb through.

I also chose Block over Managed Challenge here, deliberately. Start aggressive: a hard block is discoverable through support if a legitimate edge case shows up, and country exceptions can be added on request. A Managed Challenge degrades quietly for everyone; a Block produces a clear signal (a support email) the rare time you are wrong. For a business with a known, narrow customer geography, aggressive-with-an-escape-hatch beats permissive-and-fuzzy.

Verify with real out-of-country nodes, not assumptions

The temptation here is to ship the rule and trust the logic. I do not trust “it should work” on anything I can actually test, and geo-blocking is trivially testable.

I used check-host.net, which fires real HTTP requests at your URL from real nodes in real countries. That is the whole point: you are not guessing what Cloudflare will do to a German request, you are watching it happen from an actual German node. The result table was clean:

  • Brazil: 403
  • Germany: 403 (this one was satisfying, it caught a residential-proxy operator that had been hammering the site)
  • Japan: 403
  • UK: 403
  • Canada: 302
  • US: 302

Blocked everywhere it should block, served everywhere it should serve. I tried Cloudflare’s own Trace beta as a backup, but it would not render for me. check-host was the more authoritative source anyway, because it is a genuine remote client, not a simulation.

One probe needed a second look. An Atlanta US probe came back 403, which momentarily looked like the geo rule misfiring on a US request. It was not. A Los Angeles probe from the same service passed, and the geo rule allows US, so geo cannot be what blocked Atlanta. The likely culprit was a separate empty-User-Agent rule (check-host’s nodes do not all send the same UA, and the empty-UA rule catches that), with SBFM as the other candidate now that JS Detections was on. When you run overlapping defense layers, “why did this specific request block” stops being obvious, and you have to read the actual matched rule rather than assume the one you just shipped is the culprit.

The territory gotcha that follows immediately

There is a second clause this rule needs, and I learned it the hard way the same day. ip.geoip.country uses ISO 3166-1 alpha-2 codes, and under that standard, US territories are their own country codes. US does not include them.

A customer in San Juan emailed support the exact minute they hit the wall. The trace showed their IP getting blocked over and over by the geo rule while trying to load their own team’s assets, because Puerto Rico is PR, not US, to Cloudflare. The same is true of the US Virgin Islands (VI), Guam (GU), American Samoa (AS), and the Northern Mariana Islands (MP). So the real expression, after that fix, became:

(not ip.geoip.country in {"US" "CA" "PR" "VI" "GU" "AS" "MP"}) and (not cf.client.bot)

If your customer base is “the US,” your allow-list has to spell out the territories explicitly, or you will geo-block American citizens off your own product.