Cloudflare WAF Custom Rules for WordPress: 5 Rules That Block Most Attacks
Most attacks on WordPress sites are automated and boring: bots hammering wp-login.php, abusing xmlrpc.php, scanning for .env files and probing upload folders for backdoors. If your site is behind Cloudflare, you can stop most of that traffic before it ever reaches your server, even on the Free plan. Here are the custom WAF rules I set up on WordPress sites, with expressions you can paste in as-is.
Before you start: your DNS records must be proxied (orange cloud) for any of this to work. Rules don't apply to grey-cloud DNS-only records. The Free plan currently includes 5 custom rules, which is enough for the set below.
Where to add custom rules
- In the Cloudflare dashboard, select your domain.
- Go to Security → WAF → Custom rules. In the newer dashboard layout it's Security → Security rules, then Create rule → Custom rule.
- Give the rule a name, click Edit expression, paste the expression, choose the action and click Deploy.
Rules run in the order listed, and the first matching Block action ends evaluation, so the order below matters.
Rule 1: Skip rules for your own IP (optional but recommended)
If you or your team work from fixed IP addresses, let them bypass the rules below so you never lock yourself out.
(ip.src in {203.0.113.10 198.51.100.24})
Action: Skip → All remaining custom rules. Replace the example IPs with your own, separated by spaces. Place this rule first. If your IP changes often, skip this rule. The Managed Challenge in rule 3 still lets you in after a quick check.
Rule 2: Block xmlrpc.php
xmlrpc.php is a legacy remote-publishing API. Bots love it because a single request can test hundreds of passwords. Most sites don't need it.
(http.request.uri.path eq "/xmlrpc.php")
Action: Block.
Check first: Jetpack, the WordPress mobile app and some remote-management tools still use XML-RPC. If you rely on one of them, skip this rule or add an exception for that service's IP ranges.
Rule 3: Challenge wp-login.php and wp-admin
A Managed Challenge is a quick, usually invisible browser check. Real people pass it, and most bots don't. This stops credential stuffing without blocking you or your editors.
(http.request.uri.path eq "/wp-login.php")
or (starts_with(http.request.uri.path, "/wp-admin/")
and not http.request.uri.path in {"/wp-admin/admin-ajax.php" "/wp-admin/admin-post.php"})
Action: Managed Challenge.
Excluding admin-ajax.php and admin-post.php is important. Front-end features like add-to-cart, filters and contact forms call those URLs for logged-out visitors, and challenging them would break those features silently.
If your whole team is in a few countries, you can tighten this rule further:
((http.request.uri.path eq "/wp-login.php")
or (starts_with(http.request.uri.path, "/wp-admin/")
and not http.request.uri.path in {"/wp-admin/admin-ajax.php" "/wp-admin/admin-post.php"}))
and not ip.src.country in {"US" "GB"}
With the action set to Block, login from any other country is refused outright. Use two-letter country codes, and remember that a VPN or travel will put you in a different country.
Rule 4: Block PHP execution in uploads and probes for sensitive files
The uploads folder should only ever serve images and documents. A request for a .php file inside it is either a scan or an attacker trying to reach a backdoor they've planted. The same rule can catch requests for files that should never be public.
(starts_with(http.request.uri.path, "/wp-content/uploads/") and ends_with(http.request.uri.path, ".php"))
or (http.request.uri.path contains "/.env")
or (http.request.uri.path contains "/.git/")
or (ends_with(http.request.uri.path, ".sql"))
or (http.request.uri.path in {"/wp-config.php" "/wp-config.php.bak" "/wp-config.php~" "/readme.html" "/license.txt"})
or (http.request.uri.path eq "/wp-content/debug.log")
Action: Block.
This doesn't replace server-level protection. You should still disable PHP execution in uploads on the server itself. But it stops the traffic early and keeps your logs readable.
Rule 5: Stop username enumeration
Attackers usually find your admin username before trying passwords. WordPress gives it away in two places: /?author=1 redirects to the author's archive URL, and the REST API lists users at /wp-json/wp/v2/users.
(http.request.uri.query contains "author=" and not starts_with(http.request.uri.path, "/wp-admin/"))
or (starts_with(http.request.uri.path, "/wp-json/wp/v2/users")
and not http.cookie contains "wordpress_logged_in_")
Action: Block.
The cookie check keeps the block editor working: when you're logged in, the editor calls /wp-json/wp/v2/users/me, and your login cookie lets that request through. Logged-out visitors and bots get blocked.
Headless or app setups: if a front-end app or mobile app legitimately reads users from the REST API, leave the second half of this rule out.
Bonus: rate limit login attempts
Under Security → WAF → Rate limiting rules, the Free plan includes one rate limiting rule with a short counting period. A good use for it:
(http.request.uri.path eq "/wp-login.php" and http.request.method eq "POST")
Set a low threshold, say 5 requests per period per IP, and the action to Block. Real users rarely submit the login form that fast, while brute-force tools do it constantly.
Test your rules
After deploying, check each rule from a private browser window:
https://yoursite.com/xmlrpc.phpshould show a Cloudflare block page.https://yoursite.com/wp-login.phpshould show a brief challenge, then the login form.https://yoursite.com/?author=1should be blocked.- Add a product to the cart and submit a contact form to confirm
admin-ajax.phpstill works.
Then open Security → Events. You'll see each request a rule caught, which rule matched, and from where. On most WordPress sites the event log fills up within hours, which shows how much junk traffic was reaching your server before.
What a WAF won't fix
These rules cut the noise and block most automated attacks, but they're one layer, not the whole defense. They won't patch an outdated plugin with a known vulnerability, and they can't help if an attacker reaches your server's IP directly. Keep plugins updated, use strong passwords with two-factor authentication, and if possible lock your origin server so it only accepts traffic from Cloudflare's IP ranges.
If you're reading this because your site has already been hacked, a WAF won't remove what's already on the server. Start with my hacked WordPress cleanup guide, then add these rules once the site is clean.
Written by
Sameer Hussain, Senior Shopify & WordPress Engineer
Top Rated on Upwork and the Shopify & WordPress engineer at Inceptus Digital. Short on time, or not comfortable editing code? Send me your URL and the error on Upwork and I'll tell you how I'd fix it before you commit.
Need Custom Engineering or Growth Systems?
Our team helps ambitious brands build bespoke software, AI integrations, and high-conversion platforms.