Block scanners at the edge with one Cloudflare rule
Stand up anything on the public internet and within minutes the scanners show
up. 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.
@vikingmute shared a clean way to deal with it, 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 in the app with 404s or middleware, but the request still reaches your server, burns a process, and lands in your logs. Block it at Cloudflare and three things change. The request dies at the edge, so there's zero origin load. Your logs stay clean instead of drowning in scanner noise. And there's no exposure window: even if a secret file really 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 gets
caught too. Set the action to Block. No browser has a legitimate reason to
request /.env, so you don't need a softer challenge.
Where do the paths come from? The ayoubfathi/leaky-paths
wordlist, a deliberately lean, high-signal list of endpoints from modern stacks
that tend to leak. Don't try to block 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 and admin groups if you actually run those, or you'll block your own
/wp-admin login.
(http.request.uri.path contains "/.env")
or (http.request.uri.path contains "/.git")
or (http.request.uri.path contains "/.svn")
or (http.request.uri.path contains "/.aws")
or (http.request.uri.path contains "/.ssh")
or (http.request.uri.path contains "/.config")
or (http.request.uri.path contains "/.DS_Store")
or (http.request.uri.path contains "/.htaccess")
or (http.request.uri.path contains "/.htpasswd")
or (http.request.uri.path contains "/wp-login")
or (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 "/xmlrpc")
or (http.request.uri.path contains "/phpmyadmin")
or (http.request.uri.path contains "/adminer")
or (http.request.uri.path contains "/server-status")
or (http.request.uri.path contains "/server-info")Cloudflare dashboard → Security → WAF → Custom rules → Create → paste as the expression → action Block.
Then head to the Cloudflare dashboard, go to Security → WAF → Custom rules → Create, switch to Edit expression, paste it in, set the action to Block, and deploy.
Verify
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/ # → 200The mental model that stuck with me: scanners hit a predictable set of leaky paths, so block them as a class at the edge, not one 404 handler at a time. Keep the list lean and tuned to what you actually run.
I packaged this up on the Freebies page: an interactive generator, plus a downloadable Claude Code skill that'll write and apply the rule for any zone.
Ask your agent to implement this
Read the full writeup at https://seangeng.com/writing/block-scanners-at-the-edge.md and implement it in my project.
It covers: Block scanners at the edge with one Cloudflare rule. 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.
Requirements:
- Follow the technique/approach exactly as described in the writeup.
- Adapt names, colors, and styling to my project's existing conventions.
- If it's a component, make it reusable with sensible props and TypeScript types.
- Keep it accessible: semantic HTML, keyboard support, and respect prefers-reduced-motion.
- When done, tell me which files you created or changed and how to use it.Paste into Claude Code, Codex, Cursor, or any agent. view raw .md