Cloudflare WAF & Security
Rule families (evaluation order)
- IP Access Rules (Security → WAF → Tools): blanket allow/block by IP/ASN/country.
- Custom Rules: your business logic, written in CF's expression language.
- Rate Limiting Rules: throttle by counter (IP, header, cookie).
- Managed Rules: CF-maintained signatures (OWASP, Cloudflare Managed Ruleset, exposed-credential check).
- Bot Management: ML-driven bot score (1=bot, 99=human).
Custom Rule examples
# Block path traversal attempts to /admin
Expression: (http.request.uri.path contains "/admin" and http.request.uri.query contains "../")
Action: Block
# Challenge requests with no User-Agent
Expression: (http.user_agent eq "")
Action: Managed Challenge
# Allow office IPs to bypass all WAF
Expression: (ip.src in {203.0.113.0/24 198.51.100.42})
Action: Skip → all remaining custom rules + managed rulesets
# Geo-block but allow CDN origins
Expression: (ip.geoip.country in {"CN" "RU" "KP"} and not cf.client.bot)
Action: Block
Fields cheat-sheet: http.request.method, http.host, http.request.uri.path, http.request.uri.query, http.user_agent, http.request.headers["x-foo"], ip.src, ip.geoip.country, ip.geoip.asnum, ssl, cf.threat_score, cf.bot_management.score, cf.bot_management.verified_bot.
Rate Limiting
Expression: (http.request.uri.path eq "/api/login")
Counting: characteristic = IP, period = 60s
Threshold: 5 requests
Action: Block for 600s
# Optional: response code 429 with custom JSON body
For logged-in throttling, count by http.request.headers["authorization"] or a session cookie instead of IP.
Managed Rules
Enable the Cloudflare Managed Ruleset + OWASP Core Ruleset. Start in Log action sitewide for 24–48h, then triage false positives via the Security Events log → exclude specific rules per path:
When: (http.request.uri.path eq "/webhook")
Action: Skip → managed rule id 100001 (or set sensitivity = Low for that path)
Bot Management
- Free tier: "Bot Fight Mode" (blunt; can break legitimate API clients).
- Super Bot Fight Mode (Pro): tiered actions for definitely-automated vs likely-automated.
- Bot Management (Enterprise): per-request ML score, JS detections, mobile SDK.
Expression: (cf.bot_management.score < 30 and not cf.bot_management.verified_bot)
Action: Managed Challenge
verified_bot = Googlebot, Bingbot, etc. — never block these unless intentional.
Turnstile (CAPTCHA replacement)
Free, privacy-friendly. Drop-in for forms:
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form method="POST">
<div class="cf-turnstile" data-sitekey="0x4AAA…"></div>
<button>Submit</button>
</form>
Server-side verify:
const form = await req.formData();
const token = form.get('cf-turnstile-response');
const r = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
body: new URLSearchParams({ secret: env.TURNSTILE_SECRET, response: token, remoteip: req.headers.get('cf-connecting-ip') ?? '' }),
});
const { success } = await r.json();
if (!success) return new Response('CAPTCHA failed', { status: 403 });
Incident response playbook
- Identify the attack pattern via Security → Events (filter by action=Block/Challenge, sort by Top N).
- Mitigate immediately: "Under Attack Mode" (Security → Settings) → JS challenge on every request. Brutal but buys time.
- Narrow the rule: write a Custom Rule targeting the exact pattern (URI, ASN, UA fingerprint).
- Disable Under Attack Mode once the targeted rule is catching the traffic.
- Postmortem: export logs (Logpush → R2/S3), refine managed-ruleset exclusions.
Headers reference
Every proxied request to your origin includes:
CF-Connecting-IP: real client IP — trust this, notX-Forwarded-For.CF-IPCountry: ISO-3166-1 alpha-2.CF-Ray: unique request ID; quote this in support tickets.CF-Worker: present if a Worker handled it.