Stopping a Coordinated Bot Attack on a WP Engine Site
At around 2am on a Tuesday, UptimeRobot fired an alert for a client site I manage on WP Engine. Response times had spiked past 8 seconds. By the time I was looking at the dashboard, the site was effectively down — not from a conventional DDoS, but from something more targeted and harder to diagnose: a coordinated cache-busting attack.
Here’s how I diagnosed it, what I built to stop it, and what you should know before you’re dealing with this at 2am yourself.
What Cache-Busting Looks Like in the Logs
The first sign wasn’t volume — it was pattern. When I pulled the WP Engine access logs, I was seeing requests like these hitting the same pages in rapid succession:
# Same page, hundreds of unique query strings GET /products/?ref=a1b2c3&session=x9y8z7 GET /products/?ref=d4e5f6&session=m3n2p1 GET /products/?utm_rand=8472938&nocache=1 GET /products/?v=20250412083721&bust=true
None of these parameters had functional meaning — they existed purely to generate unique cache keys. WP Engine’s edge cache treats each unique URL as a separate cacheable resource, so every request was a cold miss hitting the PHP origin. With hundreds of bots rotating IPs and user agents, the origin was handling full PHP + database requests for what should have been cached, static responses.
The UptimeRobot incident history showed the pattern clearly: synthetic downtime windows of 4–7 minutes every 20–30 minutes, rather than continuous. The bots were pacing themselves — likely to avoid triggering rate limits while maximizing cache pollution.
Why This Is Harder Than a Standard DDoS
A volume-based DDoS is relatively straightforward to mitigate at the network layer. Cache-busting attacks are more insidious because the individual requests can look legitimate — real user agents, reasonable request rates per IP, no obviously malicious payload. Standard rate limiting set too conservatively will miss it entirely. Set too aggressively and you start blocking real users.
The attack also has a compounding effect: once the cache is polluted, legitimate traffic that would normally serve from edge cache is also forced to origin, which amplifies the load beyond just the bot traffic itself.
The Cloudflare WAF Response
WP Engine sits behind Cloudflare at the infrastructure level, which gives you access to Cloudflare’s WAF even on the free tier. The fix required two custom rules working together.
Rule 1 — Strip Cache-Busting Parameters
The first rule targeted the query string patterns directly. Rather than blocking the requests outright (which risks false positives on legitimate UTM parameters), I used a Cloudflare Transform Rule to normalize the URL before it reached the cache layer:
# Cloudflare Transform Rule — Query String Normalization
# Field: URI Query String
# Expression matches random/high-entropy parameter patterns
(http.request.uri.query matches "[a-z]{2,6}=[a-f0-9]{8,}")
or (http.request.uri.query matches "nocache|bust|rand|session=[^&]{6,}")
The transform action rewrites the request to remove the matching parameters before the cache lookup happens. Legitimate UTM parameters (utm_source, utm_medium, utm_campaign) are whitelisted explicitly and pass through unchanged.
Rule 2 — Rate Limit by Normalized URI
The second rule rate-limits requests after normalization, so bots can’t just rotate parameters to evade the first rule. At this point, repeated hits to the same page from diverse IPs get flagged, and the threshold is set against normalized paths rather than raw URLs:
# Cloudflare Rate Limiting Rule Action: Managed Challenge (not block — preserves real user fallback) Threshold: 50 requests per minute per IP Matching: URI Path (post-normalization) Scope: /products/, /catalog/, high-traffic archive pages only
Use Managed Challenge instead of Block: Block returns a hard 403 and has no fallback for legitimate traffic that trips the threshold by coincidence. Managed Challenge presents a browser integrity check — real users pass it once and proceed transparently. Bots fail it and get dropped.
WP Engine Escalation
While the Cloudflare rules were deploying, I opened a support ticket with WP Engine to get their security team aware of the incident. A few things worth knowing about the WP Engine side of this:
- WP Engine’s CDN cache doesn’t have a query string normalization layer by default — every unique query string is a unique cache key. You can request a custom cache configuration to treat certain parameters as cache-ignorable, but this isn’t self-serve.
- Their Global Edge Security add-on adds another Cloudflare layer, but if you’re already proxied through your own Cloudflare account, you can end up with double-proxying issues. Confirm your DNS setup before escalating.
- Cache purge doesn’t help during an active attack — purging the cache while bots are actively busting it just gives them more origin hits. Stop the bots first, then purge.
What Resolved It
Within 15 minutes of the Cloudflare rules going live, origin request volume dropped by roughly 80%. The query string normalization rule was the decisive intervention — stripping the randomized parameters meant the vast majority of bot requests were now hitting cached responses at the edge, never reaching PHP.
The remaining 20% were caught by the rate limit rule over the following hour as the bots cycled through their IP pool. By 4am the site was responding normally with no further UptimeRobot alerts.
Caveats
- Query string normalization rules require careful whitelisting — if your site uses meaningful query parameters for dynamic content (search, filters, pagination), you need to audit these before deploying
- Cloudflare’s free tier WAF has rule limits — if you’re already close to the limit on custom rules, you may need to consolidate
- This approach stops cache-busting specifically. A pure volume attack with no cache exploitation requires different tooling (Cloudflare’s Bot Fight Mode or their paid Bot Management tier)
- After mitigation, review your cache exclusion rules in WP Engine — any page excluded from cache is always an origin hit and a softer target
The real lesson: cache-busting attacks are designed to look like legitimate traffic spikes. If you’re seeing high response times with normal-looking request patterns, pull the raw access logs and look at query string entropy before assuming it’s a volume problem.