T Techclick ← All lessons
Cloudflare · WAF · Interactive lesson

Cloudflare WAF proxy first then rules

Ticket: Managed Rules are on, origin 192.0.2.10 is still eating SQLi on api.example.com. The WAF never saw the packet. Proxy status on that A record is DNS-only — grey cloud. Cloudflare WAF is not a zone-wide toggle. It inspects HTTP that terminated at the edge on a proxied record. Lab: origin 192.0.2.10, attacker 203.0.113.45, path /login.

16 min read · L2 primary · Quiz at end

After this page you can

Lessons · WAF series · Cloudflare WAF

This page vs an on-prem WAF

F5 ASM hangs on a Standard virtual server. Cloudflare WAF hangs on a proxied DNS record. Same idea: no attach, no inspection. Policy building on BIG-IP is a different lesson; here the attach is the orange cloud.

F5 ASM architecture · WAF interview

Hero · browser hits the edge, origin stays hidden
Laptop HTTPS request terminating at stacked DNS, TLS and WAF layers before a hidden origin
Mood, not a wiring diagram. Exact path is in the SVG: browser → proxied DNS (anycast edge IP) → TLS + WAF at the PoP → separate fetch to 192.0.2.10. Direct to the origin IP bypasses every rule.
Quick answer

Cloudflare WAF inspects HTTP(S) only after a request hits a proxied (orange-cloud) A / AAAA / CNAME record. DNS-only (grey cloud) answers with the origin IP — WAF, rate limit, and L7 DDoS are off for that hostname. On the edge, security phases run in order: HTTP DDoS (ddos_l7), Custom rules (http_request_firewall_custom), Rate limiting (http_ratelimit), Managed Rules (http_request_firewall_managed), Super Bot Fight Mode (http_request_sbfm). Account-scope runs before zone-scope in the same phase. A Block / Managed Challenge stops later phases. Prove a hit in Security Events (Analytics → Events): Service, Action, Rule ID, Ray ID — not in a quiet origin firewall log.

Why the WAF is silent

The day-one ticket is always the same: “WAF is enabled on the zone, so the app is protected.” Wrong. Enabling Managed Rules on the zone does nothing for a hostname whose DNS record is DNS-only. The browser never reached the edge. It opened TCP to 192.0.2.10.

Three silent-zero states look identical in Security Events (empty for that hostname):

Grey-cloud is not “WAF in log mode”

DNS-only is not Transparent. It is bypass. dig +short api.example.com returning 192.0.2.10 is the proof. Anyone who has that IP skips every custom rule, managed rule, and L7 DDoS policy on the zone. Internal APIs that truly need a direct path use Cloudflare Tunnel or Authenticated Origin Pulls — they do not grey-cloud a public A record.

Orange cloud is the attach

Cloudflare is a reverse proxy on anycast. When Proxy status is Proxied, the A/AAAA answer is a Cloudflare anycast address. The client TCP (and TLS) terminates at the nearest PoP. WAF, Bot, cache, and SSL run there. The edge then opens its own connection to 192.0.2.10 and sets CF-Connecting-IP to the original client. When Proxy status is DNS-only, DNS returns 192.0.2.10 and none of that happens.

Only A, AAAA, and CNAME used for HTTP/HTTPS can be proxied. MX and TXT are always DNS-only. Mail, SSH, and FTP records that you orange-cloud will break — that is a DNS mistake, not a WAF bug.

Journey · proxy first, then the rule list matters
Decision diamond Proxy? splitting traffic to a shielded origin versus an exposed origin
Feel of the fork. Path A is Proxied: edge IPs, WAF on. Path B is DNS-only: origin IP on the wire. Exact labels are in the SVG below — the artwork is not the product UI.
Flow 1 · one DNS toggle decides if WAF runs
DNS Records → Proxy status. Proxied = WAF. DNS-only = origin IP. PROXIED (orange cloud) · www.example.com A → anycast Browser TCP/443 DNS answer edge anycast IP not 192.0.2.10 Edge PoP — TLS + WAF custom → ratelimit → managed → SBFM CF-Connecting-IP on the origin fetch Origin 192.0.2.10 (hidden) DNS-ONLY (grey cloud) · api.example.com A → 192.0.2.10 Browser or attacker DNS answer 192.0.2.10 no edge, no WAF, no L7 DDoS — origin IP is public Origin direct 192.0.2.10:443

Read left → right. The gold band is one proxied hostname. Managed Rules on the zone do not inspect a grey-cloud sibling. Source: Cloudflare DNS — Proxy status.

Say this out loud

WAF runs only on proxied records. Grey cloud returns the origin IP. Non-HTTP must stay DNS-only. A rule that is never reached is not a WAF failure — it is a DNS miss.

Proxied

A / AAAA / CNAME for web. Dashboard: DNS → Records → Proxy status Proxied. Edge IP in dig. WAF can see HTTP.

DNS-only

Grey cloud. Origin IP is the answer. Use for MX-related A records, SSH, SMTP, services the HTTP proxy cannot terminate.

Full (strict)

SSL/TLS encryption mode. Browser↔edge and edge↔origin both TLS, origin cert validated. Flexible is HTTP to origin — do not use it in production.

Hide the origin

Proxy the public hostnames. Filter 192.0.2.10 so only Cloudflare IP ranges (or Tunnel / Authenticated Origin Pulls) can reach it. Otherwise the orange cloud is optional in the attacker’s path.

Phases — custom before managed

This is the whiteboard question: walk a request through the WAF. L3/L4 DDoS runs on packets before HTTP is parsed. Then the Ruleset Engine security phases run in a fixed order. Account-level rulesets in a phase run before zone-level rulesets in that same phase. A terminating action (Block, Managed Challenge) stops evaluation — later phases never see the request.

Flow 2 · security phases on a proxied request
L3/L4 DDoS packet, before HTTP ddos_l7 HTTP DDoS Protection custom http_request_firewall_custom account then zone http_ratelimit Rate limiting rules Block / Challenge pipeline stops here managed http_request_firewall_managed OWASP + CF Managed http_request_sbfm Super Bot Fight Mode Pro / Business / Ent Cache / Workers then origin 192.0.2.10 survived every phase Gotcha: a Custom rule Block on 203.0.113.45 kills the request before Managed Rules Security Events then shows the custom Block — not the SQLi rule ID. Use Log on the IP rule if you still want managed attribution. Skip in custom can bypass later phases (rate limit, managed, SBFM). Bot Fight Mode (Free) is not Ruleset Engine — custom Skip cannot turn it off. Account-scope Block in a phase runs before a zone-scope Skip in that same phase. The zone skip never runs. URL Rewrite runs before WAF custom. A custom rule matching the original path may miss after rewrite.

Source: Cloudflare WAF — Security features interoperability and WAF phases. Custom is http_request_firewall_custom. Managed is http_request_firewall_managed.

PhaseProductWhere you configure it
ddos_l7HTTP DDoS Attack ProtectionAlways on. Overrides live under DDoS protection — not a Custom rule.
http_request_firewall_customCustom rules / account custom rulesetsSecurity → WAF / Security rules → Create rule → Custom rules. Account then zone.
http_ratelimitRate limiting rulesSecurity rules → Rate limiting rules. Characteristic often ip.src.
http_request_firewall_managedCloudflare Managed Ruleset, OWASP Core Ruleset, Sensitive Data DetectionSecurity → WAF / Security Settings: turn on the managed ruleset. Exceptions skip rules in this phase.
http_request_sbfmSuper Bot Fight ModeBot product on Pro+. Skip from a custom rule can bypass SBFM. Bot Fight Mode (Free) cannot be skipped.
The #1 phase-order trap

A Custom rule that Blocks 203.0.113.45 “because of SQLi” means Managed Rules never run on that IP. Security Events will not carry the SQLi rule ID. Prefer action Log (Enterprise) on the IP rule if you still want managed attribution — or accept the trade-off for a permanent ban. Source: WAF phases — terminating actions stop later phases.

OWASP map and rule actions

Interviewers ask which Cloudflare control maps to which OWASP bucket. Injection and known CVEs are Managed Rules. Auth brute is rate limit plus Exposed Credentials Check. Broken access control and insecure design are not a signature problem.

OWASP (2021)Cloudflare controlWhat it actually matches
A01 Broken Access ControlCustom rules; API Shield on EnterprisePath / method allow-lists. IDOR on a valid session is still an app check.
A02 Cryptographic FailuresSSL/TLS Full (strict), TLS 1.3, HSTSNot a WAF signature. Flexible is HTTP to origin.
A03 InjectionOWASP Core Ruleset + Cloudflare Managed Ruleset; Attack Score cf.waf.score (Business+)?id=1' OR '1'='1, XSS, command injection patterns.
A04 Insecure Design— not a WAF jobBusiness logic. Say this out loud in the interview.
A05 Security MisconfigurationManaged Ruleset (admin-panel signatures)GET /server-status, leftover consoles.
A06 Vulnerable ComponentsManaged Ruleset per-CVE virtual patchLog4Shell-style ${jndi: in headers — origin never reached.
A07 Auth FailuresRate limiting on /login; Exposed Credentials CheckBrute POST. Stolen cookie still looks legitimate.
A08 Integrity FailuresManaged Ruleset deserialisation signaturesPattern match, not supply-chain attestation.
A09 Logging failuresSecurity Events; Logpush dataset http_requestsInfra. WAF does not replace SIEM retention.
A10 SSRFManaged Ruleset SSRF signatures + custom on metadata IPs?url=169.254.169.254. Hex/encoding bypasses still exist.
Actions you actually pick

Block · Managed Challenge · JS Challenge · Challenge · Skip (selected later phases / rulesets) · Log (Enterprise custom rules). When a managed rule is noisy, start with a scoped exception or a Log period — do not turn the whole OWASP Core Ruleset off. Log is not available on Free/Pro/Business custom rules (official availability table).

Rules language — expressions from the old cheat-sheet, field names unchanged
(http.request.uri.path eq "/wp-login.php" and http.request.method eq "POST" and ip.src.country ne "IN")
(ip.src.country in {"KP" "IR" "SY"})
(cf.threat_score gt 14)
(http.request.uri.path eq "/login" and http.request.method eq "POST")
# rate-limit: characteristic ip.src · period 60s · requests 5 · action Block · duration 300s
(http.host eq "api.example.com" and http.request.uri.path eq "/stripe/webhook" and ip.src in {192.0.2.80})
# Skip → Super Bot Fight Mode / Managed Rulesets — payment callbacks

Operators: eq ne in contains matches. Fields: http.request.uri.path, http.request.method, http.host, ip.src, ip.src.country (ISO 3166 alpha-2), http.user_agent, cf.threat_score, cf.client.bot. Enterprise Bot Management adds cf.bot_management.score (1–99). C-style == is not the language.

Plan fact (official custom-rule table)FreeProBusinessEnterprise
Custom rules (zone, http_request_firewall_custom)5201001,000
Log action on custom rulesNoNoNoYes
Regex in expressionsNoNoYesYes
Account-level custom rulesetsNoNoNoYes
BotBot Fight ModeSuper Bot Fight ModeBot Management add-on for cf.bot_management.score

Runbook Side A / B / C

Lab: origin 192.0.2.10, public host www.example.com Proxied, do not grey-cloud it “to test origin.” Encryption mode Full (strict). Dashboard names below are Security → WAF / Security rules / Security Events as they appear in current docs.

Side A — DNS / client / bypass

  1. Proxy the web records

    DNS → Records. A / AAAA / CNAME for www and the apex: Proxy status Proxied. Leave MX, SPF/DKIM TXT, and mail / SSH hosts DNS-only. Source: Cloudflare DNS — Proxy status.

  2. Prove the answer is not the origin

    dig +short www.example.com A must not return 192.0.2.10. If it does, the WAF is not in path. Then filter origin so only Cloudflare (or Tunnel) can connect.

  3. Encryption mode Full (strict)

    SSL/TLS → Full (strict). Origin presents a valid certificate the edge will verify. Flexible is HTTP to 192.0.2.10.

Side B — product (managed, custom, rate limit)

  1. Turn on managed rulesets

    Security → WAF / Security Settings (filter Web application exploits). Enable Cloudflare managed ruleset and OWASP Core. Optional: Sensitive data detection. Source: Deploy a WAF managed ruleset in the dashboard.

  2. One Custom rule

    Security rules → Create rule → Custom rules. Rule name block_high_threat. When incoming requests match: (cf.threat_score gt 14). Then take action: Managed Challenge (or Block). Deploy. Keep IP bans here; do not use this phase to “also catch SQLi” if you still want managed rule IDs in Security Events.

  3. Rate limit /login

    Create rule → Rate limiting rules. Expression (http.request.uri.path eq "/login" and http.request.method eq "POST"). Characteristic ip.src. Period 60 seconds, 5 requests, action Block, duration 300s. Behind corporate NAT, ip.src alone punishes a whole office — add a cookie/session characteristic if you have one.

  4. Do not skip managed for convenience

    Skip is for known-good callbacks (payment webhooks with a tight ip.src list). Skip-all-managed on / is turning the WAF off with extra steps.

https://dash.cloudflare.com/ · example.com · Security › Security rules › Create rule › Custom rules
Training mock · not live

Security › Security rules › Create rule › Custom rules

Create custom rule

block_high_threat
(cf.threat_score gt 14)
Managed Challenge
Last (evaluated in list order)

Source: Cloudflare WAF — Create a custom rule in the dashboard. Fields: Rule name, When incoming requests match, Then take action, Deploy / Save as Draft. New dashboard path is Security rules; older docs still say Security → WAF. Same phase: http_request_firewall_custom.

Side C — proof (Security Events / Logpush)

  1. Synthetic request

    From a test client that resolves through the proxied name: curl -sS -D- "https://www.example.com/product?id=1'+OR+'1'='1". Expect 403 (or challenge) if managed SQLi is in Block. If you get the app 200, first re-check Proxy status — then whether the managed rule action is actually Block.

  2. Security Events

    Dashboard: Analytics → Events (Security Events). Filter the hostname and last 24 hours. You want a row: Service = Managed rules (or Custom rules / Rate limiting rules), Action = Block (or Managed Challenge), Rule ID, Ray ID, Client IP. Source: Cloudflare WAF — Security Events.

  3. False-positive ladder

    Marketing says /api/upload multipart is blocked. Open the event, read the payload, take the rule ID (OWASP often 949110 / related anomaly rules). If it is a real attack, keep the block. If it is a false positive: Security rules → Create → Managed rules exception. When incoming requests match: path /api/upload. Then skip those rule IDs only. Re-test. Do not disable OWASP Core for the zone.

  4. Logpush (optional SIEM)

    Logpush job, dataset http_requests, fields at least RayID, ClientIP, ClientCountry, WAFAction, WAFRuleID. Destination is your SIEM or R2. Plan availability is account-specific — confirm on the Logpush page rather than assuming Free includes it.

https://dash.cloudflare.com/ · example.com · Analytics › Events
Training mock · not live

Analytics › Events · Last 24 hours · www.example.com

Security Events

Managed rules
Block
942100
0000000000000000
203.0.113.45
/product?id=1' OR '1'='1

Source: Cloudflare WAF — Security Events. Official path: Analytics page → Events tab. Service tells you which phase acted. Empty Events on a grey-cloud hostname is expected — there was no edge request to log.

Logpush — http_requests with WAF fields (API shape from the previous lesson)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/logpush/jobs" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "destination_conf": "r2://lab-bucket/cf-waf?account-id=$ACCT",
    "dataset": "http_requests",
    "logpull_options": "fields=RayID,ClientIP,ClientCountry,WAFAction,WAFRuleID×tamps=rfc3339"
  }'
Green success

One request after go-live

After the record is Proxied and rules are Deployed, GET /product?id=1' OR '1'='1 from 203.0.113.45 does this:

  1. Resolver returns an anycast edge IP. Client TLS to the PoP. Origin IP stays unpublished.
  2. L3/L4 DDoS, then ddos_l7. A single SQLi GET is not a flood — it continues.
  3. http_request_firewall_custom — if cf.threat_score is low and no IP rule matches, continue. If a custom Block matches, stop; Managed never runs.
  4. http_ratelimit/product is not the login rule; continue.
  5. http_request_firewall_managed — OWASP / Cloudflare Managed SQLi rule Blocks. 403 to the client. Security Events writes Service, Rule ID, Ray ID. Origin is not fetched.
  6. SBFM, cache, Workers, origin — skipped because the action terminated the pipeline.
Flow 3 · false-positive ladder (do not disable the ruleset)
Security Events Rule ID + payload Real attack? read the body Exception path + rule ID only Keep Block tag IP / ASN if repeat Re-test Events upload 200 · SQLi still Block Do not Disable OWASP Core Global paranoia drop Permanent Log on CRS Grey-cloud the API

WAF exceptions are skip rules in http_request_firewall_managed. Scope them. Source: Add a WAF exception in the dashboard.

Traps + proof

Proof · a quiet origin log is success if Events has the Block
Operator at a SOC desk reviewing a security-events console with one blocked row highlighted
Artwork. Real proof is Analytics → Events: Service, Action, Rule ID, Ray ID. Origin application logs looking “too quiet” after a proxied go-live is the point — blocked requests never left the PoP.
SymptomLikely causeProof / fix
Managed Rules on; origin still sees SQLiHostname is DNS-only, or clients use the origin IP.dig +short that hostname. Proxy it. Filter 192.0.2.10.
Empty Security Events for that hostNo edge request — grey cloud, wrong zone, or HTTP never reached Cloudflare.Analytics → Events filtered on hostname. Empty + origin IP in DNS = bypass, not a stale ruleset.
Custom IP Block, no SQLi rule IDCustom phase terminated before managed.Expected. Log the IP rule (Enterprise) or live with lost attribution.
Zone Skip for CN, still blockedAccount-scope Block in the same phase runs first.Put the exception at account scope, or move the geo-block to the zone.
Multipart /api/upload 403OWASP anomaly / size rules on a legitimate body.Exception: path + those rule IDs. Not “disable OWASP Core.”
SSH / SMTP died after orange-cloudHTTP proxy cannot terminate those protocols.DNS-only on that record. Non-HTTP is not a WAF miss — it is out of scope.
Office NAT rate-limited as one userRate limit characteristic = ip.src only.Add session cookie or split the login hostname. Do not raise the whole-zone threshold to 10,000.
Custom rule matches old path, not rewritten pathURL Rewrite runs before WAF custom.Trace the request. Match the rewritten URI, or reorder the rewrite.
Bot Fight Mode still challenges after SkipBot Fight Mode is not Ruleset Engine.Skip cannot disable it. SBFM / Bot Management, or turn Bot Fight Mode off under Security Settings.
What this WAF does not protect

Origin IP exposed — grey-cloud, old A record in an email, mobile app pin, or origin that still accepts the world. Non-HTTP — SSH, SMTP, RDP, anything the reverse proxy does not parse. Mis-proxied DNS — the public name orange, the name clients actually use still grey. On top of that, pattern WAFs miss business logic, IDOR on a valid session, stolen cookies, and race conditions. Those need app auth and code review. Cloudflare WAF is one layer.

Pilot checklist

Knowledge check

Six judgment calls on proxy, phases, and tuning — not menu trivia.

Q1

Someone sets api.example.com to DNS-only “so the internal API can hit origin directly.” What is actually true after that change?

Correct: a. DNS-only is bypass, not log mode. Use Tunnel or Authenticated Origin Pulls if you need a private path. Re-read Orange cloud is the attach.
Q2

A Custom rule Blocks 203.0.113.45. That IP then sends SQLi. Managed Ruleset is on. What lands in Security Events?

Correct: d. Custom before managed. Terminating actions stop later phases. Re-read Phases.
Q3

Block POST to /wp-login.php from outside India. Which expression is the rules language?

Correct: b. Operators are eq / ne. http.host is the Host header, not the path. Country is ISO 3166 alpha-2. Re-read OWASP map and rule actions.
Q4

OWASP Core is blocking legitimate multipart uploads to /api/upload. Right tuning move?

Correct: c. Exceptions are surgical skip rules in the managed phase. Re-read Side C and Flow 3.
Q5

Account-scope custom rule Blocks country CN. Zone-scope rule on api.example.com Skips CN for /api/partner. A CN client hits that path. Result?

Correct: b. Account then zone inside a phase. Put the exception at account scope, or move the geo-block to the zone. Re-read Phases.
Q6

What does Cloudflare WAF not protect, even with Managed Rules in Block on a proxied zone?

Correct: c. Injection is exactly what managed rules target. HTTP DDoS is ddos_l7. Bot Fight / SBFM sit in the same story. The senior answer is attach misses (origin IP, non-HTTP, grey cloud) plus logic the pattern engine cannot see. Re-read Traps.

Sources

Related: F5 ASM architecture · F5 WAF interview