AWS Builder Center
AWS WAF Best Practices, Part 1: Enterprise Web ACL Design, Custom Rules and Every AWS Managed Rule Group

AWS WAF Best Practices, Part 1: Enterprise Web ACL Design, Custom Rules and Every AWS Managed Rule Group

How to structure AWS WAF for production: web ACL design, how requests are inspected, custom rules with JSON, every AWS managed rule group by its real name, must-have vs good-to-have rules with cost, and the OWASP Top 10:2025 mapping.

Security Engineer, AWS Certified Security Specialty and Solutions Architect


By Dipnarayan Nandi, Cloud Security Engineer
It's 2:40 on a Saturday morning, and the on-call phone won't stop buzzing.
The checkout API is slow. Then it's timing out. CPU on the application servers is pinned at 100%. Nothing was deployed. Traffic looks "normal" in the load balancer graphs, just a lot more of it. By the time someone finds the pattern, a few thousand IPs have spent four hours hammering  /login  with leaked passwords, a scraper has copied the entire product catalogue, and a scanner has tried every path from  /wp-admin  to  /.env .
The worst part? The company had AWS WAF. It was attached to the load balancer. It had the Core rule set ticked. And it was in Count mode, because six months earlier it had blocked a customer's file upload and someone switched it off "for now".
I've seen some version of this story more times than I'd like. So this is the guide I wish those teams had: how to build AWS WAF properly, end to end, the way it needs to run in an enterprise. Not a checkbox, but a set of decisions about your traffic: who should reach your app, from where, with which methods, how fast, and carrying what.
This guide comes in two parts, because doing it properly takes more than one post.
Part 1 (this post): building the rules
  • Why you need a WAF when you already have security groups and Shield
  • The associated resources: what AWS WAF can protect and where the web ACL lives
  • Implementation step by step: the web ACL, a rule group, how AWS WAF inspects each request, then every custom rule and every AWS managed rule group by its real name, with its use case
  • Must-have vs good-to-have rules, with what each costs, and how it all maps to the OWASP Top 10:2025
 Part 2 : running it in production. Bot Control, Anti-DDoS, CAPTCHA and Challenge, logging to S3 and your SIEM, monitoring, the 14-day Count-mode rollout, and the full cost breakdown.
Everything here is based on the current AWS WAF documentation (October 2026), with links at the end.



Why you need a WAF at all

Security groups and network ACLs work at layers 3 and 4. They decide which IPs and ports can talk to your load balancer. Port 443 is open to the world, because it has to be. Everything an attacker does next happens inside that HTTPS request:
  • Injection and exploits: SQL injection, cross-site scripting, Log4j-style payloads, path traversal to  /etc/passwd , SSRF to the EC2 metadata endpoint.
  • Credential stuffing and brute force: thousands of login attempts per minute with leaked passwords.
  • Bots and scrapers: price scraping, inventory hoarding, fake sign-ups, and now AI crawlers reading everything you publish.
  • Layer 7 DDoS: HTTP floods that look like normal requests, just millions of them.
AWS Shield Standard (free, always on) absorbs network-level floods. It doesn't read HTTP requests. That's the WAF's job: it inspects every request at layer 7 and decides to allow, block, count, challenge or rate-limit it before your application spends a single CPU cycle on it.
There's also the compliance angle. PCI DSS v4.0 requirement 6.4.2 expects an automated technical solution that detects and prevents web-based attacks for public-facing web applications. A properly configured WAF is the usual answer.



Part 1: The associated resources (what AWS WAF protects)

AWS WAF is a layer 7 firewall. It inspects HTTP(S) requests and decides to allow, block, count, or challenge them, based on rules you put in a web ACL (the console now also calls it a protection pack).
You attach a web ACL to a resource. The resource decides where the web ACL must be created:
Resource
Web ACL scope
Amazon CloudFront distribution
Global, created in US East (N. Virginia) us-east-1
Application Load Balancer (ALB)
Regional, same Region as the ALB
Amazon API Gateway REST API
Regional
AWS AppSync GraphQL API
Regional
Amazon Cognito user pool
Regional
AWS App Runner service
Regional
AWS Verified Access instance
Regional
Amazon Bedrock AgentCore Gateway
Regional
AWS Amplify
Global (CloudFront) web ACL
A few rules that catch people out:
  • Each resource can have only one web ACL. One web ACL can protect many resources.
  • A web ACL attached to CloudFront can't also be attached to a regional resource. Plan separate global and regional web ACLs.
  • Body inspection limits differ. For ALB and AppSync, AWS WAF inspects only the first 8 KB of the request body, and that's fixed. For CloudFront, API Gateway, Cognito, App Runner, Verified Access and AgentCore Gateway, the default is 16 KB, configurable up to 64 KB (larger limits cost extra). This matters a lot later when we talk about upload restrictions and SQL injection in large JSON bodies.
AWS WAF enterprise reference architecture: CloudFront global web ACL, regional web ACL on ALB, API Gateway, AppSync, Cognito and App Runner, with logging to Firehose, S3, Athena and a SIEM

My recommendation: if your app sits behind CloudFront, put your main web ACL on CloudFront. You block bad traffic at the edge, before it reaches your Region, and you get the larger body inspection limit. Keep a lean regional web ACL on the ALB as defence in depth, and lock the ALB down so it only accepts traffic from CloudFront.



Part 2: Implementation, step by step

Step 1: Create the web ACL

In the AWS WAF console, choose Protection packs (web ACLs) → Create. A few settings deserve thought:
  • Default action: Allow. For a public website, the default is Allow, and your rules block what's bad. (A default Block web ACL makes sense only for private apps with a strict allow-list.)
  • Body inspection size limit. For CloudFront and API Gateway, raise it from 16 KB if your app accepts large JSON or form bodies. Every additional 16 KB is billed per request, so pick what you need, not the maximum.
  • CloudWatch metrics and sampled requests. Turn both on for every rule. Metrics are free and they're how you'll tune.
  • Token domains. If you'll use CAPTCHA, Challenge or Bot Control Targeted across subdomains (for example  www.example.com  and  app.example.com ), add your apex domain so one token works across them.
  • Immunity time. Defaults to 300 seconds for both CAPTCHA and Challenge. CAPTCHA can go as low as 60 seconds and both can go up to 3 days.

Capacity: the WCU budget

Every rule costs web ACL capacity units (WCUs). The first 1,500 WCUs are included in the base price. Beyond that you pay per request for each extra 500 WCUs, up to a hard limit of 5,000 per web ACL.
The managed rule groups add up quickly: the Core rule set alone is 700 WCUs, and Known bad inputs, SQLi and Linux are 200 each (full list with costs in Step 4). Add every group in this post and you're at about 1,775 WCUs before a single custom rule. So decide which groups your stack actually needs: no PHP, no PHP rule group; no WordPress, no WordPress rule group.



Step 2: Create a rule group for your custom rules

You can add custom rules directly to the web ACL, but in an enterprise I put them in a rule group instead. A rule group is a reusable bundle of rules you can attach to many web ACLs. Your geo, method and upload rules then live in one place, and with AWS Firewall Manager you can push the same rule groups to every account in your AWS Organization.
Two things to know:
  • A rule group's capacity is fixed when you create it. Leave headroom (for example, set 500 WCUs even if your rules need 200 today), because you can't change it later.
  • IP sets and regex pattern sets are separate resources. Create them first, in the same Region (or  us-east-1  for CloudFront), and reference them from rules. Updating an IP set updates every rule that uses it, with no rule changes.



How AWS WAF inspects every request (read this before writing rules)

Getting the rule structure right matters more than any single rule. Here's what actually happens to each request:
  1. The request arrives at the protected resource, for example CloudFront or your ALB, and is handed to the web ACL before it reaches your application.
  1. Rules run in priority order, lowest number first. A rule group (yours or AWS-managed) counts as one rule in that list, and the rules inside it run in their own order.
  1. Each matching rule takes its action:
  • Allow or Block is terminating. Evaluation stops, and nothing below it runs.
  • Count is non-terminating. The match is recorded and labelled, and evaluation continues.
  • CAPTCHA and Challenge stop the request if the client has no valid token. If the token is valid, they behave like Count and evaluation continues.
  1. Labels carry information down the list. Managed rules add labels such as  awswaf:managed:aws:core-rule-set:SizeRestrictions_Body , and a rule with a higher priority number can act on them. This is how you build precise exceptions.
  1. If nothing terminates the request, the web ACL's default action applies (usually Allow for a public site).
That gives a simple design principle: cheap, high-confidence decisions first; expensive, deep inspection last; label-based follow-ups at the very end.

The best-practice structure: custom rules first, then AWS managed rules

I think of the web ACL in layers:
  • 1. Trust and deny lists (priority 0–1): IP allow-list (trusted), IP block-list. Your monitoring and partners never trip a rule, and known-bad IPs stop instantly.
  • 2. Business rules (custom) (priority 2–6): Geo allow-list, HTTP methods, rate limits, upload file types. Encode what your business allows. Cheap, and removes most noise early.
  • 3. Reputation (managed) (priority 10–11):  AWSManagedRulesAmazonIpReputationList ,  AWSManagedRulesAnonymousIpList . Low WCU, high signal. Drops known attackers before deep inspection.
  • 4. Layer 7 DDoS (managed) (priority 12):  AWSManagedRulesAntiDDoSRuleSet . Must see traffic early to build its baseline and react during an event.
  • 5. OWASP baseline (managed) (priority 20–22):  AWSManagedRulesCommonRuleSet  + your label exceptions,  AWSManagedRulesKnownBadInputsRuleSet . The OWASP Top 10 protections every app needs.
  • 6. Stack-specific (managed) (priority 23–28):  AWSManagedRulesSQLiRuleSet ,  AWSManagedRulesLinuxRuleSet ,  AWSManagedRulesUnixRuleSet ,  AWSManagedRulesWindowsRuleSet ,  AWSManagedRulesPHPRuleSet ,  AWSManagedRulesWordPressRuleSet ,  AWSManagedRulesAdminProtectionRuleSet . Only what matches your technology.
  • 7. Bots (managed, paid) (priority 30):  AWSManagedRulesBotControlRuleSet , scoped down. The most expensive per request, so it sees the least traffic.
  • 8. Label follow-ups (custom) (priority 31+): Rules that match labels from the groups above. Fine-grained decisions, such as blocking unverified AI bots on one path.
Two practical tips:
  • Leave gaps in priority numbers (0, 1, 2… then 10, 20, 30). You'll add rules later, and renumbering a production web ACL is no fun.
  • Prefix rule names by layer, for example  L2-Geo-AllowBusinessCountries  or  L5-CRS . Your CloudWatch metrics and logs become readable at a glance.



Step 3: The custom rules every enterprise needs, one by one

Managed rules catch known attack patterns. Custom rules encode your business: where your customers are, what your app accepts, and how fast a normal user moves. These are the five I put in almost every web ACL, in priority order.

3.1 IP allow-list and block-list (priority 0–1)

Create two IP sets: one for trusted sources (office egress IPs, monitoring, partner integrations, your own pentest team) and one for known-bad IPs from incident response.
  • Allow the trusted set at priority 0, so your own monitoring never trips a managed rule at 3 AM.
  • Block the bad set at priority 1.
Keep the allow-list short and reviewed. An allow rule ends evaluation, so everything in that set skips every rule below it.

3.2 Geo restriction based on your business (priority 2)

If you only serve customers in India, the UAE and Singapore, there's no reason to accept traffic from 190 other countries. An allow-list approach (block everything not from your countries) is far stronger than chasing "bad" countries.
JSON
{
"Name": "Geo-AllowBusinessCountriesOnly",
"Priority": 2,
"Statement": {
"NotStatement": {
"Statement": {
"GeoMatchStatement": { "CountryCodes": ["IN", "AE", "SG"] }
}
}
},
"Action": { "Block": {} },
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "Geo-AllowBusinessCountriesOnly"
}
}

Two things to check first:
  • Legal and business sign-off. Geo-blocking is a business decision, not just a security one. Get it agreed with the product owner.
  • Proxies. Behind another proxy or CDN that isn't CloudFront, use the  ForwardedIPConfig  option so WAF reads the client IP from the forwarded header instead of the proxy's IP.
Geo-blocking also keeps a lot of noise out of your logs, which makes everything else easier to tune.

3.3 Allow only the HTTP methods your app uses (priority 3)

Most web apps need  GET ,  POST  and  HEAD , plus  OPTIONS  if browsers make cross-origin (CORS) calls. APIs may add  PUT ,  PATCH  and  DELETE . Anything else, such as  TRACE ,  PROPFIND  or  CONNECT , is almost always a scanner.
JSON
{
"Name": "Allow-OnlyExpectedMethods",
"Priority": 3,
"Statement": { "NotStatement": { "Statement": { "OrStatement": { "Statements": [
{ "ByteMatchStatement": { "FieldToMatch": { "Method": {} }, "PositionalConstraint": "EXACTLY", "SearchString": "GET", "TextTransformations": [{ "Priority": 0, "Type": "NONE" }] } },
{ "...same for POST, HEAD and OPTIONS...": {} }
] } } } },
"Action": { "Block": {} },
"VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "Allow-OnlyExpectedMethods" }
}

If different paths need different methods (for example  DELETE  only under  /api/ ), split it into two rules with a URI scope.

3.4 Rate limiting with a proper 429 response (priority 4)

Rate-based rules count requests per aggregation key over an evaluation window of 60, 120, 300 or 600 seconds, and act on anything over your limit (minimum 10 requests per window). You can aggregate by IP, by forwarded IP, by a constant (all matching requests together) or by up to 5 custom keys, such as a header, cookie, query argument or label.
I always run at least two:
  1. A global per-IP limit, generous enough that no real user ever hits it (for example, 2,000 requests per 5 minutes). This is your flood brake.
  1. A tight limit on sensitive paths such as  /login ,  /otp ,  /password-reset  and  /api/search , where brute force and scraping actually happen.
Don't just block. Return a 429 Too Many Requests with a friendly body, so legitimate clients and your frontend know what happened. First define the body once at web ACL level (custom response bodies), then reference it:
JSON
{
"Name": "RateLimit-Login",
"Priority": 5,
"Statement": {
"RateBasedStatement": {
"Limit": 20,
"EvaluationWindowSec": 300,
"AggregateKeyType": "IP",
"ScopeDownStatement": {
"ByteMatchStatement": {
"FieldToMatch": { "UriPath": {} },
"PositionalConstraint": "STARTS_WITH",
"SearchString": "/login",
"TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }]
}
}
}
},
"Action": {
"Block": {
"CustomResponse": {
"ResponseCode": 429,
"CustomResponseBodyKey": "too-many-requests",
"ResponseHeaders": [{ "Name": "Retry-After", "Value": "300" }]
}
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "RateLimit-Login"
}
}

For browser traffic, consider CAPTCHA instead of Block on the login limit. A real user who fat-fingers their password six times gets a puzzle, not a wall.

3.5 File upload restrictions: allow only what the business needs

If your app accepts uploads such as invoices or profile pictures, decide the allowed types (for example  .csv ,  .pdf ,  .jpg ,  .png ) and block everything else on the upload endpoint, especially executables ( .exe ,  .dll ,  .bat ,  .ps1 ,  .sh ,  .msi ,  .jar ,  .js ,  .hta ) and server-side scripts ( .php ,  .jsp ,  .aspx ).
The trick is to write it as an allow-list: block an upload whose  filename="..."  doesn't end in an approved extension.
JSON
{
"Name": "Upload-AllowOnlyApprovedFileTypes",
"Priority": 6,
"Statement": {
"AndStatement": {
"Statements": [
{
"ByteMatchStatement": {
"FieldToMatch": { "UriPath": {} },
"PositionalConstraint": "STARTS_WITH",
"SearchString": "/api/upload",
"TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }]
}
},
{
"NotStatement": {
"Statement": {
"RegexMatchStatement": {
"FieldToMatch": { "Body": { "OversizeHandling": "CONTINUE" } },
"RegexString": "(?i)filename=\"[^\"]+\\.(csv|pdf|jpe?g|png)\"",
"TextTransformations": [{ "Priority": 0, "Type": "NONE" }]
}
}
}
}
]
}
},
"Action": { "Block": {} },
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "Upload-AllowOnlyApprovedFileTypes"
}
}

I'll be honest about the limits of this rule:
  • It checks the file name, not the file. Someone can rename  malware.exe  to  invoice.pdf . WAF stops lazy attacks and mistakes. For real protection, scan the stored file too, for example with Amazon GuardDuty Malware Protection for S3, and validate the file's magic bytes in your app.
  • Body inspection limits apply. WAF only sees the first 8 KB (ALB) or 16–64 KB (CloudFront and others). The  filename=  part of a multipart upload usually appears early, but test it with your client.
  • Be careful with SVG. SVG is XML and can carry JavaScript. Only allow it if you sanitise it server-side.
You can also block requests for executable files anywhere in the URI path with a  RegexMatchStatement  on  UriPath , using a pattern like  (?i)\.(exe|dll|bat|cmd|ps1|sh|msi|jar)$ . That stops scanners hunting for leftover installers and scripts.



Step 4: AWS managed rule groups, one by one

AWS Managed Rules are written and maintained by the AWS Threat Research Team and update automatically. You add each one to the web ACL with its vendor ( AWS ) and exact name. Here's every group I use, what it does, its key rules, and how I tune it.

4.1 Amazon IP reputation list:  AWSManagedRulesAmazonIpReputationList  (25 WCU)

What it does: blocks IP addresses Amazon's own threat intelligence (including the MadPot honeypot network) has seen attacking.
Rule
Default
Catches
AWSManagedIPReputationList
Block
IPs actively engaged in malicious activity
AWSManagedReconnaissanceList
Block
IPs scanning and probing AWS resources
AWSManagedIPDDoSList
Count
IPs taking part in DDoS attacks
Use it: always. It's the cheapest high-signal rule group there is. Tune it: consider overriding  AWSManagedIPDDoSList  to Block once you've checked its labels in your logs.

4.2 Anonymous IP list:  AWSManagedRulesAnonymousIpList  (50 WCU)

What it does: blocks clients that hide who they are.
Rule
Default
Catches
AnonymousIPList
Block
Tor exit nodes, anonymising VPNs and temporary proxies
HostingProviderIPList
Block
Traffic from web hosting and cloud providers (not AWS IPs)
Use it: for consumer-facing apps, login and checkout. Tune it:  HostingProviderIPList  is the one that bites. Payment gateway webhooks, uptime monitors and partner integrations often run on cloud providers. Set it to Count for two weeks, then enforce it or allow known partners by label.

4.3 Core rule set (CRS):  AWSManagedRulesCommonRuleSet  (700 WCU)

What it does: the OWASP Top 10 baseline. If you only add one managed group, add this one.
Rule family
Catches
NoUserAgent_HEADER, UserAgent_BadBots_HEADER
Requests with no User-Agent, and scanners such as nmap and nessus
SizeRestrictions_QUERYSTRING / _Cookie_HEADER / _BODY / _URIPATH
Query strings over 2 KB, cookies over 10 KB, bodies over 8 KB, URI paths over 1 KB
EC2MetaDataSSRF_*
SSRF attempts to reach the EC2 instance metadata endpoint
GenericLFI_*
Local file inclusion such as ../../etc/passwd
GenericRFI_*
Remote file inclusion using URLs to IPv4 hosts
RestrictedExtensions_*
Requests for files such as .log, .ini and backups
CrossSiteScripting_*
XSS in cookies, query arguments, body and URI path
Use it: always. Tune it:  SizeRestrictions_BODY  and  CrossSiteScripting_BODY  cause most false positives. Fix them with label-based exceptions (covered in Part 2), never by setting the whole group to Count.

4.4 Known bad inputs:  AWSManagedRulesKnownBadInputsRuleSet  (200 WCU)

What it does: blocks request patterns that are known to be exploits. It's effectively free virtual patching for famous CVEs.
Rule family
Catches
Log4JRCE_*
Log4Shell (CVE-2021-44228 and related), such as ${jndi:ldap://...} in headers, query string, body or path
JavaDeserializationRCE_*
Java deserialization RCE such as Spring4Shell (CVE-2022-22965)
ReactJSRCE_BODY
The React RCE CVE-2025-55182
Host_localhost_HEADER
A Host: localhost header, a common SSRF and bypass trick
PROPFIND_METHOD
The WebDAV PROPFIND method
ExploitablePaths_URIPATH
Paths such as web-inf
Use it: always. It's rare for it to block legitimate traffic.

4.5 SQL database:  AWSManagedRulesSQLiRuleSet  (200 WCU)

What it does: blocks SQL injection.
Rule family
Catches
SQLi_QUERYARGUMENTS / _BODY / _COOKIE / _URIPATH
SQL injection using AWS WAF's built-in SQLi detection
SQLiExtendedPatterns_QUERYARGUMENTS / _BODY / _HEADER / _URIPATH
Extra SQL patterns the basic rules miss
Use it: whenever there's a SQL database behind the app, which is almost always. Tune it: search boxes and "notes" fields that legitimately contain SQL-like text sometimes match. Exempt that one path using labels, not the whole group.

4.6 Linux operating system:  AWSManagedRulesLinuxRuleSet  (200 WCU)

What it does: blocks Linux-specific local file inclusion, such as attempts to read  /proc/version  or  /etc/shadow . Key rules:  LFI_URIPATH ,  LFI_QUERYSTRING ,  LFI_HEADER . Use it: if your app runs on Linux, including containers.

4.7 POSIX operating system:  AWSManagedRulesUnixRuleSet  (100 WCU)

What it does: blocks shell command injection and path traversal on Unix-like systems, such as  echo $HOME  or  ;cat /etc/passwd . Key rules:  UNIXShellCommandsVariables_QUERYSTRING ,  _BODY ,  _HEADER . Use it: with the Linux rule set. AWS recommends using the two together.

4.8 Windows operating system:  AWSManagedRulesWindowsRuleSet  (200 WCU)

What it does: blocks Windows command injection and PowerShell injection.
Rule family
Catches
WindowsShellCommands_*
Windows shell injection such as ||nslookup and ;cmd in headers, query, path or body
PowerShellCommands_*
PowerShell injection such as Invoke-Expression in cookies, query arguments or body
Use it: only if your backend runs on Windows or IIS. Don't pay the WCUs for a Linux stack.

4.9 PHP application:  AWSManagedRulesPHPRuleSet  (100 WCU)

What it does: blocks PHP code injection, such as calls to  fsockopen  or abuse of  $_GET  superglobals. Key rules:  PHPHighRiskMethodsVariables_HEADER ,  _QUERYSTRING ,  _BODY ,  _URIPATH , plus  PHP2Shell_BODY  and  PHP2Shell_BODY_Extended  for the October 2026 PHP2Shell flaws (CVE-2026-109618 and CVE-2026-109619). Use it: for any PHP app, including Laravel and WordPress. Right now it's also your virtual patch for PHP2Shell until PHP is upgraded.

4.10 WordPress application:  AWSManagedRulesWordPressRuleSet  (100 WCU)

What it does: blocks WordPress-specific attacks. Key rules:  WordPressExploitableCommands_QUERYSTRING  (such as  do-reset-wordpress ) and  WordPressExploitablePaths_URIPATH  (such as  xmlrpc.php ). Use it: with the SQL and PHP rule groups, as AWS recommends.

4.11 Admin protection:  AWSManagedRulesAdminProtectionRuleSet  (100 WCU)

What it does: blocks external access to administrative paths, such as  sqlmanager . Key rule:  AdminProtection_URIPATH . Use it: when admin pages exist but shouldn't be reachable from the internet. If your team needs them, allow their IPs with a higher-priority rule.

Must-have vs good-to-have: what to turn on first, and what it costs

Not every team needs every rule on day one. This is how I tier them when someone asks "what's the minimum?" and "what's next when we have budget?". Prices are the standard AWS WAF list prices at the time of writing: every rule or rule group in the web ACL is $1 per month, plus $0.60 per million requests for the whole web ACL. The baseline AWS managed rule groups have no subscription fee; the paid ones are marked.
Tier
Rule (in web ACL order)
WCU
Cost/month
Must have
IP allow/block lists (custom)
1 each
$1 each
Must have
/login rate limit with 429 (custom)
~4
$1
Must have
HTTP method allow-list (custom)
~10
$1
Must have
AWSManagedRulesAmazonIpReputationList
25
$1
Must have
AWSManagedRulesCommonRuleSet
700
$1
Must have
AWSManagedRulesKnownBadInputsRuleSet
200
$1
Must have (SQL DB)
AWSManagedRulesSQLiRuleSet
200
$1
Your stack
AWSManagedRulesLinuxRuleSet + UnixRuleSet
300
$2
Your stack
Windows / PHP / WordPress rule sets
100–200
$1 each
Good to have
Geo allow-list (custom)
1
$1
Good to have
Upload file-type allow-list (custom)
~30
$1
Good to have
AWSManagedRulesAnonymousIpList
50
$1
Good to have
AWSManagedRulesAdminProtectionRuleSet
100
$1
Paid
AWSManagedRulesBotControlRuleSet (Common)
50
$1 + $10 + $1/M req (10M free)
Paid
AWSManagedRulesATPRuleSet
50
$1 + $10 + request fees
Paid
AWSManagedRulesAntiDDoSRuleSet
50
$1 + subscription + request fees
Paid
Bot Control (Targeted)
50
$1 + $10 + $10/M req (1M free)
The budget version. A web ACL with the three must-have custom rules and the four baseline managed groups costs $5 + $7 = $12 a month plus $0.60 per million requests. Add the Linux and POSIX rule sets and you are at $14 a month and roughly 1,440 WCUs, still inside the 1,500 included WCUs. Above 1,500 WCUs, every extra 500 adds $0.20 per million requests, so check the capacity number in the console before adding the next group.
The rule of thumb: spend the first $15 on the must-have row, put everything in Count for 14 days, and only then spend on Bot Control, ATP or Anti-DDoS, scoped down to the paths that need them.

Mapping the OWASP Top 10:2025 to AWS WAF

AWS WAF is a strong control for some OWASP risks and a weak one for others. Being honest about that helps you explain to auditors (and yourself) what the WAF really covers:
  • A01 Broken Access Control (now includes SSRF): Admin protection, geo and IP rules, CRS  EC2MetaDataSSRF_*  and  GenericRFI_* , Known bad inputs  Host_localhost_HEADER . Coverage: Partial. Access control still has to be enforced in your app.
  • A02 Security Misconfiguration: CRS  RestrictedExtensions_* ,  ExploitablePaths_URIPATH , HTTP method allow-list. Coverage: Partial.
  • A03 Software Supply Chain Failures: Virtual patches for vulnerable components: Log4j, Spring4Shell, React RCE, PHP2Shell. Coverage: Partial. Buys time to patch.
  • A04 Cryptographic Failures: None. Use TLS policies on CloudFront and ALB. Coverage: Not a WAF control.
  • A05 Injection:  AWSManagedRulesSQLiRuleSet , CRS XSS rules, Linux, POSIX, Windows and PHP rule sets. Coverage: Strong.
  • A06 Insecure Design: Rate limits, Bot Control, CAPTCHA on business flows. Coverage: Partial.
  • A07 Authentication Failures: Login rate limits, CAPTCHA, Account Takeover Prevention ( AWSManagedRulesATPRuleSet ). Coverage: Strong for brute force and credential stuffing.
  • A08 Software or Data Integrity Failures: Known bad inputs  JavaDeserializationRCE_* . Coverage: Partial.
  • A09 Security Logging & Alerting Failures: WAF logging to S3 or Firehose → SIEM, CloudWatch alarms. Coverage: Strong for the edge layer.
  • A10 Mishandling of Exceptional Conditions: Size restrictions and malformed-request rules reduce exposure. Coverage: Limited.



What's next

At this point you have a web ACL with a clear structure: trust lists and business rules first, then reputation, then the OWASP baseline, then the groups for your stack. Every rule has a name that tells you which layer it belongs to, and you know what each one costs.
Don't switch anything to Block yet. In  Part 2  we add Bot Control and Anti-DDoS, send the logs somewhere useful, build the dashboards and alarms, and run the 14-day Count-mode rollout that keeps this from ever breaking a customer's upload at 2:40 on a Saturday morning.
What's the one rule you always add that isn't on this list? Tell me in the comments.



References

 #AWS   #AWSWAF   #CloudSecurity   #WebApplicationFirewall   #AppSec   #OWASP 
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article