Security

Cloudflare WAF Custom Rules: A Production Cookbook

27 Cloudflare WAF custom rule recipes for production sites: scraper blocks, geo restrictions, credential stuffing, OTP abuse, admin protection, API hardening.

Engineering Team
9 min read

This is a cookbook of 27 Cloudflare WAF custom rules used in real production deployments, grouped by purpose: access control, scraper blocking, credential abuse, payment and checkout protection, admin and CMS hardening, and API protection. Each recipe has the rule name, the reason to deploy it, the expression to paste into the Cloudflare dashboard, and the recommended action. Managed rules and the OWASP Core Rule Set are necessary but not sufficient. Custom rules are where business-specific protection lives, and they typically block more traffic than managed rules combined.

Most articles about Cloudflare custom rules show two or three examples and stop. This one is organised by attack pattern so you can pick the recipes that match your site’s risk profile, deploy them in log mode, then promote to block. Every expression is production-tested.

How do I write a Cloudflare WAF custom rule?

A Cloudflare custom rule has three parts: a name, an expression (the Rules language predicate that decides whether the rule fires), and an action (block, managed challenge, JavaScript challenge, skip, log). Open the dashboard at Security > WAF > Custom rules, click Create rule, paste the expression, set the action, and save.

Two atomic facts worth remembering: rule order matters (Cloudflare evaluates top to bottom, first match wins), and “skip” rules placed above block rules are how you handle false positives without weakening the block rules themselves. The official Cloudflare Rules language reference covers the full expression syntax.

Deploy every new rule in log mode for at least one week before flipping to block. False positives are how this rolls out badly. Watch the Security Events log, fix the rule, then promote.

Access Control Rules

1. Geo-restrict the corporate B2B portal

Why: B2B portals usually serve a fixed list of countries. Everything else is noise or reconnaissance.

(http.host eq "corporate.example.com") and not (ip.geoip.country in {"SA" "AE" "KW" "QA" "BH" "OM"})

Action: Block.

2. Allow-list admin paths to office IPs

Why: The CMS and ops dashboards should never be reachable from the open internet.

(http.request.uri.path contains "/admin" or http.request.uri.path contains "/wp-admin") and not (ip.src in {1.2.3.0/24 5.6.7.0/24})

Action: Block.

3. Block known abuse country origins on signup

Why: Account fraud signups concentrate in a handful of source countries on most consumer platforms. Block them on signup specifically (not site-wide).

(http.request.uri.path eq "/api/signup") and (ip.geoip.country in {"CN" "RU" "VN" "ID"})

Action: Managed challenge. Use block only if you have no legitimate users from those countries.

4. Block Tor and known VPN exit nodes on payment

Why: Tor and commercial VPN traffic is disproportionately associated with carding attempts on payment endpoints. Real customers paying with stolen cards do not need Tor.

(http.request.uri.path eq "/api/payment") and (ip.geoip.is_in_european_union or cf.threat_score gt 30)

Action: Managed challenge.

5. Block ASNs known for hosting scrapers

Why: Cheap cloud ASNs are where most scraping infrastructure lives. Block them on pricing and listing endpoints.

(http.request.uri.path in {"/api/pricing" "/api/search" "/api/listings"}) and (ip.geoip.asnum in {16509 14618 14061 39351})

Action: Managed challenge. Note: 16509 = AWS, 14618 = AWS, 14061 = DigitalOcean, 39351 = OVH. Tune to your own threat data.

Scraper and Bot Blocking Rules

6. Block requests with no User-Agent

Why: Every real browser sends one. Empty UA is automation.

(http.user_agent eq "") and not (http.request.uri.path eq "/health")

Action: Block. Exempt your health checks.

7. Block known scraper user agents

Why: Headless Chrome, Selenium, Playwright, PhantomJS, and named scrapers are the bulk of automated traffic.

(http.user_agent contains "HeadlessChrome") or (http.user_agent contains "PhantomJS") or (http.user_agent contains "scrapy") or (http.user_agent contains "Python-urllib") or (http.user_agent contains "Go-http-client")

Action: Managed challenge on read-only endpoints, block on write endpoints.

8. Require browser-standard headers on pricing endpoints

Why: Real browsers send Accept-Language and Accept-Encoding. Scrapers often skip these.

(http.request.uri.path eq "/api/pricing/search") and not (http.request.headers["accept-language"][0] matches "^[a-z]{2}")

Action: Managed challenge.

9. Block AI training crawlers

Why: AI scraping is now a significant share of automated traffic. Many crawlers (GPTBot, ClaudeBot, Bytespider, CCBot) identify themselves. Decide whether you want your content used for AI training before deciding the action.

(http.user_agent contains "GPTBot") or (http.user_agent contains "ClaudeBot") or (http.user_agent contains "anthropic-ai") or (http.user_agent contains "Bytespider") or (http.user_agent contains "CCBot")

Action: Block, or allow if you want to be in AI training data. For most commercial sites, block.

10. Allow-list verified bots before any block rule

Why: Googlebot, Bingbot, and monitoring services need to pass through. Put this rule above every other block rule.

cf.client.bot

Action: Skip remaining rules. Position: top of list.

Credential Abuse Rules

11. Reject empty referrer on login POST

Why: Real users get to login from a browser session with a referrer. Empty referrer on login POST is almost always credential stuffing.

(http.request.method eq "POST") and (http.request.uri.path eq "/api/login") and (http.referer eq "")

Action: Block.

12. Block requests to /login with no cookies set

Why: Real users have at least a session or analytics cookie by the time they reach login. Cookieless requests indicate automation.

(http.request.uri.path eq "/api/login") and (http.request.method eq "POST") and (http.cookie eq "")

Action: Managed challenge.

13. Block password reset abuse by header signature

Why: Botnets often send unusually short request bodies on password reset. Real reset requests have an email or username payload.

(http.request.uri.path eq "/api/password-reset") and (http.request.body.size lt 10)

Action: Block.

14. Block JSON content-type mismatch on auth APIs

Why: Your auth APIs accept application/json. Form-encoded requests to JSON endpoints are usually attack tooling.

(http.request.uri.path in {"/api/login" "/api/signup" "/api/password-reset"}) and (http.request.method eq "POST") and not (http.request.headers["content-type"][0] contains "application/json")

Action: Block.

Payment and Checkout Protection Rules

15. Block payment API requests with no session

Why: Real users reach payment through a session created earlier in the checkout flow. Session-less payment POSTs are carding bots.

(http.request.uri.path eq "/api/payment") and (http.request.method eq "POST") and not (http.cookie contains "session=")

Action: Block.

16. Allow-list payment provider webhook IPs

Why: Stripe, Checkout.com, HyperPay, Tap, Mada call your webhook endpoints back. Generic rate limits and bot rules can drop legitimate webhooks. Put this allow-list above all other payment-path rules.

(http.request.uri.path eq "/api/webhooks/payment") and (ip.src in {payment_provider_cidrs})

Action: Skip remaining rules. Audit the provider IP list quarterly.

17. Block coupon and gift-card brute force by content-length

Why: Coupon enumeration bots send fixed-size requests rapidly. Combined with rate limiting, a content-length filter catches the pattern.

(http.request.uri.path eq "/api/coupon/apply") and (http.request.method eq "POST") and (http.request.body.size lt 100)

Action: Managed challenge.

18. Block carding pattern: multiple BIN ranges from same IP

Why: Card testing rotates through BIN ranges from the same source. This rule needs Enterprise (raw bot score), but the principle works.

(http.request.uri.path eq "/api/payment") and (cf.bot_management.score lt 30)

Action: Block. Enterprise Bot Management required for cf.bot_management.score.

Admin and CMS Hardening Rules

19. Block XML-RPC and old WordPress paths

Why: Even non-WordPress sites get hit. Block the paths universally.

(http.request.uri.path contains "xmlrpc.php") or (http.request.uri.path contains "wp-login.php" and not (ip.src in {office_cidrs})) or (http.request.uri.path contains "/wp-content/uploads/")

Action: Block.

20. Block requests to common backup file extensions

Why: Reconnaissance scanning probes for leaked backups: .sql, .tar.gz, .zip, .bak, .env.

(http.request.uri.path matches "\\.(sql|tar\\.gz|zip|bak|env|git|svn)$")

Action: Block.

21. Block PHP file requests on a non-PHP site

Why: If your stack is not PHP, no one should ever request a .php file. These are all attacks.

(http.request.uri.path matches "\\.php($|\\?)")

Action: Block. Skip this rule if you actually run PHP.

22. Block sensitive path traversal patterns

Why: /../, /./, and encoded variants in the URL path are reconnaissance.

(http.request.uri.path contains "../") or (http.request.uri.path contains "/./" ) or (http.request.uri.path contains "%2e%2e%2f")

Action: Block.

23. Block HEAD requests to write endpoints

Why: Real clients do not send HEAD to write endpoints. Scanners do.

(http.request.method eq "HEAD") and (http.request.uri.path in {"/api/login" "/api/payment" "/api/booking"})

Action: Block.

API Protection Rules

24. Require API version header

Why: Real clients send your documented API version header. Requests without it are usually old integrations or scanners.

(http.request.uri.path matches "^/api/v2/") and (http.request.headers["x-api-version"][0] eq "")

Action: Block.

25. Require Authorization header on protected APIs

Why: Internal protected APIs need a token. Cloudflare can enforce the header presence at the edge before traffic reaches origin.

(http.request.uri.path matches "^/api/internal/") and not (http.request.headers["authorization"][0] matches "^Bearer ")

Action: Block.

26. Block oversized request bodies

Why: Cap payload size at the edge to defend against payload-flood attacks and resource exhaustion. Set the size to your real maximum (file uploads, batch requests).

(http.request.body.size gt 5242880) and not (http.request.uri.path eq "/api/upload")

Action: Block. 5MB cap with upload path exempt.

27. Block requests with deprecated TLS versions

Why: TLS 1.0 and 1.1 are deprecated. Modern browsers do not use them. Requests on old TLS are almost always tooling.

ssl and (cf.tls_version in {"TLSv1" "TLSv1.1"})

Action: Block.

What action should I use: block, challenge, or managed challenge?

The choice depends on the cost of a false positive:

  • Block for attacks with no legitimate version (path traversal, PHP probes on non-PHP sites, empty UA, deprecated TLS). Zero false positive risk.
  • Managed challenge for attacks that could occasionally have a legitimate caller (scrapers on pricing, signup from a flagged country). Lets real users solve a challenge.
  • JavaScript challenge rarely. Slower than managed challenge and gives a worse UX.
  • Log during the first one to two weeks for any new rule. Always.

The decision matrix: high confidence + high impact = block. Lower confidence or any chance of legitimate use = managed challenge. New rule = log first, regardless.

Common mistakes to avoid

Putting block rules above the verified-bot allow-list. You will lose search rankings within a week. Allow-list cf.client.bot first, always.

Forgetting to audit payment provider IP allow-lists. Providers change IP ranges. A quarterly audit prevents reconciliation failures.

Using contains when eq is correct. http.request.uri.path contains "/admin" matches /administration, /admin-tools, and /api/v1/admin-stuff. Use eq or anchored regex when you mean a specific path.

Writing one giant rule with OR conditions for many threats. Splitting into separate, named rules makes Security Events readable and lets you tune each one independently.

Skipping the log period. Two weeks of log mode before block. Always.

For the order in which to deploy these rules alongside managed rules and rate limits, see our Cloudflare WAF setup guide for booking and payment platforms. For the bot management layer that sits alongside these rules, our Super Bot Fight Mode tuning guide covers endpoint-specific scoring. For the rate limiting companion to most of these rules, see our Cloudflare Rate Limiting production patterns. For an end-to-end deployment using many of these recipes, the Saudi mobility platform case study shows them in production.


Need Help Deploying These Custom Rules in Production?

A custom-rule library on paper is one thing. Tuning the rules against your real traffic, ordering them correctly in the Cloudflare dashboard, log-mode testing each one, and handing the configuration to your team in a maintainable state is the actual work.

Tasrie IT Services provides comprehensive Cloudflare managed services to help you:

  • Deploy a custom-rule library tuned to your business logic, with each rule calibrated against your real traffic before blocking is enabled
  • Order rules correctly in the WAF rule chain so allow-lists, skip rules, and block rules interact the way you expect
  • Hand over a documented configuration so your team can add, refine, and triage rules without our involvement

We have shipped these rule patterns across booking, e-commerce, fintech, and government workloads in Saudi Arabia, the UAE, and the UK. For broader cybersecurity strategy, see our cybersecurity services.

Talk to our team about your Cloudflare WAF rule library

E

Engineering Team

Published on June 8, 2026

Ready to get started?

Concerned about security?

We help teams implement security best practices across their infrastructure and applications.

Get started
Chat with real humans
Chat on WhatsApp