Ops

Blocking a country's traffic with Cloudflare WAF (without blocking everyone)

One country was sending 96% of our traffic, and almost none of it was real users. Here's the WAF rule that stopped it, the expression mistakes that block everyone or no one, and how to check the rule does what you meant.

· ~5 min read

The symptom

One country sent over 60,000 requests in 24 hours, 96% of all traffic to corsproxy.dev, while every other country combined sent a few hundred. It came in spikes, not the steady daily curve real usage has. That split was the giveaway: this was a flood, not growth. Most of it was unauthenticated, so it reached the Worker before our per-key rate limiter had anything to count.

Why this isn't a job for your app's rate limiter

corsproxy.dev already rate-limits every account with a Cloudflare Durable Object, one atomic counter per account. That's the right tool for "this user is over their quota." It's the wrong tool for "someone is throwing anonymous volume at the edge," because by the time your Worker code runs, you've already paid to run it. You want to reject that traffic before it reaches your application.

That's what a WAF (Web Application Firewall) custom rule is for: it blocks at Cloudflare's edge, before your Worker runs.

The rule

Cloudflare → your zone → Security → WAF → Custom rules → Create rule. Match on ip.src.country, which takes a two-letter ISO 3166-1 country code. Using Germany as an example:

(ip.src.country eq "DE")

Blocking several countries:

(ip.src.country in {"DE" "US"})

Pick an action. Block returns a 403 for every matching request. Managed Challenge is gentler: real people in that country can still get through, and bots mostly can't. If you're not sure the traffic is all junk, start with the challenge. The rule applies to every hostname in the zone: landing page, app, and API.

Older rules and blog posts use ip.geoip.country for the same thing. If you're copying an expression from somewhere, check the field against Cloudflare's field reference.

Mistakes that block too much, or nothing

Checking it actually worked

Don't trust the dashboard toggle alone. Right after deploying:

What this doesn't solve

A country block stops volume from one region. It doesn't stop a flood spread across many countries, and it's a blunt tool if any of your real users live in the blocked country. We used it because the pattern was clear, a 96/4 split with none of the signs of real usage, and because it's one click to undo if that changes.