---
title: "Block scanners at the edge with one Cloudflare rule"
description: "Bots hammer every site for /.env, /.git, /wp-login and a hundred other 'leaky paths'. Here's a single Cloudflare WAF custom rule that blocks them before they ever reach your origin — plus a generator and a Claude Code skill to apply it."
date: "2026-05-29"
tags: ["cloudflare", "security", "waf", "infrastructure"]
---

Stand up anything on the public internet and within minutes the scanners arrive.
They don't know or care what you're running — they just fire a fixed list of
"maybe someone left this lying around" requests at you: `/.env`, `/.git/config`,
`/wp-login.php`, `/phpmyadmin`, `/backup.sql`. Every one is a roll of the dice on
finding a secret you didn't mean to ship.

I saw [@vikingmute](https://x.com/vikingmute/status/2042057483863388456) share a
clean way to deal with this, and it's worth passing on: **block the whole class
of requests at Cloudflare's edge**, so they never touch your origin at all.

## Why the edge

You could handle this at the app — return 404s, add middleware — but that still
means the request reaches your server, burns a worker/process, and lands in your
logs. Blocking at Cloudflare means:

- **Zero origin load.** The request dies at the edge.
- **Clean logs.** Your real traffic isn't buried under scanner noise.
- **No exposure window.** Even if a secret file *is* sitting there, the fetch
  never completes.

## The rule

Cloudflare WAF custom rules take a boolean **expression**. Block the paths by
OR-ing a `contains` check for each:

```
(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 "/wp-login")
or (http.request.uri.path contains "/phpmyadmin")
or (http.request.uri.path contains "/xmlrpc")
```

`contains` matches anywhere in the path, so `/some/nested/.git/config` is caught
too. Set the action to **Block** — there is no legitimate reason for a browser
to request `/.env`, so you don't need a softer challenge.

Where do the paths come from? The excellent
[`ayoubfathi/leaky-paths`](https://github.com/ayoubfathi/leaky-paths) wordlist —
a deliberately lean, high-signal list of endpoints from modern stacks that tend
to leak. You don't want *every* CVE path (that's a losing game); you want the
handful scanners actually try first.

## Build your own

Toggle the groups that match your stack and copy the expression. **Skip the
WordPress / admin groups if you actually run those** — otherwise you'll block
your own `/wp-admin` login.

Then: Cloudflare dashboard → **Security → WAF → Custom rules → Create**, switch
to *Edit expression*, paste, action **Block**, deploy.

## Verify

```bash
curl -s -o /dev/null -w "%{http_code}\n" https://yoursite.com/.env   # → 403
curl -s -o /dev/null -w "%{http_code}\n" https://yoursite.com/        # → 200
```

## Takeaways

- Scanners hit a predictable set of **leaky paths** — block them as a class, not
  one 404 handler at a time.
- Do it at the **edge** (Cloudflare WAF custom rule) so requests never reach the
  origin: no load, no log noise, no exposure window.
- Keep the list **lean and high-signal**; tune the groups to your actual stack.

I packaged this up on the [Freebies](/freebies) page — an interactive generator,
plus a downloadable Claude Code skill that'll write and apply the rule for any
zone.
