You’re Already Paying for This — Stop Ignoring It
You set up Cloudflare, pointed your DNS at it, and called it a day. Orange cloud icon? Enabled. Traffic proxied? Sure. Security configured? …default settings. Sound familiar?
Here’s the thing: Cloudflare’s free tier ships with a genuinely useful WAF, rate limiting, bot protection, and always-on DDoS mitigation. Most homelab owners and small site operators leave all of it at factory defaults and wonder why wp-login.php is getting hammered at 3 AM.
Let’s fix that.
What the Free Tier Actually Gives You
Before you dive in, know what you’re working with:
- 5 custom WAF firewall rules — write your own match expressions
- Bot Fight Mode — automated bot detection and blocking
- Rate limiting — requests-per-period rules (1 rule free, more on paid)
- DDoS protection — always-on, no configuration needed, no rule quota used
- Security levels — global sensitivity tuning
- Page Shield — basic JS supply chain monitoring
What you don’t get free: Cloudflare’s managed rulesets (OWASP, WordPress, etc.), ML-based anomaly detection, custom IP lists beyond 10 entries, and advanced rate limiting with multiple conditions. Paid tiers start at $20/month if you ever need those.
For a homelab blog, self-hosted app, or small business site? Five well-written rules and rate limiting will cover 90% of the garbage you’ll see in the wild.
Custom Firewall Rules: The Expression Builder
Head to Security → WAF → Custom rules in your Cloudflare dashboard. Each rule has:
- A match expression — what traffic to target
- An action — what to do when it matches
Actions available on free tier:
| Action | What It Does |
|---|---|
| Block | Hard 403. Done. |
| JS Challenge | Serves a JavaScript puzzle. Bots usually fail. |
| Managed Challenge | Cloudflare decides: invisible CAPTCHA, JS challenge, or pass. Smart default. |
| Challenge | Old-school CAPTCHA. Avoid unless you hate your users. |
| Skip | Bypass other rules. Useful for allowlisting. |
| Log | Record only. Good for testing before blocking. |
Managed Challenge is usually the right call for suspicious-but-not-certain traffic. It’s invisible to real humans and annoying to bots.
Practical Rules Worth Writing
Block WordPress Attack Probes
If you’re not running WordPress, there’s zero reason to let scanners probe these endpoints.
(http.request.uri.path contains "/wp-login.php") or(http.request.uri.path contains "/xmlrpc.php") or(http.request.uri.path contains "/wp-admin") and(not ip.src in {YOUR_HOME_IP/32 YOUR_VPN_IP/32})Action: Block
If you are running WordPress, swap Block for Managed Challenge on wp-login.php and keep your own IPs allowlisted via the Skip action with higher priority.
Block Known Bad User Agents
Scanners, vulnerability tools, and lazy scrapers announce themselves in the User-Agent header. Take them up on the offer.
(http.user_agent contains "sqlmap") or(http.user_agent contains "nikto") or(http.user_agent contains "nmap") or(http.user_agent contains "masscan") or(http.user_agent contains "zgrab") or(http.user_agent eq "")Action: Block
The empty user agent catch (http.user_agent eq "") is a nice bonus — real browsers always send one.
Geo-Restrict Admin Areas
If your admin panel only needs to be accessible from your country (or a short list of countries), enforce it.
(http.request.uri.path contains "/admin") and(not ip.geoip.country in {"US" "CA"})Action: Block or Managed Challenge depending on your paranoia level.
Challenge High-Threat Countries for the Whole Site
You can dial up friction for countries you’re not targeting without fully blocking them.
(ip.geoip.country in {"CN" "RU" "KP" "IR"}) and(not cf.client.bot)Action: Managed Challenge
The not cf.client.bot condition exempts Cloudflare’s own verified bots (Googlebot, etc.) from the challenge. Without it, you’d break crawling.
Bot Fight Mode: Good, With Caveats
Enable it: Security → Bots → Bot Fight Mode → On
Bot Fight Mode uses Cloudflare’s fingerprinting to identify and block automated traffic. It’s effective against dumb scanners and credential stuffing attacks.
The catch: it catches legitimate bots too. Specifically:
- Uptime monitors (UptimeRobot, Better Uptime, etc.) may get challenged
- API clients that don’t run JavaScript will fail JS challenges
- Custom scrapers you actually want (your own monitoring scripts)
If you’re getting false positives, check Security → Events to see what’s being challenged and whether it matters. You can write a Skip rule for specific IPs (your uptime monitor’s IPs) with higher priority than Bot Fight Mode.
Rate Limiting: Protect Login Endpoints and APIs
Free tier gives you one rate limiting rule. Use it on your most-abused endpoint.
Security → WAF → Rate limiting rules → Create rule
A good default for a login endpoint:
http.request.uri.path eq "/login"- Requests: 5
- Period: 10 seconds
- Action: Block for 1 hour
That’s 5 attempts per 10 seconds before a 1-hour block. Aggressive enough to stop brute force, permissive enough that a human who mistyped their password twice won’t get locked out for an hour.
For an API endpoint you want to throttle:
- Requests: 60
- Period: 60 seconds
- Action: Managed Challenge
That’s 1 req/sec sustained — reasonable for most API consumers, limiting for scrapers.
Security Levels: Global Sensitivity
Security → Settings → Security Level
This is a global dial that controls how aggressive Cloudflare is with visitors who have bad IP reputation scores. Options:
- Essentially Off — only block the worst actors
- Low — challenge high-risk IPs
- Medium (default) — challenge medium-to-high risk IPs
- High — challenge most visitors with questionable history
- I’m Under Attack — JavaScript challenge for everyone, including real users
Default is Medium. For a homelab or personal site with no business need to be maximally accessible: High is fine. You’ll challenge more legitimate users (your logs will show it), but the noise drop is real.
Don’t use “I’m Under Attack” as a permanent setting. It breaks RSS readers, API clients, and anything that doesn’t run JavaScript.
Page Shield: Basic JS Supply Chain Protection
Security → Page Shield
Page Shield monitors the third-party scripts loading on your pages. On the free tier, it logs what scripts are detected and alerts on new ones. It won’t block them automatically (that’s paid), but it’s a useful early warning for supply chain attacks.
Enable it and let it run for a week. Then audit the list. If you see something unexpected — a CDN you don’t recognize, a script path you didn’t add — investigate.
Cloudflare Tunnels + WAF: The Full Picture
If you’re running Cloudflare Tunnels (cloudflared) to expose services without opening ports, the WAF still applies. Traffic hits Cloudflare’s edge first, goes through your firewall rules and Bot Fight Mode, and only reaches your tunnel if it passes.
Your origin is never exposed. The WAF rules you write above protect the tunnel endpoint just as they’d protect any proxied domain. This is the setup: origin fully hidden, WAF in front, rate limiting on sensitive paths.
The Five Rules I’d Write First
If you only have five slots and you’re not sure where to start:
- Block scanner user agents (empty + known tool strings)
- Block wp-login.php and xmlrpc.php (unless you’re actually running WP and you’ve allowlisted your IP)
- Geo-restrict your admin panel to your country + your VPN IP
- Challenge known high-threat geos on the main site
- Rate limit your login or API endpoint
That’s it. Five rules, maybe 20 minutes of setup, and you’ve gone from “default Cloudflare” to “actually defended.”
Your 3 AM self will appreciate it.