2026年11月12日

2026年11月12日

Cloudflare WAF for WordPress: Custom, Rate Limiting and Managed Rules

This article is a localized edition of the Japanese original. Read the Japanese original

Cloudflare WAF inspects requests passing through Cloudflare at the edge (on Cloudflare's side) and stops those that match your conditions before they reach the origin server. Even on shared hosting where installing something like ModSecurity is hard, you can add WordPress-oriented protection from the Cloudflare dashboard.

This article checks what each plan allows, then covers custom rules that protect xmlrpc.php, wp-login.php and /wp-admin/, which are frequent targets on WordPress sites, plus rate limiting rules and managed rules, and how to handle visitor IP addresses correctly at the origin behind Cloudflare. It is based on Cloudflare's official documentation (developers.cloudflare.com) as of October 2026.

Prerequisite: requests must go through Cloudflare

The WAF only works when the DNS record is proxied (orange cloud) and requests pass through Cloudflare. Use the following command to check whether responses carry a cf-ray header.

curl -sI https://example.com/ | grep -iE "^cf-ray|^server"
# cf-ray: ... and server: cloudflare mean the request went through Cloudflare

What each plan allows

WAF limits differ by plan. The main differences are (as of October 2026):

Item Free Pro Business
Number of custom rules 5 20 100
Regex in custom rules No No Yes
Number of rate limiting rules 1 2 5
Rate limiting counting period 10 seconds Up to 1 minute Up to 10 minutes
Rate limiting mitigation timeout 10 seconds Up to 1 hour Up to 1 day
Fields usable in rate limiting conditions Path, verified bot status Host, URI, path, query, etc. The above plus method, source IP, User-Agent, etc.
Free Managed Ruleset Yes Yes Yes
Cloudflare Managed Ruleset / OWASP Core Ruleset No Yes Yes

The Free plan still gives you five custom rules. The Cloudflare Managed Ruleset, which includes WordPress rules, requires Pro or higher.

Protect WordPress entry points with custom rules

In the dashboard, open the domain, go to the "Security rules" page, and choose "Create rule" → "Custom rules". Give the rule a name, set the condition under "When incoming requests match" and the action under "Then take action", then click "Deploy" to enable it. You can build conditions by choosing "Field", "Operator" and "Value", or select "Edit expression" to write the expression directly. Conditions that use functions (such as starts_with()) have to be written as an expression. The rules below are shown as expressions.

Rather than going straight to "Block", start with "Managed Challenge", check in security events what is being caught, then switch to "Block". This avoids accidentally stopping legitimate use (the "Log" action is Enterprise-only).

Rule 1: block xmlrpc.php

(http.request.uri.path eq "/xmlrpc.php")

Action: Block

XML-RPC is an older remote-publishing mechanism that is sometimes used for password brute forcing. However, some plugins and apps, such as Jetpack, use XML-RPC, so confirm that you don't need it before blocking it.

Rule 2: challenge the login screen

(http.request.uri.path eq "/wp-login.php")

Action: Managed Challenge

Cloudflare's check runs as soon as the login screen is opened, and only browsers that pass can use the login form. This reduces automated login attempts by bots.

Rule 3: restrict the admin area to specific IP addresses

If you manage the site from a fixed IP address, you can block /wp-admin/ from everywhere else. Replace 203.0.113.10 with your own IP address.

(starts_with(http.request.uri.path, "/wp-admin/") and http.request.uri.path ne "/wp-admin/admin-ajax.php" and not ip.src in {203.0.113.10})

Action: Block

/wp-admin/admin-ajax.php is excluded because front-end search, forms, carts and the like call it on behalf of visitors who aren't logged in. Depending on your theme and plugins, /wp-admin/admin-post.php may also be used by visitor-facing forms, so check that your contact form and similar features still work.

If you manage the site from places where your IP address changes, such as on the road or at home, this rule locks you out too. In that case, instead of blocking by IP address, set the action to "Managed Challenge" or consider an authentication layer such as Cloudflare Access (Zero Trust).

Limit login attempts with rate limiting rules

A rate limiting rule blocks or challenges an IP address for a set time once its number of requests exceeds your threshold. Create one from "Security rules" with "Create rule" → "Rate limiting rules".

An example for the Free plan. On Free, only the path can be used in the condition, and both the counting period and the mitigation timeout are 10 seconds.

Item Example
Condition URI Path equals /wp-login.php
Counting characteristic IP
Threshold 5 requests per 10 seconds
Action Block (for 10 seconds)

On the Free plan you can't use the method (POST) as a condition, so loading the login screen (GET) is counted too. Set the threshold with in mind that one login is two requests: showing the screen and submitting it. On Pro and higher you can extend the counting period up to 1 minute, and on Business and higher you can add http.request.method eq "POST" to the condition.

You may be tempted to rate limit the REST API (/wp-json/) too, but the block editor and many plugins use the REST API heavily. If you do, set a generous threshold and check that editing doesn't get blocked.

Enable managed rules

Managed rules are rule sets that Cloudflare provides and keeps updated.

  • Free Managed Ruleset: available on all plans. Basic rules against high-impact known vulnerabilities.
  • Cloudflare Managed Ruleset: available on Pro and higher. Rules carry tags such as wordpress, and you can set the action for all rules with a tag at once.
  • OWASP Core Ruleset: available on Pro and higher. It tends to produce false positives, so start with a low sensitivity and watch the results.

If a managed rule stops a legitimate operation (saving a post, submitting a form and so on), find the rule in security events and disable just that rule or create an exception. Avoid turning off managed rules as a whole.

Register custom rules with the API (Rulesets API)

The Firewall Rules API and Filters API used by the previous WAF stopped being supported on June 15, 2025. Today's custom rules are added with the Rulesets API, to the entry point ruleset of the http_request_firewall_custom phase. /firewall/waf/packages and /rate_limits are also APIs of the previous WAF and rate limiting; current managed rules and rate limiting rules are handled with the Rulesets API.

ZONE_ID="your zone ID"
CLOUDFLARE_API_TOKEN="an API token with permission to edit the WAF"

# 1. Get the entry point ruleset for custom rules (note its id)
curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_request_firewall_custom/entrypoint" \
	--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

# 2. Add one rule to that ruleset
RULESET_ID="the id noted in step 1"
curl "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$RULESET_ID/rules" \
	--request POST \
	--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
	--json '{
		"description": "Block xmlrpc.php",
		"expression": "(http.request.uri.path eq \"/xmlrpc.php\")",
		"action": "block"
	}'

If step 1 returns 404 Not Found, there are no custom rules yet and no entry point ruleset exists. In that case, create the first rule in the dashboard, or create the entry point with the Rulesets API's "Create a zone ruleset" operation and then add rules.

Handle visitor IP addresses correctly at the origin

Behind Cloudflare, the party connecting to your origin server is a Cloudflare server, so PHP's $_SERVER['REMOTE_ADDR'] is a Cloudflare IP address. The original visitor's IP address arrives in the CF-Connecting-IP header.

However, don't overwrite REMOTE_ADDR by trusting this header unconditionally. If someone knows your origin's IP address, they can connect directly without going through Cloudflare and send any CF-Connecting-IP value they like. If login restrictions or a maintenance-mode IP allowlist rely on this value, they can be bypassed by spoofing.

The safe approach is to use the web server's own feature to take the header value only for connections coming from Cloudflare's IP ranges. Cloudflare publishes its IP ranges at https://www.cloudflare.com/ips-v4 and https://www.cloudflare.com/ips-v6, and the official documentation notes that the list needs regular updating.

For Nginx (ngx_http_realip_module), build a configuration file from the range list and include it in the http block.

# Build Nginx settings from Cloudflare's IP ranges
{
  curl -s https://www.cloudflare.com/ips-v4
  echo
  curl -s https://www.cloudflare.com/ips-v6
} | grep -v '^$' | sed 's/.*/set_real_ip_from &;/' | sudo tee /etc/nginx/conf.d/cloudflare-realip.inc
echo 'real_ip_header CF-Connecting-IP;' | sudo tee -a /etc/nginx/conf.d/cloudflare-realip.inc
# Inside the http block of /etc/nginx/nginx.conf
include /etc/nginx/conf.d/cloudflare-realip.inc;

Check the configuration with nginx -t before reloading. On Apache 2.4, enable mod_remoteip and set RemoteIPHeader CF-Connecting-IP plus a RemoteIPTrustedProxy for each Cloudflare range. Either way, PHP receives the correct, replaced REMOTE_ADDR, so WordPress code doesn't need to read the header.

If you really must handle it in PHP, use the header value only after confirming that REMOTE_ADDR is within Cloudflare's ranges. Include the IPv6 ranges in the check and update the range list regularly.

In addition, if the origin server's firewall accepts HTTP and HTTPS only from Cloudflare's IP ranges, direct access that bypasses Cloudflare is blocked altogether.

Checks and day-to-day operation

  • After enabling the rules, open /xmlrpc.php and /wp-login.php while logged out and confirm that you get a block or a challenge.
  • Make sure everyday operations such as saving posts in the admin, updating plugins and submitting the contact form are not blocked.
  • Security events in the dashboard show which rule stopped which request. If legitimate operations are being stopped, revise the conditions.
  • "I'm Under Attack!" mode challenges every visitor, so it is meant for temporary use while you are under attack. Normally, rely on rate limiting and custom rules.

Summary

  • The WAF only works for requests that go through Cloudflare. Check with the cf-ray header.
  • Even the Free plan gives you five custom rules and one rate limiting rule. The Cloudflare Managed Ruleset, which includes WordPress rules, requires Pro or higher.
  • Start by blocking xmlrpc.php, challenging wp-login.php, and restricting /wp-admin/ by IP (excluding admin-ajax.php).
  • Use the Rulesets API. The Firewall Rules API stopped being supported on June 15, 2025.
  • At the origin, don't trust CF-Connecting-IP unconditionally; use the web server's realip / remoteip settings to replace the IP address only for connections from Cloudflare.

Related articles:

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲