Skip to content
← writing

Block scanners at the edge with one Cloudflare rule

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 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 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.

Cloudflare WAF expression · 18 paths
(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: Cloudflare dashboard → Security → WAF → Custom rules → Create, switch to Edit expression, paste, action Block, 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/        # → 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 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