Guushu Studio / Notes

Cloudflare free plan WAF rules I kept after 7 days of scanner logs

Cloudflare free plan WAF rules from one real zone: 3 custom rules and 1 rate limit, tuned on 7 days of scanner logs, plus the limit that blocked my images.

By · · updated

These are the Cloudflare free plan WAF rules running on my zone today: three custom rules and one rate limiting rule. I got there by reading a week of firewall events, and by undoing one change that blocked my own images.

The short version: path rules do most of the work. The rate limit is where you hurt yourself.

What 7 days of Cloudflare security events looked like

Between September 17 and 24 the zone saw about 34,000 requests. 55% of them hit a security action. The events sample I pulled had 5,208 rows from 920 IPs.

None of it was aimed at me. It was the same background noise every public hostname gets: .env files, .git/config, WordPress logins, a React RCE probe for CVE-2025-55182, empty user agents, Go-http-client.

The part that surprised me was cost. Blocked requests never reach the Worker. My busiest Worker peaked at 786 requests in a day, against a free allowance of 100,000.

To see your own, the GraphQL Analytics API has a firewall events dataset. On Free it's sampled and the hourly query budget is small, so don't loop it.

{
  viewer {
    zones(filter: { zoneTag: "<zone id>" }) {
      firewallEventsAdaptive(
        limit: 100
        filter: { datetime_geq: "2026-09-17T00:00:00Z" }
        orderBy: [datetime_DESC]
      ) { action source clientIP clientRequestPath userAgent clientASNDescription }
    }
  }
}

Is it an attack or background scanning?

Background scanning, in my case. Look at three things: the paths (secret files and CMS logins you don't have), the source network (cloud and VPS ranges, not home ISPs), and the timing (dozens of paths from one IP inside a second). If all three match, it's an automated scanner, and blocking it at the edge is enough.

One warning before you trust a report. The first analysis I got, written by an AI assistant from the same API data, listed /estimate as an unknown endpoint probe. That's my calculator page. It also counted /zh-Hant/ and /sitemap.xml. Check every "attack path" against your own routes.

Custom rules 1 and 2: block WordPress and secret-file paths

Free gives you 5 custom rules and no regex, so every rule is a chain of contains. I use two for paths.

The WordPress one. I don't run WordPress anywhere, so nothing legitimate matches:

(http.request.uri.path contains "/wp-admin") or (http.request.uri.path contains "/wp-includes")
or (http.request.uri.path contains "/wp-content") or (http.request.uri.path contains "/wp-json")
or (http.request.uri.path contains "/xmlrpc.php") or (http.request.uri.path contains "/wp-login")
or (http.request.uri.path contains "/wlwmanifest.xml") or (http.request.uri.path contains "/wordpress/")
or (starts_with(http.request.uri.path, "/wp/"))

The secret-file one is longer. The core of it:

(http.request.uri.path contains "/.env") or (http.request.uri.path contains "/.git/")
or (http.request.uri.path contains "/.aws/") or (http.request.uri.path contains "/.docker/")
or (http.request.uri.path contains "/credentials.yml") or (http.request.uri.path contains "/phpinfo")
or (http.request.uri.path contains "/info.php") or (http.request.uri.path contains "/dump.sql")
or (http.request.uri.path contains "/_profiler")

contains "/.env" already catches .env.production and /api/.env. You don't need a line per variant.

Why bother, if those files don't exist? A path rule blocks the first request. A rate limit only kicks in after the threshold, so a slow scanner walks under it forever.

Custom rule 3: block tool user agents, and pay for it

The third rule blocks two known IPs plus any user agent containing curl, Go-http-client, python, scrapy or fasthttp.

It works. It also bit me. My own ops notes said Python and Go clients were allowed. The live rule blocked both, and a bare curl https://guushu.com/ returns 403 today. When I check my own site now, I send a browser user agent.

Before copying this rule, list what calls your zone: webhooks, uptime checks, your own scripts. If any of them use a default library user agent, they're gone. This is the rule most likely to cause a quiet outage.

The one free rate limiting rule, and how it 429'd my own images

Free gets one rate limiting rule, counted per IP, with a fixed 10 second window and a fixed 10 second block.

The AI report suggested dropping mine from 20 to 10 requests per 10 seconds. I did. Three days later an image-heavy page on another subdomain was returning 429 on its own first screen. AI search crawlers were getting rate limited too: GPTBot, ClaudeBot, PerplexityBot.

The fix was to stop counting things that aren't worth counting:

((http.host eq "guushu.com") or ends_with(http.host, ".guushu.com"))
and not (http.request.uri.path.extension in {"js" "css" "png" "jpg" "jpeg" "gif" "webp" "avif" "svg" "ico" "woff" "woff2" "ttf" "json" "map" "txt" "xml" "webmanifest"})
and not cf.client.bot

Back at 20 requests. The test: 40 concurrent HTML requests from one IP gave 20 × 200 and 20 × 429. 40 concurrent static requests gave 40 × 200. [cf.client.bot](https://developers.cloudflare.com/ruleset-engine/rules-language/fields/reference/cf.client.bot/) is true only for Cloudflare's verified bots.

Does a 429 on Googlebot mean you're blocking Google?

Not necessarily. My logs had 107 rate limit events with a Googlebot user agent. Every one came from Google Cloud customer ranges, not the crawler. Google publishes its crawler IP ranges; check the source IP before you loosen anything. Real Googlebot and Bingbot had zero blocks.

Zone settings I changed, and ones I left off

On the settings side I moved minimum TLS to 1.2, turned on HSTS, and left Bot Fight Mode, AI Labyrinth and the Free managed ruleset on. The full list with a curl for each is in my Cloudflare zone security checklist.

One caution on HSTS. My header now says includeSubDomains; preload. Preload is hard to take back, so be sure every subdomain serves HTTPS before you add it.

I left three things off. Under Attack Mode puts a challenge in front of every visitor, which is a lot of friction for background noise. mTLS wants a client certificate that none of my visitors have. Hotlink protection solves a problem these scans don't have.

A week later the numbers I'll look at are simple: whether rate limit events from home ISPs stay near zero (before the fix, about 3 of 69 blocked IPs were residential), and whether my own pages still load in one go.

If you run more than one zone and want this kind of drift flagged daily, there's a one-question form, not a product yet: guard.guushu.com/zone-audit.