{"id":1379,"date":"2025-11-05T22:24:34","date_gmt":"2025-11-05T19:24:34","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/the-layered-shield-i-trust-waf-and-bot-protection-with-cloudflare-modsecurity-and-fail2ban\/"},"modified":"2025-11-05T22:24:34","modified_gmt":"2025-11-05T19:24:34","slug":"the-layered-shield-i-trust-waf-and-bot-protection-with-cloudflare-modsecurity-and-fail2ban","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/the-layered-shield-i-trust-waf-and-bot-protection-with-cloudflare-modsecurity-and-fail2ban\/","title":{"rendered":"The Layered Shield I Trust: WAF and Bot Protection with Cloudflare, ModSecurity, and Fail2ban"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><p>So there I was, sipping a lukewarm coffee after midnight, watching a modest WordPress site chew through CPU like it had a grudge. Traffic looked normal, but the logs told a different story: login probes like metronomes, empty user agents by the hundreds, and the occasional script kiddie trying their luck with XML\u2011RPC. I\u2019d seen this movie before, and the ending is never good if you face it with a single tool. That night reminded me of a simple truth I\u2019ve carried from one server room to the next: you don\u2019t beat bots with one weapon. You win with a layered shield.<\/p>\n<p>If your site lives on the public internet, you\u2019ve probably felt that creeping discomfort\u2014pages that should be fast suddenly hesitate, cron jobs stall, PHP-FPM workers pile up, your cache hit rate slips for no reason. And then the support emails start. Ever had that moment when everything looks fine in the dashboard but your gut says something\u2019s off? That\u2019s usually the bot noise getting louder.<\/p>\n<p>In this post, I\u2019ll walk you through the way I actually stack defenses in real life\u2014starting at the edge with Cloudflare, filtering where it counts with ModSecurity (and the OWASP Core Rule Set), and finishing with Fail2ban quietly reading the logs and escorting troublemakers out. No silver bullets here. Just a practical, layered playbook that feels almost boring when it\u2019s working. Which is the dream, right?<\/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=\"#The_Day_the_Bots_Got_Loud_Why_Layers_Win\"><span class=\"toc_number toc_depth_1\">1<\/span> The Day the Bots Got Loud: Why Layers Win<\/a><\/li><li><a href=\"#Meet_the_Trio_Cloudflare_ModSecurity_Fail2ban\"><span class=\"toc_number toc_depth_1\">2<\/span> Meet the Trio: Cloudflare, ModSecurity, Fail2ban<\/a><\/li><li><a href=\"#Start_at_the_Edge_with_Cloudflare\"><span class=\"toc_number toc_depth_1\">3<\/span> Start at the Edge with Cloudflare<\/a><\/li><li><a href=\"#Make_Your_Origin_Tell_the_Truth_Real_IPs_Clean_Logs\"><span class=\"toc_number toc_depth_1\">4<\/span> Make Your Origin Tell the Truth (Real IPs, Clean Logs)<\/a><\/li><li><a href=\"#Turn_on_ModSecurity_with_CRS_Without_Breaking_Stuff\"><span class=\"toc_number toc_depth_1\">5<\/span> Turn on ModSecurity (with CRS) Without Breaking Stuff<\/a><\/li><li><a href=\"#Fail2ban_Your_Calm_Log-Powered_Gatekeeper\"><span class=\"toc_number toc_depth_1\">6<\/span> Fail2ban: Your Calm, Log-Powered Gatekeeper<\/a><\/li><li><a href=\"#Orchestration_Policies_Exceptions_and_Testing\"><span class=\"toc_number toc_depth_1\">7<\/span> Orchestration: Policies, Exceptions, and Testing<\/a><\/li><li><a href=\"#A_Few_Practical_Config_Touches_I_Keep_Reusing\"><span class=\"toc_number toc_depth_1\">8<\/span> A Few Practical Config Touches I Keep Reusing<\/a><\/li><li><a href=\"#Common_Gotchas_And_How_I_Dodge_Them\"><span class=\"toc_number toc_depth_1\">9<\/span> Common Gotchas (And How I Dodge Them)<\/a><\/li><li><a href=\"#When_You_Want_to_Push_Further\"><span class=\"toc_number toc_depth_1\">10<\/span> When You Want to Push Further<\/a><\/li><li><a href=\"#WrapUp_A_Calm_Site_Is_a_Fast_Site\"><span class=\"toc_number toc_depth_1\">11<\/span> Wrap\u2011Up: A Calm Site Is a Fast Site<\/a><ul><li><a href=\"#Resources_I_Mentioned\"><span class=\"toc_number toc_depth_2\">11.1<\/span> Resources I Mentioned<\/a><\/li><\/ul><\/li><\/ul><\/div>\n<h2 id=\"section-1\"><span id=\"The_Day_the_Bots_Got_Loud_Why_Layers_Win\">The Day the Bots Got Loud: Why Layers Win<\/span><\/h2>\n<p>I remember a client who ran a cozy WooCommerce shop. Great products, happy customers\u2014until one day the login page started getting hammered. Not a DDoS to brag about on the news, just annoying, relentless noise. The kind that slips past naive rate limits and turns into death by a thousand paper cuts. We tried one quick fix after another: a captcha here, a user-agent block there. It was whack\u2011a\u2011mole.<\/p>\n<p>Here\u2019s the thing: bots show up at different stages. Some never touch your origin because an edge network slaps them back. Others make it to your web server and misbehave just enough to sneak under the radar. And some wear decent disguises and only reveal their intentions when you watch how they move. That\u2019s why a layered approach works. You let the edge provider swat the obvious pests, your application firewall sift the gray area, and your log\u2011driven bouncer (hello, Fail2ban) quietly remove anything that still acts like a jerk.<\/p>\n<p>Think of it like a club. Cloudflare is the doorman who spots fake IDs before they reach the rope. ModSecurity is the security team inside, trained to notice sketchy behavior pattern\u2011by\u2011pattern. And Fail2ban is the manager watching the cameras who, when he sees repeat nonsense, adds a face to the do\u2011not\u2011enter list. When those three work together, the dance floor stays fun and your servers don\u2019t sweat.<\/p>\n<h2 id=\"section-2\"><span id=\"Meet_the_Trio_Cloudflare_ModSecurity_Fail2ban\">Meet the Trio: Cloudflare, ModSecurity, Fail2ban<\/span><\/h2>\n<p>Let\u2019s map each role without getting stuck in buzzwords.<\/p>\n<p>Cloudflare sits in front of everything. It blocks obvious scans, rate\u2011limits repetitive hits, and applies managed rules before packets even sniff your origin. This is where you stop the easy stuff. Sometimes you\u2019ll turn up the heat for a day during an attack, and then dial back to normal so real users glide through.<\/p>\n<p>ModSecurity lives on your web server\u2014Apache, Nginx with ModSecurity v3, or a proxy layer. With the OWASP Core Rule Set (CRS), it looks at requests like a seasoned detective: parameters that don\u2019t belong, weird encodings, suspicious payloads. It\u2019s not magic; you\u2019ll tune it. But once you fit it to your app, it\u2019s the watchdog that doesn\u2019t sleep.<\/p>\n<p>Fail2ban is your quiet enforcer. It doesn\u2019t guess. It reads logs\u2014Nginx access logs, ModSecurity audit logs, auth logs\u2014and when it sees patterns you define, it bans IPs for a while. Think of it as a \u201ccooling\u2011off\u201d timer for misbehaving clients. You can make it talk to your firewall or even to Cloudflare\u2019s API to block at the edge.<\/p>\n<p>Each layer is strong, but together they\u2019re forgiving. If something slips by one, another catches it. If a legitimate integration trips a wire, you soften that specific rule while keeping the rest. That balance makes the difference between a secure site and a cranky one.<\/p>\n<h2 id=\"section-3\"><span id=\"Start_at_the_Edge_with_Cloudflare\">Start at the Edge with Cloudflare<\/span><\/h2>\n<p>When I onboard a site, I start at the edge because that\u2019s where you can block the most junk with the least pain. Turn on the orange cloud for your DNS records, enable the WAF, and set a reasonable Security Level. If you\u2019re on a plan that supports it, use managed rulesets and add a couple of light custom rules for the obvious paths bots love (login forms, XML\u2011RPC, search endpoints that can be abused). The trick is to be surgical\u2014challenge suspicious traffic, don\u2019t block half the internet.<\/p>\n<p>I especially like using rate limiting for POST requests to login endpoints and XML\u2011RPC. Keep thresholds sane so real users aren\u2019t punished. And when traffic gets weird, temporarily tighten the screws: higher sensitivity, more aggressive rate limits, or a targeted challenge for a region where the noise originates. When things settle, loosen up again. It feels manual at first, but over time you\u2019ll know your patterns.<\/p>\n<p>If you want a friendly, step\u2011by\u2011step approach that I use specifically for WordPress bots, I wrote about it here: <a href=\"https:\/\/www.dchost.com\/blog\/en\/cloudflare-waf-kurallari-ve-oran-sinirlama-ile-wordpressi-botlardan-nasil-korursun\/\">the playbook I use for Cloudflare WAF and rate limiting on WordPress<\/a>. Even if you\u2019re not on WordPress, the logic is the same: protect the chokepoints and never let login or XML\u2011RPC be an unmetered ramp into your CPU.<\/p>\n<p>One more tip that has saved me a lot of confusion: keep your challenge actions consistent. If you challenge login attempts from high\u2011risk ASNs for a day, remember to roll it back when the storm passes. Drift in security settings is like residue\u2014it accumulates and eventually blocks someone who calls you angry from a coffee shop Wi\u2011Fi. That\u2019s not a great Tuesday.<\/p>\n<p>If you haven\u2019t set up real visitor IP restoration on your origin (we\u2019ll do that next), bookmark this for later: <a href=\"https:\/\/developers.cloudflare.com\/support\/troubleshooting\/restoring-visitor-ips\/restoring-original-visitor-ips\/\" rel=\"nofollow noopener\" target=\"_blank\">Cloudflare\u2019s guide to restoring the original visitor IP<\/a>. Your logs are only as good as the IPs inside them, and without this, Fail2ban and ModSecurity analytics won\u2019t make much sense.<\/p>\n<h2 id=\"section-4\"><span id=\"Make_Your_Origin_Tell_the_Truth_Real_IPs_Clean_Logs\">Make Your Origin Tell the Truth (Real IPs, Clean Logs)<\/span><\/h2>\n<p>Every time I audit a troubled server, I look at the access logs first. If I see Cloudflare IPs everywhere, I know we\u2019ve been flying blind. You can\u2019t ban what you can\u2019t see. So let\u2019s fix that.<\/p>\n<p>On Nginx, load the real IP module and trust Cloudflare\u2019s networks. Cloudflare sends the real client IP in CF-Connecting-IP. After this, your logs will show the actual visitor\u2019s IP in <strong>$remote_addr<\/strong>.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># \/etc\/nginx\/conf.d\/realip.conf\n# Trust Cloudflare proxies (use the full, up-to-date list!)\n# Better: include a file you auto-update weekly.\nset_real_ip_from 103.21.244.0\/22;\nset_real_ip_from 103.22.200.0\/22;\nset_real_ip_from 103.31.4.0\/22;\n# ... (add all Cloudflare ranges)\nreal_ip_header CF-Connecting-IP;\nreal_ip_recursive on;\n\n# Logging with client IP now accurate\nlog_format main_cf '$remote_addr - $remote_user [$time_local] &quot;$request&quot; '\n                   '$status $body_bytes_sent &quot;$http_referer&quot; '\n                   '&quot;$http_user_agent&quot; rt=$request_time';\naccess_log \/var\/log\/nginx\/access.log main_cf;\n<\/code><\/pre>\n<p>If you\u2019re on Apache, use mod_remoteip and make sure your LogFormat uses %a (client IP after remoteip) instead of %h. The key is consistency: once you restore the real IP, keep it that way everywhere\u2014access logs, audit logs, and any metrics pipeline.<\/p>\n<p>Clean logs help you spot patterns fast. I like adding <strong>request_time<\/strong> to Nginx logs and enabling the user agent. A spike in 401s from a single subnet, an unusual sprint across many 404s, or slow responses only on POSTs\u2014these patterns become obvious. They also feed directly into Fail2ban, which we\u2019ll wire up in a minute.<\/p>\n<h2 id=\"section-5\"><span id=\"Turn_on_ModSecurity_with_CRS_Without_Breaking_Stuff\">Turn on ModSecurity (with CRS) Without Breaking Stuff<\/span><\/h2>\n<p>ModSecurity gets a bad reputation for false positives, but in my experience it\u2019s almost always about how it\u2019s introduced. If you flip it to \u201cfull block\u201d mode on day one with a sensitive app, of course you\u2019ll break things. The secret is to start in DetectionOnly, watch, exclude what\u2019s legit, and then enforce gradually.<\/p>\n<p>First, install ModSecurity and the OWASP Core Rule Set. The CRS folks do a great job evolving rules to catch modern tricks. It\u2019s worth the time to read their docs and understand paranoia levels and anomaly scoring. Here\u2019s their home: <a href=\"https:\/\/coreruleset.org\/\" rel=\"nofollow noopener\" target=\"_blank\">OWASP Core Rule Set<\/a>.<\/p>\n<p>When I turn CRS on, I start with a moderate paranoia level. Then I run the site like normal\u2014login, checkout, account updates, API calls, webhooks. Anything legit that triggers a rule gets a targeted exclusion. Over time, your ruleset learns the shape of your app without opening big holes.<\/p>\n<p>Here\u2019s a tiny example of the kind of custom rule that stops obvious nonsense without touching real users:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># Block empty user agents (common in low-effort scans)\nSecRule REQUEST_HEADERS:User-Agent &quot;^$&quot; \n  &quot;id:1000001,phase:1,deny,status:403,log,msg:'Empty User-Agent blocked'&quot;\n\n# Challenge noisy logins from known bad agents hitting wp-login\nSecRule REQUEST_URI &quot;@endsWith \/wp-login.php&quot; \n  &quot;id:1000002,phase:1,chain,deny,status:403,log,msg:'Suspicious login agent'&quot;\n  SecRule REQUEST_HEADERS:User-Agent &quot;(?i)curl|python|wp-scan|wpscan|libwww-perl&quot;\n<\/code><\/pre>\n<p>And here\u2019s how I exclude a legitimate webhook or payment callback that trips CRS. Let\u2019s say your payment provider hits \/wc-api\/ with structured data CRS doesn\u2019t love. Rather than turning off a whole rule set, <strong>narrow<\/strong> the exclusion:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># Example: relax rule 949110 only for WooCommerce API callbacks\nSecRuleUpdateTargetById 949110 &quot;!ARGS:*\/wc-api\/*&quot;\n<\/code><\/pre>\n<p>Notice the tone: add small, precise exceptions. If you start globally disabling rules, you won\u2019t have a WAF; you\u2019ll have a sticker.<\/p>\n<p>Performance matters too. ModSecurity inspects requests, so don\u2019t run it on endpoints that never need it, like static assets. Put the module only on dynamic vhosts or exclude common static locations. Also keep the audit log sane\u2014log what you need, not every byte of every request, or you\u2019ll fill disks for sport.<\/p>\n<p>Once you\u2019ve tuned false positives down, flip to blocking mode. You\u2019ll feel the difference on those late nights when a bot net tries sqli-lite payloads across your search forms. Instead of watching CPU spikes, you\u2019ll watch clean 403s roll in, your cache stays hot, and customers keep checking out without a hiccup.<\/p>\n<h2 id=\"section-6\"><span id=\"Fail2ban_Your_Calm_Log-Powered_Gatekeeper\">Fail2ban: Your Calm, Log-Powered Gatekeeper<\/span><\/h2>\n<p>If Cloudflare is the front gate and ModSecurity is your floor security, Fail2ban is the night manager reading the journals. It doesn\u2019t care about the hype; it cares about patterns. The moment you restore real client IPs in your logs, Fail2ban becomes incredibly effective.<\/p>\n<p>You can build jails for all kinds of behaviors: too many 401s on wp-login, scraping 404s across non-existent admin panels, hammering XML\u2011RPC with repeated POSTs, and even ModSecurity rule hits that imply malice. The ban can be local (iptables\/nftables) or upstream (Cloudflare firewall rule via API). If you\u2019re origin\u2011shielded behind Cloudflare, I like pushing bans to Cloudflare so the origin never sees the traffic again.<\/p>\n<p>Let\u2019s sketch a simple Nginx\u2011based jail that cools off aggressive login attempts. We\u2019ll assume your access log is truthful and uses a format with $remote_addr and $status.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># \/etc\/fail2ban\/filter.d\/nginx-wplogin.conf\n[Definition]\nfailregex = ^&lt;HOST&gt; - - [.*] &quot;POST \/wp-login.php HTTP\/1.[01]&quot; 40[13] .*\n            ^&lt;HOST&gt; - - [.*] &quot;POST \/xmlrpc.php HTTP\/1.[01]&quot; 40[13] .*\n\n# \/etc\/fail2ban\/jail.d\/nginx-wplogin.local\n[nginx-wplogin]\nenabled  = true\nport     = http,https\nlogpath  = \/var\/log\/nginx\/access.log\nmaxretry = 6\nfindtime = 600\nbantime  = 3600\naction   = iptables-multiport[name=nginx-wplogin, port=&quot;http,https&quot;]\n# Or: action = cloudflare[cfuser=..., cftoken=..., zone=...] (custom action)\n<\/code><\/pre>\n<p>That\u2019s gentle but effective. If someone fails six times in ten minutes on your login endpoints, they sit out for an hour. Legit users rarely trigger it; bots do it all the time. And yes, you can tailor responses based on status codes\u2014401 is typical for auth failures, 403 for WAF blocks.<\/p>\n<p>My favorite move is to let ModSecurity tell Fail2ban when someone broke a high\u2011confidence rule. You parse the ModSecurity audit log for specific rule IDs or messages and ban those IPs quickly. It\u2019s like giving your night manager a direct hotline to the security team.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># \/etc\/fail2ban\/filter.d\/modsec-highrisk.conf\n[Definition]\nfailregex = ^.* [id &quot;(?:942100|949110|930120)&quot;.*$  # example high-risk CRS IDs\n\n# \/etc\/fail2ban\/jail.d\/modsec-highrisk.local\n[modsec-highrisk]\nenabled  = true\nlogpath  = \/var\/log\/modsec_audit.log\nmaxretry = 1\nfindtime = 3600\nbantime  = 86400\naction   = iptables-allports[name=modsec-highrisk]\n<\/code><\/pre>\n<p>Adjust IDs to match your comfort level. The point is, if someone triggers a rule that is almost always malicious, you don\u2019t need to be polite.<\/p>\n<p>Fail2ban has great documentation if you want to go deeper on crafting filters: <a href=\"https:\/\/fail2ban.readthedocs.io\/en\/latest\/\" rel=\"nofollow noopener\" target=\"_blank\">Fail2ban documentation<\/a>. My advice: keep jails focused, don\u2019t let them overlap too much, and review your ban list occasionally. I also like short bantimes for noisy scrapers and longer ones for high\u2011risk behavior. It keeps the net flexible.<\/p>\n<h2 id=\"section-7\"><span id=\"Orchestration_Policies_Exceptions_and_Testing\">Orchestration: Policies, Exceptions, and Testing<\/span><\/h2>\n<p>So how do you stitch this together without weeks of tinkering? I follow a simple choreography.<\/p>\n<p>First, begin at the edge. Turn on Cloudflare WAF with sensible defaults, enable rate limits on your hot endpoints, and set a reasonable Security Level. Watch traffic for a day or two. If something feels off, check the firewall events and adjust. Never forget: real users first. If your checkout is global, avoid blunt region restrictions unless you\u2019re under pressure.<\/p>\n<p>Second, make logs honest. Restore real client IPs, standardize formats, and add request time to your access logs. That one tweak makes everything downstream smarter. This step is like putting on your glasses.<\/p>\n<p>Third, introduce ModSecurity in DetectionOnly mode. Let it watch, then carve out precise exceptions for trusted flows\u2014payment gateways, admin AJAX that\u2019s chatty, legitimate webhooks. As your false positives shrink, move to enforcement gradually. You\u2019ll be tempted to disable entire rule groups. Resist. Small exclusions win the long game.<\/p>\n<p>Fourth, empower Fail2ban. Start with one or two high\u2011value jails: login abuse and XML\u2011RPC. If you\u2019re feeling brave, add a ModSecurity\u2011driven jail for high\u2011confidence rule hits. Decide where bans live\u2014local firewall or Cloudflare. If you\u2019re often behind Cloudflare, consider publishing bans at the edge so the origin stays quiet.<\/p>\n<p>Fifth, test like a curious attacker. I\u2019ll sometimes point a harmless script and see how quickly the layers react. Try bad passwords, rapid requests, odd user agents. Watch your logs and ensure your measures are triggering where you expect. If you can\u2019t see it in logs, it didn\u2019t happen (as far as automation is concerned).<\/p>\n<p>There\u2019s also a human side to this: write down what you changed, even informally. Security settings drift. You don\u2019t want to be the person who forgot a temporary challenge that quietly suppressed conversions for a region. A tiny changelog in your repo or wiki is enough.<\/p>\n<p>And remember those integrations. If you run WordPress, many plugins call external APIs and some vendors call back to your site. Those webhooks can look odd to a strict WAF. The goal isn\u2019t to weaken the whole system; it\u2019s to create a small, protected path for known, trusted partners. Whitelist by IP when an integration vendor provides ranges, or add a very narrow exception around the path they use. Don\u2019t turn off your house alarm because a friend might visit\u2014give the friend a key.<\/p>\n<p>Curious about the broader picture of how bot risk fits into today\u2019s threat landscape? It\u2019s a rabbit hole, but one worth understanding as you tune these defenses. You\u2019ll notice patterns\u2014spikes often tie to credential stuffing elsewhere, scanners get seasonal, and some botnets are persistent yet clumsy. When your layers are tuned, most of that becomes background noise you hardly notice.<\/p>\n<h2 id=\"section-8\"><span id=\"A_Few_Practical_Config_Touches_I_Keep_Reusing\">A Few Practical Config Touches I Keep Reusing<\/span><\/h2>\n<p>Over the years, I\u2019ve settled on a handful of tweaks that save me time when the pressure is on.<\/p>\n<p>For Cloudflare: keep a small set of custom firewall rules ready\u2014things like challenging known bad ASNs that routinely abuse your login, or placing rate limits on endpoints that cost you CPU. You don\u2019t need a hundred rules; you need a few that you trust. And when you learn something new, document it as a \u201cplay\u201d you can reuse next time. That\u2019s how your setup matures without turning into a maze.<\/p>\n<p>For ModSecurity: tag your custom rules and use consistent IDs. I keep a block of ID space for house rules (say, 1000000\u20131009999) and add a short message style like \u201cWP Login Naughtiness.\u201d It makes triage easier when you\u2019re ankle\u2011deep in logs. Also, consider anomaly scoring rather than instant block on single hits\u2014some payloads are noisy but not dangerous alone. Scoring builds context.<\/p>\n<p>For Fail2ban: add a \u201ccooling off\u201d jail that watches 404s on sensitive paths (random \/wp-admin\/includes\/ etc.) and bans when it sees a burst. It\u2019s amazing how many scanners wander around trying doors that don\u2019t exist. Keep the ban short\u2014ten to thirty minutes is usually enough to make them move on. Also, if you\u2019re IPv6\u2011heavy, make sure your action supports it, or route bans via Cloudflare so you don\u2019t get stuck with v6 gaps.<\/p>\n<p>On visibility: even a lightweight dashboard that counts 403s by endpoint and bans by jail goes a long way. When a client calls about a \u201cslow morning,\u201d you want to glance and say, \u201cWe challenged 19k login probes at the edge, blocked 600 payload anomalies with ModSecurity, and Fail2ban cooled off 140 IPs scraping your admin. Real customers were never touched.\u201d That\u2019s a much better conversation than guessing.<\/p>\n<h2 id=\"section-9\"><span id=\"Common_Gotchas_And_How_I_Dodge_Them\">Common Gotchas (And How I Dodge Them)<\/span><\/h2>\n<p>Every setup has its \u201coh right, that\u201d moments. These are the ones I see most.<\/p>\n<p>First, forgetting real IP restoration. Without it, Fail2ban bans your reverse proxy (or worse, itself), ModSecurity attribution is useless, and your analytics lie. Fix it before doing anything else.<\/p>\n<p>Second, over\u2011eager rules on critical flows. I once watched a checkout crumble because a well\u2011intentioned rule challenged a payment provider\u2019s callback. The fix was simple: a tiny path\u2011based exception. The lesson stuck: test webhooks and payment flows first whenever you tighten controls.<\/p>\n<p>Third, blind rate limits. If you rate limit HTML pages or cart AJAX without care, you\u2019ll throttle real users. Rate limit precisely on state\u2011changing endpoints like POST login or POST XML\u2011RPC. And always monitor for collateral damage when you change thresholds.<\/p>\n<p>Fourth, stale lists. Cloudflare IP ranges change, vendors rotate webhook subnets, your own IP changes when you move offices. Build a habit of updating trusted ranges. A small cron job that refreshes Cloudflare networks into an include file is worth its weight in calm.<\/p>\n<p>Fifth, everything in block mode on day one. I\u2019ve learned to let ModSecurity watch first, then block with confidence. The same goes for Fail2ban: start with longer findtime and shorter bantime, then adjust as you learn your traffic personality.<\/p>\n<h2 id=\"section-10\"><span id=\"When_You_Want_to_Push_Further\">When You Want to Push Further<\/span><\/h2>\n<p>Once this core stack is humming, there\u2019s a lot you can do to sharpen it. Some folks feed ModSecurity alerts into a small SIEM, correlate with Cloudflare firewall events, and auto\u2011promote bans based on confidence. Others run user\u2011behavior detections (number of unique endpoints per minute per IP, that sort of thing) and flag outliers. Even simple heuristics go a long way\u2014humans don\u2019t click 200 pages in 30 seconds.<\/p>\n<p>I also like adding small honeypots: a fake admin URL that no human should ever visit, or a disallowed path that only scanners find. When someone touches it, you know the intent, and you can ban decisively. Just keep honeypots lightweight, and never let them interfere with real navigation.<\/p>\n<p>If you rely heavily on third\u2011party services (marketing tags, A\/B testing, payment gateways), keep a short list of hosts and paths they use. This cheat sheet cuts your tuning time in half when a new rule trips them. The goal is never to weaken your shield; it\u2019s to carve small doors for trusted guests.<\/p>\n<p>And if you want a hands\u2011on reference while you fine\u2011tune rules and thresholds, vendor documentation is your friend. The CRS site has practical guidance on tuning anomaly scores and exclusions, and Fail2ban\u2019s docs read like a good cookbook\u2014small recipes that you can adapt. Cloudflare\u2019s knowledge base is surprisingly readable for quick \u201chow do I\u201d moments too.<\/p>\n<h2 id=\"section-11\"><span id=\"WrapUp_A_Calm_Site_Is_a_Fast_Site\">Wrap\u2011Up: A Calm Site Is a Fast Site<\/span><\/h2>\n<p>Let\u2019s bring it home. The reason I love pairing Cloudflare, ModSecurity, and Fail2ban is simple: they let your site be calm. Edge filtering catches the loud and lazy stuff. ModSecurity watches the details and quietly pushes back when requests look wrong. Fail2ban reads the room from the logs and escorts out anyone who won\u2019t take the hint. You don\u2019t need heroics every day; you need routines that hold up when you\u2019re busy or asleep.<\/p>\n<p>If you\u2019re starting from scratch, do it in this order: fix real IP logging, enable Cloudflare WAF with a couple of guardrails, switch ModSecurity to DetectionOnly and tune the false positives, then turn on Fail2ban for your two or three high\u2011value patterns. Give yourself a few days to watch it settle. When you do make changes, write down what you touched. And when traffic spikes, breathe\u2014you\u2019ve built a layered shield for exactly that moment.<\/p>\n<p>Hope this was helpful. If you want me to write a deeper dive on auto\u2011banning via the Cloudflare API or a practical guide to ModSecurity exclusions for common plugins and webhooks, tell me what you\u2019re running and where it hurts. I\u2019ll bring the coffee\u2014and the boring, reliable configs that let you sleep.<\/p>\n<h3><span id=\"Resources_I_Mentioned\">Resources I Mentioned<\/span><\/h3>\n<p>&#8211; Cloudflare guide to real visitor IPs: <a href=\"https:\/\/developers.cloudflare.com\/support\/troubleshooting\/restoring-visitor-ips\/restoring-original-visitor-ips\/\" rel=\"nofollow noopener\" target=\"_blank\">restore original visitor IP<\/a><br \/>\n&#8211; OWASP Core Rule Set (CRS): <a href=\"https:\/\/coreruleset.org\/\" rel=\"nofollow noopener\" target=\"_blank\">learn and tune CRS effectively<\/a><br \/>\n&#8211; Fail2ban docs: <a href=\"https:\/\/fail2ban.readthedocs.io\/en\/latest\/\" rel=\"nofollow noopener\" target=\"_blank\">craft reliable jails and filters<\/a><\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>So there I was, sipping a lukewarm coffee after midnight, watching a modest WordPress site chew through CPU like it had a grudge. Traffic looked normal, but the logs told a different story: login probes like metronomes, empty user agents by the hundreds, and the occasional script kiddie trying their luck with XML\u2011RPC. I\u2019d seen [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1380,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-1379","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\/1379","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=1379"}],"version-history":[{"count":0,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/1379\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/1380"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=1379"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=1379"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=1379"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}