HostleloBlog
SavedSearch
Visit Hostlelo

Rate-Limit wp-login.php on Cloudflare's Free Plan

Add one Cloudflare Free rate limiting rule in front of wp-login.php, test it safely with curl, read the events, and avoid locking out your own admin login.

The short answer

Cloudflare's Free plan allows one rate limiting rule per domain, matching on Path, counting by IP, with a 10 second counting period and a 10 second mitigation timeout. Create it under Security rules > Create rule > Rate limiting rules, match the path /wp-login.php, set a limit such as 5 requests per 10 seconds and choose Block, which returns HTTP 429 by default. Rate limiting slows password guessing but does not replace strong passwords and two-factor authentication.

Reading options

On this page

Yes, you can rate-limit your WordPress login page on Cloudflare's Free plan. You get one rate limiting rule that counts requests per IP address over 10 seconds, and when an address goes over your limit, Cloudflare acts on it for 10 seconds before it reaches your server. It is small, but for /wp-login.php it is enough to slow down automated password guessing.

Here is why this matters. Login pages are a favourite target for bots that try thousands of passwords, and the WordPress developer handbook says plainly that you should "Rate-limit login attempts at the edge (WAF/CDN) or the web server" and "Apply rate limits to /wp-login.php and /xmlrpc.php globally." The edge (Cloudflare's network, in front of your host) is the cheapest place to do it, because blocked requests never touch your PHP workers.

By the end of this guide you will have one working rule in front of your login path, a safe way to test it from your own computer, a way to see the hits in Cloudflare's event log, and a plan for not locking yourself out.

What a rate limiting rule actually does

A rate limiting rule watches a set of requests (for example, everything sent to /wp-login.php), counts them per visitor, and takes an action once a visitor goes over a number you choose within a time window. Think of a bouncer with a clicker at one door: each guest can try the door a few times, but someone who tries it 30 times in 10 seconds waits outside for a bit.

Heads up: rate limiting is a speed bump, not a lock. Cloudflare's own page says "Rate limiting rules are not designed to allow a precise number of requests to reach your origin server." Counters are kept separately in each Cloudflare data center, so the count is approximate. Strong passwords and two-factor login still do the real protecting.

What the Free plan gives you (and what it does not)

Before you click anything, know the limits. At the time of writing (25 October 2026), Cloudflare's plan availability table for rate limiting rules says this about the Free plan:

  • Rules: 1 rule per zone (a zone is one domain in your Cloudflare account).
  • Fields you can match on: Path and Verified Bot only.
  • Counting characteristic: IP address only.
  • Counting period: 10 seconds.
  • Mitigation timeout (how long the action lasts): 10 seconds.
  • Custom counting expression: not available, so you cannot count only failed logins.
  • Cache exclusion: not available.

The paid plans add room. Pro allows 2 rules, more fields such as Host, URI, Full URI and Query, counting periods up to 1 minute and timeouts up to 1 hour. Business allows 5 rules, adds Method, Source IP and User Agent as fields, and periods up to 10 minutes with timeouts up to 1 day. A custom block response (your own status code and body) is "only available on Pro plans and above."

The layered setup that Cloudflare's best practices page describes for login protection, with increasing penalties for repeat failures, is listed for the Business plan or above. So on Free, you build one sensible rule and pair it with good habits on the WordPress side.

Quick tip: there is no free "log only" dry run here. Cloudflare's actions reference says the Log action is "Only available on Enterprise plans." On Free you test with a real action and a short timeout, which is exactly what the 10 second limit makes safe.

Before you start

  • Your domain is on Cloudflare, and the DNS record for your site is proxied (the orange cloud is on). If traffic does not pass through Cloudflare, the rule never sees it.
  • You can log in to the Cloudflare dashboard with an account that can edit security settings for the zone.
  • You know your real login path. Usually it is /wp-login.php. If a plugin moved it, use the new path.
  • A terminal with curl and a bash shell for the test, such as macOS Terminal, a Linux terminal, or WSL on Windows.
  • A second way into your site, such as your hosting control panel, in case something goes wrong.
  • Time: about 15 minutes. Risk: a block lasts 10 seconds per mitigation episode on Free, but traffic from a shared public IP uses one counter and sustained activity can trigger repeated episodes. Keep another way into your hosting account and monitor legitimate logins after deployment.

Set up the rule in the Cloudflare dashboard

These steps follow Cloudflare's guide to creating a rate limiting rule in the dashboard.

Step 1: Confirm the path that gets attacked

Cloudflare's best practices page says "The rule expression must match the path that receives attack traffic." A rule for /login will not catch requests to /wp-login.php. Open your site's login page in a browser and check the address bar, or look at the paths that show up in your security events (covered later). For a normal WordPress install, the path is /wp-login.php.

Step 2: Open the rate limiting rule editor

In the Cloudflare dashboard, select your domain and go to the Security rules page. Select Create rule > Rate limiting rules. Give it a clear Rule name, for example Login rate limit.

Step 3: Choose which requests to count

Under the rule's matching section, set Field to the URI path field, Operator to equals, and Value to /wp-login.php. On Free, Path is one of only two fields you can use, so this is where your one rule earns its keep.

If you also want to cover XML-RPC, which the WordPress handbook names next to the login page, add a second condition joined with Or. In expression form, the two-path version looks like this:

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

Because the Free plan counts by IP only, requests to both paths from the same address add up on one counter. That is fine for login defence: a normal person does not hit both files many times in 10 seconds.

Heads up: if your site uses a plugin or app that talks to /xmlrpc.php (some mobile apps and older publishing tools did), test that tool after deploying. If it breaks, remove the second condition.

Step 4: Set how requests are grouped

Under With the same characteristics, choose IP. On Free it is the only option. Cloudflare also groups by data center behind the scenes, which is one reason the count is approximate.

Step 5: Set the rate

Under When rate exceeds, enter the number of requests and the period. On Free the period is 10 seconds.

Pick a number with real people in mind. Loading the login page is one request to /wp-login.php, and submitting the form is another. Someone who mistypes a password twice may make five or six requests in a short burst. A limit of 5 requests per 10 seconds leaves room for that, while a bot sending dozens of guesses per second gets caught fast.

Here is a made-up example. Priya runs a small recipe blog. Her login page sees one or two requests a day from her, and suddenly 40 requests per second from one address overnight. With 5 per 10 seconds, that address keeps getting stopped for 10 seconds at a time, while Priya's own logins never come close.

Step 6: Choose the action and duration

Under Then take action, open Choose action. The options in Cloudflare's parameters reference are Block, Managed Challenge, Non-Interactive Challenge and Interactive Challenge.

For a login page, Block is the simplest choice on Free. Then set Duration to 10 seconds, which is the only mitigation timeout the Free plan offers.

If you prefer a challenge (a check page that real browsers pass), know how it behaves on Free. Cloudflare says Free, Pro and Business customers cannot choose a duration for challenge actions: they always use request throttling, meaning only requests above the rate are challenged. Also, once a visitor passes a challenge, their counter resets to zero.

Step 7: Deploy

Select Deploy to save and turn the rule on. If you are not ready, Save as Draft stores it without activating it. You should now see your rule listed on the Security rules page.

Test it with a safe request loop

This is a test you run from your own network, against your own site. Do not point it at anyone else's domain.

Your test requests come from your own public IP address, and that address will be blocked for 10 seconds when the test works. Do it at a quiet moment, and wait if other people on your connection need the admin area right now.

Step 1: Send a short burst of requests

This loop sends 15 simple GET requests to your login page and prints only the HTTP status code for each one. It sends no username or password, so it cannot lock a WordPress account. Replace example.com with your domain.

for i in $(seq 1 15); do
  printf "Request %s: " "$i"
  curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-login.php
done

Step 2: Read the result

Early lines should show 200, which means the login page loaded normally. Once you pass your limit, look for the status code to change, most likely to 429. Cloudflare's docs say the default status code for a rate limiting Block is 429 ("Too many requests"), and Free plans cannot change it, because custom responses need Pro or above.

The switch may not happen exactly at request number 6, because Cloudflare does not promise a precise cut-off. Whatever the loop prints, confirm the result in the dashboard under Analytics > Events, covered next.

If you chose a challenge action instead of Block, curl will not show 429. It cannot solve a challenge, so you will see whatever status the challenge page returns. For a clean test, use Block, then switch the action later if you want.

Step 3: Confirm the block ends

Wait at least 10 seconds, then run one request.

curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-login.php

A single 10-second mitigation episode should expire, but a continuing burst from your IP can trigger another one. If the next request is blocked again, stop the loop, review Security Events, and raise or disable the rule if normal users are affected.

Check the events in Cloudflare

Step 1: Open Security Events

In the Cloudflare dashboard, go to your domain's Analytics page and select the Events tab. This is the view Cloudflare's security events documentation describes.

Step 2: Filter to rate limiting

Look at the Events by service section and select Rate limiting rules, then choose Filter. Now only events from your rule are shown.

Step 3: Read the sampled logs

Scroll to Sampled logs. Each entry shows the date, the action taken and the security feature that acted. Expand an entry for full details, or select Edit columns to add IP address and path as columns. Your own test should appear with your IP and the /wp-login.php path.

Heads up: on the Free plan you get sampled logs only. When volume is high, not every event appears. Treat the list as a picture of what is happening, not a full count. Data goes back up to 31 days on all plans.

How not to lock yourself out

A Free-plan mitigation lasts 10 seconds at a time, not necessarily 10 seconds in total. People behind one office, school or carrier NAT address share a counter; one person or bot can temporarily block another, and continued traffic can retrigger the rule. Set the threshold from observed legitimate traffic and watch for false positives.

  • Keep the limit comfortable for humans. 5 per 10 seconds is a reasonable start; if your team logs in from one shared office address, raise it.
  • Remember that Free rules cannot exclude your own IP, because Source IP as a field needs Business or above.
  • If you really need an exemption, Cloudflare's IP Access rules are available to all customers, and the docs say "Allowing an IP or ASN will bypass any configured custom rules, rate limiting rules" and more. Only do this for a fixed address you control, because home connections often change IP.
  • Do not run the test loop while someone else on your network is in the middle of a login.
  • Keep your hosting control panel login handy. If something odd happens, you can always disable the rule from the Security rules page.

A real-world picture of these attacks

For a sense of scale, one security vendor, Quttera, published a case study of a WordPress brute force attack. It reports about 10,000 failed login attempts over roughly three days, coming from 94 unique IP addresses, with guesses at predictable usernames such as "admin" and "support". It is one vendor's case study, not a survey, but the pattern is common: many addresses, steady guessing, simple usernames.

That last detail matters for your one Free rule. A per-IP limit slows each address, but an attack spread across 94 addresses still gets many guesses through in total. This is why rate limiting sits next to strong passwords and two-factor authentication, never instead of them.

Server-side alternative: Nginx limit_req

Not on Cloudflare, or want a second layer at the server? If you manage your own Nginx, its ngx_http_limit_req_module can do something similar. These directives come from the official Nginx documentation.

Step 1: Define a shared memory zone

This line goes inside the http block (often in /etc/nginx/nginx.conf) and creates a zone named wplogin that tracks clients by IP and allows an average of 6 requests per minute.

limit_req_zone $binary_remote_addr zone=wplogin:10m rate=6r/m;

The docs explain that $binary_remote_addr is used because it is compact, and that one megabyte can hold about 16 thousand 64-byte states or about 8 thousand 128-byte states. Rates below one per second are written per minute, like 6r/m.

Step 2: Apply it to the login location

This block, inside your site's server block, applies the zone to /wp-login.php, allows a small burst of 3 extra requests without delay, and returns 429 instead of the default 503 for rejected requests.

location = /wp-login.php {
    limit_req zone=wplogin burst=3 nodelay;
    limit_req_status 429;

    # Copy the PHP handling lines from your existing "location ~ \.php$" block here,
    # for example your fastcgi include and fastcgi_pass lines.
}

Heads up: an exact-match location takes over from your general PHP block for this path. If you do not copy the PHP handling lines in, Nginx will not run wp-login.php through PHP. Those lines differ between servers, so copy them from your own config instead of from a tutorial.

Step 3: Try it in dry run mode first

Nginx has the free test mode that Cloudflare Free lacks. Adding this line inside the same location counts excess requests without limiting them (the directive appeared in Nginx 1.17.1). Remove it and reload when you are happy.

limit_req_dry_run on;

Step 4: Check the config and reload

This checks the configuration for errors and reloads Nginx only if the check passes.

sudo nginx -t && sudo systemctl reload nginx

Heads up: if your site runs behind Cloudflare and you use this Nginx rule too, Nginx sees Cloudflare's addresses instead of your visitors' unless you set up ngx_http_realip_module. Without it, many visitors share a few counters. Ask your host if you are unsure whether it is set up.

Apache and LiteSpeed users have other tools, and fail2ban, a program that reads your server logs and temporarily bans abusive IPs, is another server-side option the WordPress handbook mentions. Both are beyond this guide.

How to check it worked

  • Run the curl loop from your own network. You should see 200 first, then a change (most likely 429) once you pass the limit.
  • Wait 10 seconds and run a single request. The page should load normally again.
  • In Analytics > Events, filter Events by service to Rate limiting rules. Your test should show your IP and the /wp-login.php path.
  • Log in to WordPress normally in a browser. It should work as before.
  • For Nginx, run sudo nginx -t and expect it to report no errors, then repeat the curl loop.

Troubleshooting

The loop shows only 200, never 429

Likely cause: the path in the rule does not match, or traffic is not going through Cloudflare. Fix: check the rule's value is exactly /wp-login.php with the leading slash, and confirm the site's DNS record is proxied. Also send more requests, because counting is approximate.

I see 429 on my first request

Likely cause: someone sharing your public IP already exceeded the limit, or sustained traffic keeps triggering new 10-second mitigation episodes. Check Security Events and normal login traffic. If legitimate users are affected, raise the threshold or disable the rule; simply waiting 10 seconds may not be enough while the triggering traffic continues.

Curl shows 403 or another code instead of 429

Likely cause: a different security feature acted first, or you chose a challenge action. Cloudflare says most security features use 403 when blocking, while rate limiting rules use 429. Fix: check Analytics > Events to see which service handled the request, and use Block for testing.

The Duration or response options I see in guides are missing

Likely cause: those options belong to paid plans. On Free, the period and timeout are 10 seconds, and custom responses need Pro or above. Fix: nothing is broken; the guide you read was written for a bigger plan.

Bots still get through

Likely cause: the attack is spread across many IP addresses, each staying under your limit. Fix: per-IP limits cannot stop that alone. Turn on two-factor authentication, use long unique passwords, and avoid the username admin, which the WordPress handbook advises against.

Nginx login page downloads or shows a blank page

Likely cause: the exact-match location for /wp-login.php has no PHP handling lines. Fix: copy the fastcgi lines from your existing PHP location block into it, run sudo nginx -t, and reload.

Pair it with the protections that matter more

The WordPress handbook says to "Enable two-factor authentication (2FA) for all administrator accounts" and notes that WordPress core does not include it, so it comes from a plugin or an identity provider. It also says "Obscuring the login URL can reduce noise but should not be your only defense." Renaming your login page cuts bot noise, but it is not protection on its own.

If your hosting control panel is also a target, our guide to two-factor authentication on cPanel, Plesk and DirectAdmin covers that side. And because no defence is perfect, keep a tested backup: here is how to back up your site from cPanel and test a restore.

The one message worth forwarding

Copy and send this to whoever manages your site:

"Hi, could you add a Cloudflare rate limiting rule on our zone? Security rules > Create rule > Rate limiting rules. Match URI path equals /wp-login.php (and /xmlrpc.php if we don't use it), group by IP, 5 requests per 10 seconds, action Block, duration 10 seconds. Then test with a short curl loop from your own network and confirm in Analytics > Events that the rule fired and the login page loads again after 10 seconds. Please also confirm 2FA is on for every admin account."

One Free rule will not stop every attack, but it turns a flood of password guesses into a trickle, and it takes 15 minutes to set up.

Reader questions

Does a Cloudflare rate limiting rule replace a WordPress security plugin?

No. The rule slows repeated requests per IP at Cloudflare's edge, while a plugin can track failed logins per username and add two-factor authentication. They work well together.

Can I count only failed logins on the Free plan?

No. Custom counting expressions are available on Business and higher plans. On Free, every request to the matched path counts.

Will rate limiting the login page hurt my SEO?

Search engines do not need to log in, so a rule on the login path has little effect on crawling. Cloudflare does note that rate limiting verified bots in general may affect SEO.

How long does a block last on the Free plan?

As checked on 25 October 2026, Free has a 10-second mitigation timeout per episode, not a guaranteed 10-second total lockout. Users sharing a public IP share its counter, and sustained matching traffic can retrigger blocks. Monitor legitimate logins and raise or disable the rule if needed. Pro supports up to 1 hour and Business up to 1 day per mitigation episode.

Can I show a custom error page when the rule blocks someone?

Custom block responses, with your own status code from 400 to 499 and a body up to 30 KB, are available on Pro plans and above. Free plans use the default rate limiting response.

Should I also rate-limit xmlrpc.php?

The WordPress developer handbook recommends rate limiting both /wp-login.php and /xmlrpc.php, and disabling XML-RPC if you do not need it. On Free you can cover both paths in one rule with an Or condition.

Sources & further reading

  1. Cloudflare: Rate limiting rules (plan availability)
  2. Cloudflare: Create a rate limiting rule in the dashboard
  3. Cloudflare: Rate limiting best practices
  4. Cloudflare: Rate limiting parameters
  5. Cloudflare: Rules language actions
  6. Cloudflare: Security Events
  7. Cloudflare: IP Access rules
  8. WordPress Developer Resources: Brute Force Attacks
  9. Nginx: ngx_http_limit_req_module
  10. Quttera: WordPress brute force attack case study

Originally published . About our editorial updates.

Your next project deserves a better foundation.

Explore hosting built for your next chapter.

Explore hosting

Find your next answer.

Search published guides and articles.

Ctrl / ⌘ K to open · Esc to close