AWS WAF (Web Application Firewall) protects your web applications from common web exploits, bots, and DDoS attacks. It filters malicious traffic before it reaches your application, providing protection against SQL injection, cross-site scripting (XSS), and other OWASP Top 10 vulnerabilities.
This article is part of our comprehensive Cloud Security Tips for 2026 guide covering essential practices for protecting your cloud environment.
AWS WAF Components
| Component | Purpose |
|---|---|
| Web ACL | Container for rules, attached to resources |
| Rules | Conditions that match web requests |
| Rule Groups | Reusable collections of rules |
| IP Sets | Collections of IP addresses for filtering |
| Regex Pattern Sets | Regular expressions for matching |
| Managed Rules | Pre-built rule sets from AWS/Marketplace |
Create a Web ACL with AWS Console
-
Open the AWS WAF Console
-
Click Create web ACL
-
Enter a name (e.g., "production-web-acl")
-
Select the resource type:
-
CloudFront distributions - For global scope
-
Regional resources - For ALB, API Gateway, AppSync
-
Select the AWS resources to protect
-
Add managed rule groups (recommended starting point)
-
Set the default action (Allow or Block)
-
Review and create the Web ACL
Create Web ACL with AWS CLI
# Create Web ACL for regional resources (ALB, API Gateway)
aws wafv2 create-web-acl \
--name production-web-acl \
--scope REGIONAL \
--default-action Allow={} \
--visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=production-web-acl \
--rules '[
{
"Name": "AWS-AWSManagedRulesCommonRuleSet",
"Priority": 1,
"OverrideAction": {"None": {}},
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesCommonRuleSet"
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "AWSManagedRulesCommonRuleSetMetric"
}
}
]'
# For CloudFront (global scope), use --scope CLOUDFRONT
# and run from us-east-1 regionAdd AWS Managed Rule Groups
Essential Managed Rules
# Update Web ACL with recommended managed rules
aws wafv2 update-web-acl \
--name production-web-acl \
--scope REGIONAL \
--id "12345678-1234-1234-1234-123456789012" \
--lock-token "abc123..." \
--default-action Allow={} \
--visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=production-web-acl \
--rules '[
{
"Name": "AWS-AWSManagedRulesCommonRuleSet",
"Priority": 1,
"OverrideAction": {"None": {}},
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesCommonRuleSet"
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "CommonRuleSet"
}
},
{
"Name": "AWS-AWSManagedRulesKnownBadInputsRuleSet",
"Priority": 2,
"OverrideAction": {"None": {}},
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesKnownBadInputsRuleSet"
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "KnownBadInputs"
}
},
{
"Name": "AWS-AWSManagedRulesSQLiRuleSet",
"Priority": 3,
"OverrideAction": {"None": {}},
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesSQLiRuleSet"
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "SQLiRuleSet"
}
},
{
"Name": "AWS-AWSManagedRulesAmazonIpReputationList",
"Priority": 4,
"OverrideAction": {"None": {}},
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesAmazonIpReputationList"
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "IpReputationList"
}
}
]'Available AWS Managed Rule Groups
| Rule Group | Protection | WCUs |
|---|---|---|
| AWSManagedRulesCommonRuleSet | OWASP Top 10, common attacks | 700 |
| AWSManagedRulesKnownBadInputsRuleSet | Known exploit patterns | 200 |
| AWSManagedRulesSQLiRuleSet | SQL injection attacks | 200 |
| AWSManagedRulesLinuxRuleSet | Linux-specific vulnerabilities | 200 |
| AWSManagedRulesUnixRuleSet | POSIX OS vulnerabilities | 100 |
| AWSManagedRulesWindowsRuleSet | Windows-specific vulnerabilities | 200 |
| AWSManagedRulesAmazonIpReputationList | Malicious IPs from AWS threat intel | 25 |
| AWSManagedRulesAnonymousIpList | VPNs, proxies, Tor nodes | 50 |
| AWSManagedRulesBotControlRuleSet | Bot detection and control | 50 |
Configure Rate Limiting
# Add rate limiting rule to prevent brute force/DDoS
aws wafv2 create-rule-group \
--name rate-limit-rules \
--scope REGIONAL \
--capacity 50 \
--visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=RateLimitRules \
--rules '[
{
"Name": "RateLimit-All",
"Priority": 1,
"Action": {"Block": {}},
"Statement": {
"RateBasedStatement": {
"Limit": 2000,
"AggregateKeyType": "IP"
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "RateLimitAll"
}
},
{
"Name": "RateLimit-Login",
"Priority": 2,
"Action": {"Block": {}},
"Statement": {
"RateBasedStatement": {
"Limit": 100,
"AggregateKeyType": "IP",
"ScopeDownStatement": {
"ByteMatchStatement": {
"SearchString": "/login",
"FieldToMatch": {"UriPath": {}},
"TextTransformations": [{"Priority": 0, "Type": "LOWERCASE"}],
"PositionalConstraint": "STARTS_WITH"
}
}
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "RateLimitLogin"
}
}
]'Enable Bot Control
# Add Bot Control managed rule group
{
"Name": "AWS-AWSManagedRulesBotControlRuleSet",
"Priority": 0,
"OverrideAction": {"None": {}},
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesBotControlRuleSet",
"ManagedRuleGroupConfigs": [
{
"AWSManagedRulesBotControlRuleSet": {
"InspectionLevel": "COMMON"
}
}
]
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "BotControl"
}
}Bot Control Inspection Levels
| Level | Description | Cost |
|---|---|---|
| COMMON | Basic bot detection using request signatures | Base rate |
| TARGETED | Advanced detection using ML and browser fingerprinting | Higher rate |
Create Custom Rules
Block Specific Countries
{
"Name": "GeoBlock-HighRiskCountries",
"Priority": 5,
"Action": {"Block": {}},
"Statement": {
"GeoMatchStatement": {
"CountryCodes": ["RU", "CN", "KP", "IR"]
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "GeoBlock"
}
}Block Bad User Agents
# Create regex pattern set
aws wafv2 create-regex-pattern-set \
--name bad-user-agents \
--scope REGIONAL \
--regular-expression-list '[
{"RegexString": ".*curl.*"},
{"RegexString": ".*wget.*"},
{"RegexString": ".*python-requests.*"},
{"RegexString": ".*sqlmap.*"},
{"RegexString": ".*nikto.*"}
]'
# Use in rule
{
"Name": "BlockBadUserAgents",
"Priority": 6,
"Action": {"Block": {}},
"Statement": {
"RegexPatternSetReferenceStatement": {
"ARN": "arn:aws:wafv2:us-east-1:123456789012:regional/regexpatternset/bad-user-agents/12345678-1234-1234-1234-123456789012",
"FieldToMatch": {"SingleHeader": {"Name": "user-agent"}},
"TextTransformations": [{"Priority": 0, "Type": "LOWERCASE"}]
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "BlockBadUserAgents"
}
}Protect Admin Paths
{
"Name": "ProtectAdminPaths",
"Priority": 7,
"Action": {"Block": {}},
"Statement": {
"AndStatement": {
"Statements": [
{
"ByteMatchStatement": {
"SearchString": "/admin",
"FieldToMatch": {"UriPath": {}},
"TextTransformations": [{"Priority": 0, "Type": "LOWERCASE"}],
"PositionalConstraint": "STARTS_WITH"
}
},
{
"NotStatement": {
"Statement": {
"IPSetReferenceStatement": {
"ARN": "arn:aws:wafv2:us-east-1:123456789012:regional/ipset/admin-whitelist/..."
}
}
}
}
]
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "ProtectAdminPaths"
}
}Terraform Configuration
resource "aws_wafv2_web_acl" "main" {
name = "production-web-acl"
description = "Production WAF rules"
scope = "REGIONAL"
default_action {
allow {}
}
# AWS Managed Rules - Common Rule Set
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 1
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRulesCommonRuleSet"
sampled_requests_enabled = true
}
}
# Rate limiting
rule {
name = "RateLimit"
priority = 10
action {
block {}
}
statement {
rate_based_statement {
limit = 2000
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimit"
sampled_requests_enabled = true
}
}
# Geo blocking
rule {
name = "GeoBlock"
priority = 20
action {
block {}
}
statement {
geo_match_statement {
country_codes = ["RU", "CN", "KP", "IR"]
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "GeoBlock"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "production-web-acl"
sampled_requests_enabled = true
}
}
# Associate with ALB
resource "aws_wafv2_web_acl_association" "alb" {
resource_arn = aws_lb.main.arn
web_acl_arn = aws_wafv2_web_acl.main.arn
}Enable WAF Logging
# Create S3 bucket for logs (must have aws-waf-logs- prefix)
aws s3api create-bucket \
--bucket aws-waf-logs-production \
--region us-east-1
# Enable logging
aws wafv2 put-logging-configuration \
--logging-configuration '{
"ResourceArn": "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/production-web-acl/...",
"LogDestinationConfigs": [
"arn:aws:s3:::aws-waf-logs-production"
],
"RedactedFields": [],
"ManagedByFirewallManager": false
}'
# Alternative: Send to CloudWatch Logs
aws logs create-log-group --log-group-name aws-waf-logs/production
aws wafv2 put-logging-configuration \
--logging-configuration '{
"ResourceArn": "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/production-web-acl/...",
"LogDestinationConfigs": [
"arn:aws:logs:us-east-1:123456789012:log-group:aws-waf-logs/production"
]
}'Monitor WAF with CloudWatch
# Create alarm for high block rate
aws cloudwatch put-metric-alarm \
--alarm-name waf-high-block-rate \
--alarm-description "High WAF block rate indicates attack" \
--metric-name BlockedRequests \
--namespace AWS/WAFV2 \
--statistic Sum \
--period 300 \
--threshold 1000 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 2 \
--dimensions Name=WebACL,Value=production-web-acl Name=Region,Value=us-east-1 Name=Rule,Value=ALL \
--alarm-actions arn:aws:sns:us-east-1:123456789012:security-alerts
# Query recent blocks
aws wafv2 get-sampled-requests \
--web-acl-arn "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/production-web-acl/..." \
--rule-metric-name AWSManagedRulesCommonRuleSet \
--scope REGIONAL \
--time-window StartTime=$(date -d "1 hour ago" +%s),EndTime=$(date +%s) \
--max-items 100Best Practices Summary
| Practice | Recommendation |
|---|---|
| Start with Managed Rules | Enable AWS Core Rule Set first |
| Test Before Blocking | Use Count mode for new rules |
| Enable Logging | Send to S3 or CloudWatch Logs |
| Rate Limiting | Protect login and API endpoints |
| Bot Protection | Enable Bot Control for public sites |
| Monitoring | Set up CloudWatch alarms |
| Regular Review | Analyze blocked requests weekly |