{"id":1367,"date":"2025-11-05T16:05:20","date_gmt":"2025-11-05T13:05:20","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/can-cloudflare-waf-and-rate-limiting-actually-stop-wordpress-bots-heres-the-playbook-i-use\/"},"modified":"2025-11-05T16:05:20","modified_gmt":"2025-11-05T13:05:20","slug":"can-cloudflare-waf-and-rate-limiting-actually-stop-wordpress-bots-heres-the-playbook-i-use","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/can-cloudflare-waf-and-rate-limiting-actually-stop-wordpress-bots-heres-the-playbook-i-use\/","title":{"rendered":"Can Cloudflare WAF and Rate Limiting Actually Stop WordPress Bots? Here\u2019s the Playbook I Use"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><p>So there I was, sipping a too-strong coffee, watching a client\u2019s WordPress site crawl even though we had scaled the server more than enough. CPU looked fine. Database was healthy. No plugin gone rogue. But something felt off\u2014those sneaky little spikes that make your graph look like a heartbeat monitor were exactly the kind that bots love to create. Ever had that moment when your WooCommerce checkout feels like it\u2019s wading through molasses, even though real traffic is steady? That was the day I leaned fully into Cloudflare\u2019s Web Application Firewall (WAF) and Rate Limiting as the front door bouncer. Not the bouncer who argues with your guests, but the one who quietly filters trouble before it ever gets to your lobby.<\/p>\n<p>In this guide, I\u2019ll walk you through how I protect WordPress and WooCommerce from abusive bots without breaking logins, payments, or APIs. We\u2019ll talk about the annoying entry points bots love, the rule recipes I actually use, and how to tune rate limits so legit customers won\u2019t ever notice. By the end, you\u2019ll have a plan that feels like a friend showing you the ropes\u2014not a manual written by a turnstile.<\/p>\n<div id=\"toc_container\" class=\"toc_transparent no_bullets\"><p class=\"toc_title\">\u0130&ccedil;indekiler<\/p><ul class=\"toc_list\"><li><a href=\"#Why_WordPress_Feels_Like_a_Magnet_for_Bots\"><span class=\"toc_number toc_depth_1\">1<\/span> Why WordPress Feels Like a Magnet for Bots<\/a><\/li><li><a href=\"#The_Foundation_How_Cloudflare_Sees_WordPress_Traffic\"><span class=\"toc_number toc_depth_1\">2<\/span> The Foundation: How Cloudflare Sees WordPress Traffic<\/a><ul><li><a href=\"#The_traffic_story_in_plain_English\"><span class=\"toc_number toc_depth_2\">2.1<\/span> The traffic story in plain English<\/a><\/li><li><a href=\"#What_you_should_already_be_doing\"><span class=\"toc_number toc_depth_2\">2.2<\/span> What you should already be doing<\/a><\/li><\/ul><\/li><li><a href=\"#The_WordPress_Entry_Points_Bots_Love_And_How_I_Treat_Them\"><span class=\"toc_number toc_depth_1\">3<\/span> The WordPress Entry Points Bots Love (And How I Treat Them)<\/a><ul><li><a href=\"#1_wp-loginphp_the_all-you-can-eat_buffet_for_brute_force\"><span class=\"toc_number toc_depth_2\">3.1<\/span> 1) wp-login.php: the all-you-can-eat buffet for brute force<\/a><\/li><li><a href=\"#2_xmlrpcphp_legacy_door_too_many_keys\"><span class=\"toc_number toc_depth_2\">3.2<\/span> 2) xmlrpc.php: legacy door, too many keys<\/a><\/li><li><a href=\"#3_REST_API_convenient_chatty_easily_poked\"><span class=\"toc_number toc_depth_2\">3.3<\/span> 3) REST API: convenient, chatty, easily poked<\/a><\/li><li><a href=\"#4_admin-ajax_the_quiet_workhorse_that_can_still_be_abused\"><span class=\"toc_number toc_depth_2\">3.4<\/span> 4) admin-ajax: the quiet workhorse that can still be abused<\/a><\/li><\/ul><\/li><li><a href=\"#My_Go-To_Cloudflare_WAF_Rule_Recipes_In_Plain_Language\"><span class=\"toc_number toc_depth_1\">4<\/span> My Go-To Cloudflare WAF Rule Recipes (In Plain Language)<\/a><ul><li><a href=\"#Protect_login_with_a_firm_handshake_not_a_shove\"><span class=\"toc_number toc_depth_2\">4.1<\/span> Protect login with a firm handshake, not a shove<\/a><\/li><li><a href=\"#Block_or_quarantine_xmlrpcphp\"><span class=\"toc_number toc_depth_2\">4.2<\/span> Block or quarantine xmlrpc.php<\/a><\/li><li><a href=\"#Lock_down_admin_to_known_networks_when_possible\"><span class=\"toc_number toc_depth_2\">4.3<\/span> Lock down admin to known networks (when possible)<\/a><\/li><li><a href=\"#Keep_REST_API_chatty_but_sane\"><span class=\"toc_number toc_depth_2\">4.4<\/span> Keep REST API chatty but sane<\/a><\/li><li><a href=\"#Let_verified_bots_through\"><span class=\"toc_number toc_depth_2\">4.5<\/span> Let verified bots through<\/a><\/li><\/ul><\/li><li><a href=\"#Rate_Limiting_That_Doesnt_Break_WooCommerce\"><span class=\"toc_number toc_depth_1\">5<\/span> Rate Limiting That Doesn\u2019t Break WooCommerce<\/a><ul><li><a href=\"#General_approach\"><span class=\"toc_number toc_depth_2\">5.1<\/span> General approach<\/a><\/li><li><a href=\"#Login_rate_limit\"><span class=\"toc_number toc_depth_2\">5.2<\/span> Login rate limit<\/a><\/li><li><a href=\"#xmlrpcphp_rate_limit_only_if_you_cant_block\"><span class=\"toc_number toc_depth_2\">5.3<\/span> xmlrpc.php rate limit (only if you can\u2019t block)<\/a><\/li><li><a href=\"#REST_API_and_WooCommerce\"><span class=\"toc_number toc_depth_2\">5.4<\/span> REST API and WooCommerce<\/a><\/li><li><a href=\"#admin-ajax_rate_limits\"><span class=\"toc_number toc_depth_2\">5.5<\/span> admin-ajax rate limits<\/a><\/li><li><a href=\"#Escalation_with_grace\"><span class=\"toc_number toc_depth_2\">5.6<\/span> Escalation with grace<\/a><\/li><\/ul><\/li><li><a href=\"#Testing_Observability_and_The_Tuning_Loop\"><span class=\"toc_number toc_depth_1\">6<\/span> Testing, Observability, and The Tuning Loop<\/a><ul><li><a href=\"#Start_in_Simulate_mode\"><span class=\"toc_number toc_depth_2\">6.1<\/span> Start in Simulate mode<\/a><\/li><li><a href=\"#Watch_the_Firewall_Events_like_a_hawk\"><span class=\"toc_number toc_depth_2\">6.2<\/span> Watch the Firewall Events like a hawk<\/a><\/li><li><a href=\"#Dont_punish_the_good_bots\"><span class=\"toc_number toc_depth_2\">6.3<\/span> Don\u2019t punish the good bots<\/a><\/li><li><a href=\"#Log_to_origins_when_it_helps\"><span class=\"toc_number toc_depth_2\">6.4<\/span> Log to origins when it helps<\/a><\/li><li><a href=\"#Tune_weekly_then_monthly\"><span class=\"toc_number toc_depth_2\">6.5<\/span> Tune weekly, then monthly<\/a><\/li><\/ul><\/li><li><a href=\"#Real-World_Gotchas_The_Stuff_I_Learned_the_Hard_Way\"><span class=\"toc_number toc_depth_1\">7<\/span> Real-World Gotchas (The Stuff I Learned the Hard Way)<\/a><ul><li><a href=\"#WooCommerce_checkout_is_sacred\"><span class=\"toc_number toc_depth_2\">7.1<\/span> WooCommerce checkout is sacred<\/a><\/li><li><a href=\"#CDN_caching_and_WAF_are_teammates\"><span class=\"toc_number toc_depth_2\">7.2<\/span> CDN caching and WAF are teammates<\/a><\/li><li><a href=\"#Dont_rely_on_user_agents_alone\"><span class=\"toc_number toc_depth_2\">7.3<\/span> Don\u2019t rely on user agents alone<\/a><\/li><li><a href=\"#Country_blocks_can_be_blunt_instruments\"><span class=\"toc_number toc_depth_2\">7.4<\/span> Country blocks can be blunt instruments<\/a><\/li><li><a href=\"#When_to_use_Super_Bot_Fight_Mode\"><span class=\"toc_number toc_depth_2\">7.5<\/span> When to use Super Bot Fight Mode<\/a><\/li><li><a href=\"#Reputation_signals_are_helpful_not_divine\"><span class=\"toc_number toc_depth_2\">7.6<\/span> Reputation signals are helpful, not divine<\/a><\/li><\/ul><\/li><li><a href=\"#Step-by-Step_A_Friendly_Setup_You_Can_Copy\"><span class=\"toc_number toc_depth_1\">8<\/span> Step-by-Step: A Friendly Setup You Can Copy<\/a><ul><li><a href=\"#1_Turn_on_Cloudflare_managed_WAF_rules\"><span class=\"toc_number toc_depth_2\">8.1<\/span> 1) Turn on Cloudflare managed WAF rules<\/a><\/li><li><a href=\"#2_Add_your_custom_WordPress_rules\"><span class=\"toc_number toc_depth_2\">8.2<\/span> 2) Add your custom WordPress rules<\/a><\/li><li><a href=\"#3_Add_rate_limits_where_they_matter\"><span class=\"toc_number toc_depth_2\">8.3<\/span> 3) Add rate limits where they matter<\/a><\/li><li><a href=\"#4_Test_with_real_flows\"><span class=\"toc_number toc_depth_2\">8.4<\/span> 4) Test with real flows<\/a><\/li><li><a href=\"#5_Observe_and_adjust\"><span class=\"toc_number toc_depth_2\">8.5<\/span> 5) Observe and adjust<\/a><\/li><\/ul><\/li><li><a href=\"#Frequently_Overlooked_Details_That_Make_a_Big_Difference\"><span class=\"toc_number toc_depth_1\">9<\/span> Frequently Overlooked Details That Make a Big Difference<\/a><ul><li><a href=\"#Whitelist_your_own_services\"><span class=\"toc_number toc_depth_2\">9.1<\/span> Whitelist your own services<\/a><\/li><li><a href=\"#Keep_WordPress_itself_healthy\"><span class=\"toc_number toc_depth_2\">9.2<\/span> Keep WordPress itself healthy<\/a><\/li><li><a href=\"#Consider_Turnstile_for_the_login_page\"><span class=\"toc_number toc_depth_2\">9.3<\/span> Consider Turnstile for the login page<\/a><\/li><li><a href=\"#Set_expectations_with_your_team\"><span class=\"toc_number toc_depth_2\">9.4<\/span> Set expectations with your team<\/a><\/li><\/ul><\/li><li><a href=\"#A_Story_From_the_Trenches_The_Store_That_Stopped_Bleeding\"><span class=\"toc_number toc_depth_1\">10<\/span> A Story From the Trenches: The Store That Stopped Bleeding<\/a><\/li><li><a href=\"#Security_Meets_Performance_The_Quiet_Win\"><span class=\"toc_number toc_depth_1\">11<\/span> Security Meets Performance: The Quiet Win<\/a><\/li><li><a href=\"#Wrap-Up_Your_Friendly_Checklist_to_Keep_Bots_in_Their_Lane\"><span class=\"toc_number toc_depth_1\">12<\/span> Wrap-Up: Your Friendly Checklist to Keep Bots in Their Lane<\/a><\/li><\/ul><\/div>\n<h2 id=\"section-1\"><span id=\"Why_WordPress_Feels_Like_a_Magnet_for_Bots\">Why WordPress Feels Like a Magnet for Bots<\/span><\/h2>\n<p>I remember a client who swore they were hit by \u201cDDoS attacks every day.\u201d The funny thing? Most of what they saw wasn\u2019t a DDoS at all. It was a thousand tiny cuts: login brute force attempts, XML-RPC abuse, fake cart updates, REST API probing, and even crawlers slamming the same endpoints over and over. None of it was glamorous. All of it was exhausting. And without a layer in front of WordPress, those annoyances become resource drains that look like outages from the inside.<\/p>\n<p>Here\u2019s the thing about WordPress: because it\u2019s predictable, it\u2019s also predictable for attackers. They know where to knock. <strong>\/wp-login.php<\/strong> is always there. <strong>\/xmlrpc.php<\/strong> is often left open. <strong>\/wp-json\/<\/strong> frequently reveals more than you intend. And on WooCommerce sites, endpoints like <strong>cart fragments<\/strong> and <strong>admin-ajax<\/strong> can be abused to trigger expensive operations without buying anything. Think of it like a shop with clearly labeled doors\u2014convenient for customers, obvious for troublemakers.<\/p>\n<p>The magic of putting Cloudflare in front is that you gain a <strong>smart doorman<\/strong> who knows how to read the room. The WAF catches known bad patterns and the rate limiting rules keep repeat offenders from hammering the same doorbell all night. The trick is to combine them without making your real visitors solve puzzles or trip over blocks. Done right, it\u2019s invisible.<\/p>\n<h2 id=\"section-2\"><span id=\"The_Foundation_How_Cloudflare_Sees_WordPress_Traffic\">The Foundation: How Cloudflare Sees WordPress Traffic<\/span><\/h2>\n<h3><span id=\"The_traffic_story_in_plain_English\">The traffic story in plain English<\/span><\/h3>\n<p>Before you write rules, you want to understand what Cloudflare can see and what WordPress expects. Cloudflare sits in front as a reverse proxy. Every request\u2014whether from a browser, a bot, or a script\u2014hits Cloudflare first. That means you can inspect path, country, ASN, method, headers, user agent, and even whether the visitor matches a verified search bot. It\u2019s like having a metal detector on the door and a guest list in hand.<\/p>\n<p>When I tune rules, I\u2019m not aiming to block everything that looks unusual. I\u2019m aiming to <strong>reduce cost per request<\/strong>. If a request is junk, drop or challenge it at the edge. If it\u2019s likely real, let it flow. And if it\u2019s suspicious, slow it down or push a managed challenge so that honest humans pass without friction.<\/p>\n<h3><span id=\"What_you_should_already_be_doing\">What you should already be doing<\/span><\/h3>\n<p>Make sure your DNS is proxied (the little orange cloud should be on), your origin is locked down to only accept traffic from Cloudflare IPs if possible, and Cloudflare\u2019s default managed WAF rules are enabled for WordPress. It\u2019s also a good idea to log in to your WordPress dashboard and note what\u2019s critical: login, checkout, REST endpoints used by plugins, webhooks, and any third-party integrations. You\u2019ll protect those areas differently from your blog posts or static pages.<\/p>\n<p>If you\u2019re deep into performance too\u2014and let\u2019s be honest, security and performance are cousins\u2014pairing proper caching with the WAF is a game-changer. If you haven\u2019t yet, check out my walkthrough on <a href=\"https:\/\/www.dchost.com\/blog\/en\/wordpress-icin-cdn-onbellek-kurallari-nasil-kurulur-woocommercede-html-cache-bypass-ve-edge-ayarlariyla-uctan-uca-hiz\/\">CDN caching rules for WordPress and WooCommerce that won\u2019t break your site<\/a>. Caching the right stuff means fewer requests reach PHP in the first place, and your WAF and rate limits have less work to do.<\/p>\n<h2 id=\"section-3\"><span id=\"The_WordPress_Entry_Points_Bots_Love_And_How_I_Treat_Them\">The WordPress Entry Points Bots Love (And How I Treat Them)<\/span><\/h2>\n<h3><span id=\"1_wp-loginphp_the_all-you-can-eat_buffet_for_brute_force\">1) wp-login.php: the all-you-can-eat buffet for brute force<\/span><\/h3>\n<p>There\u2019s a reason login attempts spike after a post hits social. Attackers hope that someone reuses passwords or forgot to disable admin as a username. For <strong>\/wp-login.php<\/strong>, I almost always combine two strategies: limit the frequency of POST requests and add friction for suspicious patterns. On Cloudflare, this means a WAF rule that challenges known-bad signals and a rate limiting rule that caps retries.<\/p>\n<p>A realistic, low-friction approach: allow a few attempts, then challenge. Use a <strong>Managed Challenge<\/strong> instead of a full CAPTCHA\u2014humans pass quickly; scripts typically fail. When you\u2019re expecting higher admin activity (like a sale or a content blitz), dial thresholds gently upward to avoid frustrating your own team.<\/p>\n<h3><span id=\"2_xmlrpcphp_legacy_door_too_many_keys\">2) xmlrpc.php: legacy door, too many keys<\/span><\/h3>\n<p>XML-RPC is like the old service entrance nobody uses until they do. These days, many sites can disable it safely. Brute force via XML-RPC is still common because it allows batched attempts in a single request. So I usually block it outright unless there\u2019s a specific, proven need. If you do need it (rare), wrap it in strict conditions and a tight rate limit.<\/p>\n<h3><span id=\"3_REST_API_convenient_chatty_easily_poked\">3) REST API: convenient, chatty, easily poked<\/span><\/h3>\n<p>The REST API (<strong>\/wp-json\/<\/strong> or <strong>\/?rest_route=<\/strong>) is essential to modern WordPress. WooCommerce, page builders, and headless setups live there. The goal isn\u2019t to block it; it\u2019s to moderate it. Focus on methods and paths: GET on public endpoints is fine; repeated POSTs to sensitive routes should be watched closely. For WooCommerce stores, watch endpoints that touch carts, checkout, and session state through admin-ajax.<\/p>\n<h3><span id=\"4_admin-ajax_the_quiet_workhorse_that_can_still_be_abused\">4) admin-ajax: the quiet workhorse that can still be abused<\/span><\/h3>\n<p><strong>\/wp-admin\/admin-ajax.php<\/strong> powers a lot, especially in WooCommerce. Trouble is, it\u2019s often called frequently, sometimes every few seconds. The trick is not to blanket-throttle it, but to look at <strong>action=<\/strong> parameters and the method being used. Read your plugin docs to know which actions are safe to throttle and which are mission-critical. Then craft rate limits that don\u2019t smack real buyers in the face.<\/p>\n<h2 id=\"section-4\"><span id=\"My_Go-To_Cloudflare_WAF_Rule_Recipes_In_Plain_Language\">My Go-To Cloudflare WAF Rule Recipes (In Plain Language)<\/span><\/h2>\n<p>I\u2019ve lost track of how many times I\u2019ve stared at Cloudflare\u2019s Firewall Events, piecing together a pattern like a detective with too many sticky notes. Let me spare you some of that with the rules I keep coming back to. Consider these \u201cstarting recipes.\u201d You\u2019ll tweak them based on your site\u2019s traffic and plugins.<\/p>\n<h3><span id=\"Protect_login_with_a_firm_handshake_not_a_shove\">Protect login with a firm handshake, not a shove<\/span><\/h3>\n<p>Goal: slow brute force without punishing the one person who fat-fingered their password three times.<\/p>\n<p>Cloudflare WAF expression idea:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">(http.request.uri.path eq &quot;\/wp-login.php&quot; and http.request.method eq &quot;POST&quot; and not cf.client.bot)<\/code><\/pre>\n<p>Action: Managed Challenge. Optional add-ons: challenge only when <strong>cf.threat_score<\/strong> is high, or when the same IP hits multiple times quickly. Pair this with a separate rate limit rule (coming up) so repeat offenders get cooled off.<\/p>\n<h3><span id=\"Block_or_quarantine_xmlrpcphp\">Block or quarantine xmlrpc.php<\/span><\/h3>\n<p>Cloudflare WAF expression idea:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">(http.request.uri.path eq &quot;\/xmlrpc.php&quot;)<\/code><\/pre>\n<p>Action: Block. If you truly must keep XML-RPC, consider changing the action to Managed Challenge and add a rate limit. Be wary of letting user-agent checks alone decide\u2014those are easy to fake.<\/p>\n<h3><span id=\"Lock_down_admin_to_known_networks_when_possible\">Lock down admin to known networks (when possible)<\/span><\/h3>\n<p>For teams with static office IPs or a VPN, this is the cleanest approach.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">((http.request.uri.path eq &quot;\/wp-login.php&quot;) or (http.request.uri.path contains &quot;\/wp-admin&quot;)) and not ip.src in {YOUR_OFFICE_IPS_OR_VPN}<\/code><\/pre>\n<p>Action: Block or Managed Challenge. If your team is remote with changing IPs, consider Cloudflare Zero Trust for identity-aware access to wp-admin. It\u2019s extra setup but incredibly smooth once in place.<\/p>\n<h3><span id=\"Keep_REST_API_chatty_but_sane\">Keep REST API chatty but sane<\/span><\/h3>\n<p>I rarely hard-block <strong>\/wp-json\/<\/strong>. Instead, I challenge POSTs on sensitive routes if they come from sketchy countries or ASNs not typical for your audience. Start gently: challenge, log, and observe. Then tighten if needed.<\/p>\n<h3><span id=\"Let_verified_bots_through\">Let verified bots through<\/span><\/h3>\n<p>Search engines, performance monitors, and integrations should flow without friction. Cloudflare offers a verified bots signal. In the WAF, I like to add \u201cand not cf.client.bot\u201d to my stricter rules. This avoids punishing Googlebot or legitimate crawlers unnecessarily. If you\u2019re curious about the official bot lists and verification signals, Cloudflare\u2019s docs on <a href=\"https:\/\/developers.cloudflare.com\/waf\/tools\/managed-bot\/verified-bots\/\" rel=\"nofollow noopener\" target=\"_blank\">verified bots and good bot signals<\/a> are a helpful reference when you need them.<\/p>\n<h2 id=\"section-5\"><span id=\"Rate_Limiting_That_Doesnt_Break_WooCommerce\">Rate Limiting That Doesn\u2019t Break WooCommerce<\/span><\/h2>\n<p>Rate limiting is where most people either underdo it (no limits at all) or overdo it (everyone gets blocked during checkout). The sweet spot is using different limits for different endpoints and methods. And yes, you\u2019ll tune them a few times until it feels right. That\u2019s normal.<\/p>\n<h3><span id=\"General_approach\">General approach<\/span><\/h3>\n<p>Think of rate limiting like a speed bump, not a roadblock. A good rule slows down repetition and discourages scripts but still lets humans proceed. I like to use <strong>per-IP counters<\/strong>, small windows (like 30\u201360 seconds), and actions that escalate with behavior. First a managed challenge; then a block if the pattern won\u2019t stop.<\/p>\n<h3><span id=\"Login_rate_limit\">Login rate limit<\/span><\/h3>\n<p>Cloudflare Rate Limiting rule idea:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">Expression: (http.request.uri.path eq &quot;\/wp-login.php&quot; and http.request.method eq &quot;POST&quot;)\nThreshold: 10 requests per 60 seconds per IP\nAction: Managed Challenge (or Block if you\u2019re seeing heavy abuse)<\/code><\/pre>\n<p>Tuning tip: if your organization has multiple admins behind one office IP, increase the threshold a bit. You can also carve an exception for your office IPs so your team never bumps into the speed bump.<\/p>\n<h3><span id=\"xmlrpcphp_rate_limit_only_if_you_cant_block\">xmlrpc.php rate limit (only if you can\u2019t block)<\/span><\/h3>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">Expression: (http.request.uri.path eq &quot;\/xmlrpc.php&quot;)\nThreshold: 5 requests per 60 seconds per IP\nAction: Block<\/code><\/pre>\n<p>Most sites can block XML-RPC entirely. If not, this rule helps prevent the batched login attack style that XML-RPC enables.<\/p>\n<h3><span id=\"REST_API_and_WooCommerce\">REST API and WooCommerce<\/span><\/h3>\n<p>WooCommerce storefronts need traffic flowing to <strong>\/wp-json\/<\/strong>, <strong>admin-ajax<\/strong>, and sometimes custom endpoints. The goal isn\u2019t to cap overall API traffic; it\u2019s to curb abusive patterns.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">Expression: (http.request.uri.path contains &quot;\/wp-json\/&quot; and http.request.method in {&quot;POST&quot;,&quot;PUT&quot;,&quot;DELETE&quot;})\nThreshold: 30 requests per 60 seconds per IP\nAction: Managed Challenge<\/code><\/pre>\n<p>This lets regular browsing and GET requests sail through while adding friction to scripts hammering cart or account actions. If you find a specific route abused (like a plugin endpoint), target it directly with a tighter threshold.<\/p>\n<h3><span id=\"admin-ajax_rate_limits\">admin-ajax rate limits<\/span><\/h3>\n<p>admin-ajax is a blender for many plugins. Some actions are harmless and frequent, especially cart fragments. Others are computationally expensive. When I don\u2019t know every action off-hand, I start with a broad rule for POST with a generous threshold, observe, and then narrow it down:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">Expression: (http.request.uri.path eq &quot;\/wp-admin\/admin-ajax.php&quot; and http.request.method eq &quot;POST&quot;)\nThreshold: 120 requests per 60 seconds per IP\nAction: Managed Challenge<\/code><\/pre>\n<p>After a week of logs, you\u2019ll know which actions deserve stricter limits. Adjust those individually so you\u2019re precise without crushing normal behavior.<\/p>\n<h3><span id=\"Escalation_with_grace\">Escalation with grace<\/span><\/h3>\n<p>On noisy days\u2014like Black Friday\u2014expect more everything. I usually switch rate limit actions from Block to Managed Challenge during big events. Humans breeze through; bots get stuck. If abuse is intense, escalate to short blocks only for the noisy endpoints while leaving the rest of the site friendly.<\/p>\n<h2 id=\"section-6\"><span id=\"Testing_Observability_and_The_Tuning_Loop\">Testing, Observability, and The Tuning Loop<\/span><\/h2>\n<h3><span id=\"Start_in_Simulate_mode\">Start in Simulate mode<\/span><\/h3>\n<p>Cloudflare lets you preview what the WAF would have done without actually blocking traffic. I love this for the first week of a new ruleset. Launch new rules in <strong>Log<\/strong> or <strong>Simulate<\/strong> mode, watch the events, then switch to Challenge or Block once you\u2019re confident.<\/p>\n<h3><span id=\"Watch_the_Firewall_Events_like_a_hawk\">Watch the Firewall Events like a hawk<\/span><\/h3>\n<p>There\u2019s a story behind every spike. Open Cloudflare\u2019s Firewall Events and look at what\u2019s getting matched. Patterns jump out: same user-agent, same ASN, same URI. This also helps you prove to your team that the rules are helping rather than randomly punishing users. I often keep a dashboard open during the first 48 hours with filters for login, xmlrpc, admin-ajax, and wp-json.<\/p>\n<h3><span id=\"Dont_punish_the_good_bots\">Don\u2019t punish the good bots<\/span><\/h3>\n<p>This is where verified bot detection is golden. If your rate limits or WAF rules hit Googlebot, review your expressions. Include allowances for <strong>cf.client.bot<\/strong> where appropriate. The official Cloudflare <a href=\"https:\/\/developers.cloudflare.com\/waf\/tools\/managed-bot\/verified-bots\/\" rel=\"nofollow noopener\" target=\"_blank\">verified bots documentation<\/a> explains how Cloudflare knows a crawler is legit. It\u2019s worth skimming once, then codifying that exemption in your rules.<\/p>\n<h3><span id=\"Log_to_origins_when_it_helps\">Log to origins when it helps<\/span><\/h3>\n<p>Sometimes you want to correlate a 403 in Cloudflare with a PHP log line. Turn on local logging or integrate analytics so you can follow a session\u2019s story end to end. If you\u2019ve got a separate monitoring tool for your origin, it\u2019s satisfying to watch resource usage drop after a good WAF deployment.<\/p>\n<h3><span id=\"Tune_weekly_then_monthly\">Tune weekly, then monthly<\/span><\/h3>\n<p>Real talk: you\u2019ll tweak rules more the first month than the next six. Traffic has seasons. After the training wheels come off, set a monthly reminder to review the top matched rules and see if thresholds need slight nudges. This habit keeps you ahead of new scraping patterns and plugin updates.<\/p>\n<h2 id=\"section-7\"><span id=\"Real-World_Gotchas_The_Stuff_I_Learned_the_Hard_Way\">Real-World Gotchas (The Stuff I Learned the Hard Way)<\/span><\/h2>\n<h3><span id=\"WooCommerce_checkout_is_sacred\">WooCommerce checkout is sacred<\/span><\/h3>\n<p>I once saw a store experience \u201crandom\u201d checkout failures. The culprit? An overzealous admin-ajax throttle. It\u2019s tempting to crush everything with a big hammer, but checkout brings many background requests in a short window. Let your buyers glide through. If you rate-limit <strong>\/wp-admin\/admin-ajax.php<\/strong>, ensure generous thresholds and consider exempting checkout-specific actions. Watch the logs when a real customer checks out and note the pattern.<\/p>\n<h3><span id=\"CDN_caching_and_WAF_are_teammates\">CDN caching and WAF are teammates<\/span><\/h3>\n<p>Security and caching complement each other beautifully. Cache the static and anonymous stuff as much as you can so your WAF mostly guards dynamic endpoints. If you\u2019re not sure where to start, my <a href=\"https:\/\/www.dchost.com\/blog\/en\/wordpress-icin-cdn-onbellek-kurallari-nasil-kurulur-woocommercede-html-cache-bypass-ve-edge-ayarlariyla-uctan-uca-hiz\/\">CDN caching playbook for WordPress and WooCommerce<\/a> walks through cache-control headers and safe bypasses so you won\u2019t accidentally cache logged-in sessions or carts.<\/p>\n<h3><span id=\"Dont_rely_on_user_agents_alone\">Don\u2019t rely on user agents alone<\/span><\/h3>\n<p>Attackers fake user agents like it\u2019s a hobby. \u201cGooglebot\u201d in the UA string doesn\u2019t prove anything. Use Cloudflare\u2019s verified bot signal where possible and look at behavior patterns rather than a single header.<\/p>\n<h3><span id=\"Country_blocks_can_be_blunt_instruments\">Country blocks can be blunt instruments<\/span><\/h3>\n<p>Blocking entire countries sometimes sounds appealing, but legitimate buyers travel, and payment gateways or inventory feeds might route through places you didn\u2019t anticipate. If you must geo-block, do it only for admin and login, not your entire site. Or use challenges for high-risk regions rather than outright blocks.<\/p>\n<h3><span id=\"When_to_use_Super_Bot_Fight_Mode\">When to use Super Bot Fight Mode<\/span><\/h3>\n<p>Cloudflare\u2019s bot features are powerful. If you turn on aggressive modes, test everything\u2014especially admin-ajax and REST API heavy flows. When I enable extra bot fighting features, I prefer to use allow rules for known-good services first. Cloudflare\u2019s docs on <a href=\"https:\/\/developers.cloudflare.com\/bots\/get-started\/\" rel=\"nofollow noopener\" target=\"_blank\">Bot Management and Super Bot Fight Mode<\/a> are handy to skim before you flip big switches. Start gently; then dial up as you learn.<\/p>\n<h3><span id=\"Reputation_signals_are_helpful_not_divine\">Reputation signals are helpful, not divine<\/span><\/h3>\n<p>Cloudflare\u2019s <strong>cf.threat_score<\/strong> is useful for nudging suspicious traffic into challenges, but it\u2019s not a judge and jury. Pair it with path-based logic. For public blog posts, I don\u2019t care. For login and xmlrpc, I care a lot.<\/p>\n<h2 id=\"section-8\"><span id=\"Step-by-Step_A_Friendly_Setup_You_Can_Copy\">Step-by-Step: A Friendly Setup You Can Copy<\/span><\/h2>\n<h3><span id=\"1_Turn_on_Cloudflare_managed_WAF_rules\">1) Turn on Cloudflare managed WAF rules<\/span><\/h3>\n<p>Enable the managed rulesets designed for common CMS patterns and WordPress. Leave default actions as-is at first. This gets you baseline protection with minimal fuss. Cloudflare\u2019s <a href=\"https:\/\/developers.cloudflare.com\/waf\/managed-rulesets\/\" rel=\"nofollow noopener\" target=\"_blank\">managed rulesets overview<\/a> is worth a bookmark when you\u2019re curious about what\u2019s under the hood.<\/p>\n<h3><span id=\"2_Add_your_custom_WordPress_rules\">2) Add your custom WordPress rules<\/span><\/h3>\n<p>Start with three high-impact rules:<\/p>\n<p>Rule A: Managed Challenge for POST to <strong>\/wp-login.php<\/strong>, excluding verified bots and optionally exempting your office IPs.<\/p>\n<p>Rule B: Block <strong>\/xmlrpc.php<\/strong> outright. If you truly need it, challenge and rate limit instead.<\/p>\n<p>Rule C: Restrict <strong>\/wp-admin<\/strong> and <strong>\/wp-login.php<\/strong> to known IPs or require an identity challenge via Cloudflare Access if your team is distributed.<\/p>\n<h3><span id=\"3_Add_rate_limits_where_they_matter\">3) Add rate limits where they matter<\/span><\/h3>\n<p>Start with:<\/p>\n<p>Rate Limit 1: <strong>\/wp-login.php<\/strong> POST, threshold around 10 requests per 60 seconds per IP, Managed Challenge.<\/p>\n<p>Rate Limit 2: <strong>\/xmlrpc.php<\/strong> any method, threshold around 5 per 60 seconds, Block.<\/p>\n<p>Rate Limit 3: <strong>\/wp-json\/<\/strong> for write methods (POST\/PUT\/DELETE), threshold around 30 per 60 seconds, Managed Challenge.<\/p>\n<p>Optional Rate Limit 4: <strong>\/wp-admin\/admin-ajax.php<\/strong> POST, threshold around 120 per 60 seconds, Managed Challenge. Then refine by action parameter once you see what\u2019s noisy.<\/p>\n<h3><span id=\"4_Test_with_real_flows\">4) Test with real flows<\/span><\/h3>\n<p>Log out. Try to log in with a wrong password a couple of times. Check if you\u2019re getting challenged after your threshold. Add a product to the WooCommerce cart, proceed to checkout, and complete a test order. Watch the Firewall Events and Rate Limiting analytics during these actions. If you don\u2019t see any events where you expect them, your expressions might need small corrections.<\/p>\n<h3><span id=\"5_Observe_and_adjust\">5) Observe and adjust<\/span><\/h3>\n<p>For the first week, expect to tweak thresholds slightly. If a rule never triggers, it\u2019s either not needed or it\u2019s too strict to match legitimate patterns. If it triggers constantly, raise the threshold or move from Block to Managed Challenge. Your goal is to get a calm dashboard with clear wins on the abusive patterns.<\/p>\n<h2 id=\"section-9\"><span id=\"Frequently_Overlooked_Details_That_Make_a_Big_Difference\">Frequently Overlooked Details That Make a Big Difference<\/span><\/h2>\n<h3><span id=\"Whitelist_your_own_services\">Whitelist your own services<\/span><\/h3>\n<p>Payment gateways, inventory connectors, and uptime monitors might not be \u201cverified bots,\u201d but you rely on them. If they hit admin-ajax or webhooks frequently, add an allow rule for their IPs or user agents combined with other identifying markers. Do this early so your logs don\u2019t fill up with false positives.<\/p>\n<h3><span id=\"Keep_WordPress_itself_healthy\">Keep WordPress itself healthy<\/span><\/h3>\n<p>No WAF can fix a site that\u2019s already struggling. Update plugins and themes, remove abandoned ones, and ensure login URLs aren\u2019t publicly linked in navigation. If you\u2019re using a security plugin, make sure it\u2019s not fighting Cloudflare or duplicating work in a way that adds friction for real users.<\/p>\n<h3><span id=\"Consider_Turnstile_for_the_login_page\">Consider Turnstile for the login page<\/span><\/h3>\n<p>If you want a human-friendly check without the old-school CAPTCHA pain, Cloudflare Turnstile is worth testing. It plays nicely with Managed Challenges and gives you another layer of defense on the login screen without spamming users with puzzles.<\/p>\n<h3><span id=\"Set_expectations_with_your_team\">Set expectations with your team<\/span><\/h3>\n<p>Tell your editors and support staff, \u201cIf you ever see a challenge page on login, let me know.\u201d It takes the mystery out of the occasional bump and gives you quick feedback if a rule is too strict. I\u2019ve avoided many \u201cthe site is down!\u201d Slack pings just by explaining what a challenge means.<\/p>\n<h2 id=\"section-10\"><span id=\"A_Story_From_the_Trenches_The_Store_That_Stopped_Bleeding\">A Story From the Trenches: The Store That Stopped Bleeding<\/span><\/h2>\n<p>One WooCommerce shop I work with had a weird pattern. Every few hours, CPU would spike and PHP workers would max out for a few minutes, then everything would calm down. Visitors felt the slowdown but analytics didn\u2019t show surges in real traffic. We traced it to a scrape on the REST API where a bot was hammering a couple of product routes and triggering cart actions like clockwork. Nothing glamorous\u2014just relentless.<\/p>\n<p>We added two rules: a rate limit for POST on <strong>\/wp-json\/<\/strong> and a WAF challenge on specific cart-related admin-ajax actions. I kept the actions light at first because I didn\u2019t want to break checkout. Within a day, the spikes vanished. Legit buyers never noticed a thing. The owner messaged me, \u201cI didn\u2019t realize bots could be this quiet and still cause so much pain.\u201d Honestly, that\u2019s the point\u2014bad traffic doesn\u2019t always look like a storm. Sometimes it\u2019s a slow drip, and Cloudflare is the valve you tighten.<\/p>\n<h2 id=\"section-11\"><span id=\"Security_Meets_Performance_The_Quiet_Win\">Security Meets Performance: The Quiet Win<\/span><\/h2>\n<p>Here\u2019s what happens after a few weeks with good WAF and rate limits: your origin metrics get boring in the best way. Fewer PHP spikes. Fewer panic alerts. Checkout remains smooth even during promotions. And yes, your hosting bill might even flatten a bit because you\u2019re simply doing less work for nonsense. It\u2019s not glamorous, but it\u2019s incredibly satisfying.<\/p>\n<p>And if you\u2019re pairing this with caching, it\u2019s a double win. Put the heavy lifting on the edge, reserve PHP for real work, and let the WAF be your steady guardian. If you\u2019re curious how caching policies and bypass rules fit into the bigger picture, I break down the practical details in my guide to <a href=\"https:\/\/www.dchost.com\/blog\/en\/wordpress-icin-cdn-onbellek-kurallari-nasil-kurulur-woocommercede-html-cache-bypass-ve-edge-ayarlariyla-uctan-uca-hiz\/\">CDN caching that just works for WordPress and WooCommerce<\/a>.<\/p>\n<h2 id=\"section-12\"><span id=\"Wrap-Up_Your_Friendly_Checklist_to_Keep_Bots_in_Their_Lane\">Wrap-Up: Your Friendly Checklist to Keep Bots in Their Lane<\/span><\/h2>\n<p>Let\u2019s land this plane. Cloudflare\u2019s WAF and Rate Limiting work best when they feel like a polite, consistent doorman. You\u2019re not picking fights. You\u2019re quietly reducing waste. Start by guarding the obvious doors\u2014<strong>wp-login.php<\/strong>, <strong>xmlrpc.php<\/strong>, and sensitive <strong>REST API<\/strong> and <strong>admin-ajax<\/strong> flows. Use Managed Challenges generously and Blocks sparingly at first. Let verified bots pass, and give your own services a hall pass. Then watch the logs and nudge thresholds until things hum.<\/p>\n<p>If you\u2019ve been living with random slowdowns, suspicious login spikes, or API drips that never seem to end, this setup can make your site feel calm again. Couple it with clean plugin hygiene and smart caching, and you\u2019ll wonder why you waited so long. Hope this was helpful! If you try these rules and get stuck, shoot me a note on what endpoint is giving you trouble\u2014chances are we\u2019ve all wrestled with it at some point. See you in the next post, where we\u2019ll dig into another small tweak that makes a big difference.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>So there I was, sipping a too-strong coffee, watching a client\u2019s WordPress site crawl even though we had scaled the server more than enough. CPU looked fine. Database was healthy. No plugin gone rogue. But something felt off\u2014those sneaky little spikes that make your graph look like a heartbeat monitor were exactly the kind that [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1368,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-1367","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-teknoloji"],"_links":{"self":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/1367","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/comments?post=1367"}],"version-history":[{"count":0,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/1367\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/1368"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=1367"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=1367"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=1367"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}