The WAF AWS created for you blocked your editors: CloudFront's 300 req/5min default
Published on June 20, 2026

The symptom: editors stuck mid-work
Around 9pm, the editors of a WordPress portal started getting errors saving posts and uploading images. It wasn't "the site is down" — the public site responded fine. It was the admin: admin-ajax.php failing, media upload erroring out, the block editor freezing mid-edit.
CloudFront's WAF was blocking the editors themselves. Worse: it was a WAF nobody had consciously configured.
The silent default: the WebACL AWS creates for you
When you enable protection on a CloudFront distribution from the console, AWS can auto-attach a WebACL with a default rate-limit rule: 300 requests per IP every 5 minutes, with no scope-down. The name gives away its origin — something like AWS-RateBasedRule-IP-300-CreatedByCloudFront.
300 req / 5 min sounds generous until you remember how wp-admin behaves. WordPress heartbeat hits admin-ajax.php every 15–60 seconds. Each edit screen loads 20–40 assets. Two or three editors in the same office leave through the same public IP (NAT). Within minutes that shared IP blows past 300 requests — and the WAF, which can't tell an editor from an attacker, knocks them all down.
Diagnosis: read the WAF logs, don't guess
The first step was to confirm who was being blocked and by which rule. With the WebACL logs going to CloudWatch, Logs Insights answers in seconds:
fields @timestamp, httpRequest.clientIp, terminatingRuleId, httpRequest.uri
| filter action = "BLOCK"
| stats count(*) as blocks by terminatingRuleId
| sort blocks descThe result was unambiguous: 1,408 blocks in the window, 1,284 from the AWS-RateBasedRule-IP-300-CreatedByCloudFront rule. Breaking it down by IP and URI, the blocked ones were real editors — one alone with 111 blocks on admin-ajax.php and media-new.php, plus five Brazilian residential IPs editing and uploading media. The other 124 blocks? Those were legitimate to block: a scanner from a foreign cloud range making 934 requests hunting the ALFA webshell, and an xmlrpc.php attack from dozens of distributed IPs.
The rate limit was right to exist. It was wrong to treat a logged-in editor the same as an anonymous scanner.
The trap of the obvious fixes
Two tempting ways out, both wrong:
Raise the limit (300 → 2,000): weakens DDoS and brute-force protection for everyone, and a large office behind NAT can still blow past it. You trade one problem for another.
Switch the action to Count: effectively disables protection — the WAF only counts, no longer blocks. It cures the symptom by killing the function.
The right question isn't "what's the ideal limit?". It's "should this limit apply to authenticated users at all?". It shouldn't.
The fix: scope-down exempting whoever holds the login cookie
WAFv2 allows a ScopeDownStatement inside the RateBasedStatement: the rate-limit rule only counts requests that match the scope-down. By wrapping it in a NotStatement, we invert it: count everyone EXCEPT those sending the wordpress_logged_in_ cookie. Authenticated users no longer count toward the limit; anonymous traffic stays protected by 300 req/5min.
{
"Name": "RateLimit-300-anon-only",
"Priority": 0,
"Action": { "Block": {} },
"Statement": {
"RateBasedStatement": {
"Limit": 300,
"AggregateKeyType": "IP",
"ScopeDownStatement": {
"NotStatement": {
"Statement": {
"ByteMatchStatement": {
"SearchString": "wordpress_logged_in_",
"FieldToMatch": {
"Cookies": {
"MatchPattern": { "All": {} },
"MatchScope": "VALUE",
"OversizeHandling": "MATCH"
}
},
"PositionalConstraint": "CONTAINS",
"TextTransformations": [{ "Priority": 0, "Type": "NONE" }]
}
}
}
}
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "RateLimit300AnonOnly"
}
}A time-costing detail: when sending this rule via aws wafv2 ... --cli-input-json, the SearchString must be base64 (d29yZHByZXNzX2xvZ2dlZF9pbl8= for wordpress_logged_in_). In the console you type plain text; via the CLI with JSON, you don't.
The second finding: one distribution had no WAF logging at all
When replicating the fix to the second distribution, the query command failed with WAFNonexistentItemException: that WebACL had never had logging configured. Meaning: it was blocking blind, with no record of whom. Before any rule, we turned logging on:
aws wafv2 put-logging-configuration \
--logging-configuration \
ResourceArn=arn:aws:wafv2:us-east-1:123456789012:global/webacl/EXAMPLE/EXAMPLEID,\
LogDestinationConfigs=arn:aws:logs:us-east-1:123456789012:log-group:aws-waf-logs-cfRule of thumb: a WAF without logs is a WAF you can't operate. You don't know what it blocks, you can't tell a false positive from an attack, and you discover both the most expensive way — through the client complaining.
Result
Rate-limit blocks (logged) : 1,284 -> 0 (after 24h, on both distributions)
Editors stuck in wp-admin : yes -> no
Protection against anonymous (300/5m): on -> on (unchanged)
WAF logging on 2nd distribution : absent -> enabledWithin 24 hours, zero legitimate blocks on both distributions, and protection against anonymous traffic stayed up. The webshell scanner and the xmlrpc attack kept being blocked — now only them.
Lessons
1. CloudFront's default is hostile to logged-in WordPress. The auto-created WebACL with 300 req/5min and no scope-down treats editors as attackers. If you enabled protection from the console, go check whether that rule exists.
2. The rate-limit fix is the scope-down, not the number. Exempt authenticated traffic (wordpress_logged_in_ cookie) via NotStatement and keep the limit tight for anonymous.
3. A WAF without logging is inoperable. Enable put-logging-configuration before trusting any rule. Without logs, you can't tell an editor from a scanner.
4. Office NAT fools per-IP rate limiting. Several editors leave through the same public IP; 300 req/5min vanishes fast. Per-IP AggregateKeyType has to account for that.
Another case of a rate limit punishing the legitimate user (on the ALB, revealed by Gutenberg) is in 363,000 false 429s in a day — the trigger there was different, but the lesson repeats: a rate limit without context about who the client is becomes a foot-gun.
Conclusion
Protection the cloud turns on by itself isn't configured protection — it's a default that may be tuned for the average case, not yours. An edge WAF needs to know the difference between an anonymous IP hammering xmlrpc.php and a logged-in editor saving a draft. CloudFront doesn't know by default; you teach it, with a single rule's scope-down.