How to add WAF to AWS Services
How to Add WAF to a Load Balancer (ALB)
Step 1: Create a Web ACL
- Open AWS Console → WAF & Shield
- Choose Web ACLs → Create web ACL
- Configure:
- Name:
my-alb-waf - Resource type:
Regional resources(for ALB) - Region: Select your ALB’s region (e.g.,
ap-south-1)
- Name:
- Click Next
Step 2: Add Rules to the Web ACL
- Click Add rules → Add managed rule groups
- Enable recommended groups:
- ✅
AWSManagedRulesCommonRuleSet - ✅
AWSManagedRulesSQLiRuleSet - ✅
AWSManagedRulesAmazonIpReputationList
- ✅
- Add a Rate-based rule (optional):
- Name:
RateLimitPerIP - Rate limit:
2000requests per 5 minutes - Aggregate key:
Source IP - Action:
Block
- Name:
- Set rule priority (drag to reorder, or set numeric priority)
- Set default action:
Allow - Click Next → Next → Create web ACL
Step 3: Associate Web ACL with ALB
Option A: During Web ACL creation
- On the “Associate AWS resources” step, click Add AWS resources
- Select Application Load Balancer
- Choose your ALB from the list
- Click Add
Option B: After Web ACL creation
- Go to WAF & Shield → Web ACLs
- Click your Web ACL
- Go to Associated AWS resources tab
- Click Add AWS resources
- Select your ALB → Add
Option C: From the ALB console
- Go to EC2 → Load Balancers
- Select your ALB
- Attributes tab → Web Application Firewall (WAF)
- Select your Web ACL → Save changes
Step 4: Enable Logging
- In your Web ACL, go to Logging and metrics tab
- Click Enable logging
- Choose destination:
- S3 bucket — for long-term storage (bucket name must start with
aws-waf-logs-) - CloudWatch Logs — for real-time monitoring
- Kinesis Data Firehose — for streaming to S3/Splunk/etc.
- S3 bucket — for long-term storage (bucket name must start with
- Optionally, add log filters to log only blocked requests.
ALB WAF via AWS CLI
# Step 1: Create Web ACL
aws wafv2 create-web-acl \
--name "my-alb-waf" \
--scope REGIONAL \
--region ap-south-1 \
--default-action Allow={} \
--rules '[
{
"Name": "AWSCommonRules",
"Priority": 1,
"OverrideAction": { "None": {} },
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesCommonRuleSet"
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "AWSCommonRules"
}
}
]' \
--visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=my-alb-waf
# Step 2: Get the Web ACL ARN
aws wafv2 list-web-acls --scope REGIONAL --region ap-south-1
# Step 3: Associate Web ACL with ALB
aws wafv2 associate-web-acl \
--web-acl-arn "arn:aws:wafv2:ap-south-1:123456789012:regional/webacl/my-alb-waf/abc123" \
--resource-arn "arn:aws:elasticloadbalancing:ap-south-1:123456789012:loadbalancer/app/my-alb/abc123" \
--region ap-south-1
ALB WAF via Terraform
# Create WAF Web ACL
resource "aws_wafv2_web_acl" "alb_waf" {
name = "my-alb-waf"
scope = "REGIONAL"
description = "WAF Web ACL for ALB"
default_action {
allow {}
}
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 = "AWSCommonRules"
sampled_requests_enabled = true
}
}
rule {
name = "AWSManagedRulesSQLiRuleSet"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesSQLiRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "SQLiRules"
sampled_requests_enabled = true
}
}
rule {
name = "RateLimitPerIP"
priority = 3
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
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "my-alb-waf"
sampled_requests_enabled = true
}
tags = {
Environment = "production"
}
}
# Associate WAF with ALB
resource "aws_wafv2_web_acl_association" "alb_waf_association" {
resource_arn = aws_lb.my_alb.arn
web_acl_arn = aws_wafv2_web_acl.alb_waf.arn
}
# Enable WAF logging to S3
resource "aws_wafv2_web_acl_logging_configuration" "alb_waf_logging" {
log_destination_configs = [aws_s3_bucket.waf_logs.arn]
resource_arn = aws_wafv2_web_acl.alb_waf.arn
}
How to Add WAF to CloudFront
Important: CloudFront WAF Region
⚠️ Critical: CloudFront distributions require a WAF Web ACL with scope
CLOUDFRONT, and this Web ACL must be created in theus-east-1(N. Virginia) region, regardless of where your CloudFront distribution serves traffic.
Why? CloudFront is a global service and uses us-east-1 as its control plane. WAF for CloudFront is enforced at the edge.
Step 1: Create Web ACL in us-east-1
- Open AWS Console → WAF & Shield
- Switch your console region to
us-east-1(top-right dropdown) - Web ACLs → Create web ACL
- Configure:
- Name:
my-cloudfront-waf - Resource type:
CloudFront distributions(this automatically sets scope to CLOUDFRONT) - Region is locked to
Global (CloudFront)— this isus-east-1under the hood
- Name:
- Add rules (same as ALB WAF rules):
AWSManagedRulesCommonRuleSetAWSManagedRulesSQLiRuleSetAWSManagedRulesAmazonIpReputationList- Geo-restriction rules (if needed)
- Rate-based rules
- Set default action → Allow
- Create the Web ACL
Step 2: Attach WAF to CloudFront Distribution
Option A: During CloudFront Distribution creation
- Go to CloudFront → Create distribution
- In Web Application Firewall (WAF) section:
- Select Enable security protections → choose your existing Web ACL
- Or create a new one
- Complete the distribution setup
Option B: On existing CloudFront Distribution
- Go to CloudFront → Distributions
- Select your distribution → Edit
- Go to Security tab (or General tab, depending on console version)
- Under Web Application Firewall (WAF):
- Select your Web ACL from the dropdown
- (Only Web ACLs created with CloudFront scope in us-east-1 appear here)
- Click Save changes
- Wait for CloudFront to deploy (status:
In Progress→Deployed, ~5-15 min)
CloudFront WAF via AWS CLI
# Step 1: Create Web ACL (MUST use us-east-1, scope CLOUDFRONT)
aws wafv2 create-web-acl \
--name "my-cloudfront-waf" \
--scope CLOUDFRONT \
--region us-east-1 \
--default-action Allow={} \
--rules '[
{
"Name": "AWSCommonRules",
"Priority": 1,
"OverrideAction": { "None": {} },
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesCommonRuleSet"
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "AWSCommonRules"
}
}
]' \
--visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=my-cloudfront-waf
# Step 2: Get the Web ACL ID and ARN
aws wafv2 list-web-acls \
--scope CLOUDFRONT \
--region us-east-1
# Step 3: Attach to CloudFront distribution
# Get current distribution config first
aws cloudfront get-distribution-config \
--id E1ABCDEFGHIJK \
--output json > dist-config.json
# Update the WebACLId field in dist-config.json, then:
aws cloudfront update-distribution \
--id E1ABCDEFGHIJK \
--distribution-config file://dist-config.json \
--if-match <ETag from get-distribution-config>
CloudFront WAF via Terraform
# WAF for CloudFront MUST be in us-east-1
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
}
# Create WAF Web ACL for CloudFront
resource "aws_wafv2_web_acl" "cloudfront_waf" {
provider = aws.us_east_1
name = "my-cloudfront-waf"
scope = "CLOUDFRONT" # MUST be CLOUDFRONT for CloudFront
description = "WAF Web ACL for CloudFront"
default_action {
allow {}
}
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 = "AWSCommonRules"
sampled_requests_enabled = true
}
}
rule {
name = "GeoBlockRule"
priority = 2
action {
block {}
}
statement {
not_statement {
statement {
geo_match_statement {
country_codes = ["IN", "US", "GB"]
}
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "GeoBlock"
sampled_requests_enabled = true
}
}
rule {
name = "RateLimitPerIP"
priority = 3
action {
block {}
}
statement {
rate_based_statement {
limit = 3000
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimit"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "my-cloudfront-waf"
sampled_requests_enabled = true
}
tags = {
Environment = "production"
}
}
# CloudFront Distribution with WAF
resource "aws_cloudfront_distribution" "cdn" {
# ... your origin, behavior settings ...
web_acl_id = aws_wafv2_web_acl.cloudfront_waf.arn # Attach WAF here
enabled = true
default_root_object = "index.html"
restrictions {
geo_restriction {
restriction_type = "none" # Geo restriction handled by WAF
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
WAF Rule Priority & Best Practices
Recommended Rule Priority Order:
┌─────────────────────────────────────────────────────┐
│ Priority 1 │ IP Allow-list (your IPs) → Allow │ ← Highest priority
│ Priority 2 │ IP Block-list (known bad) → Block │
│ Priority 3 │ Geo-restriction rule → Block │
│ Priority 4 │ Rate-based rule → Block │
│ Priority 5 │ Bot Control managed rules → Block │
│ Priority 6 │ IP Reputation managed rules → Block │
│ Priority 7 │ Common Rule Set (CRS) → Block │
│ Priority 8 │ SQLi managed rules → Block │
│ Priority 9 │ Known bad inputs rules → Block │
│ Priority 10 │ App-specific custom rules → Block │
│ │ Default action → Allow │ ← Lowest priority
└─────────────────────────────────────────────────────┘
Key best practices:
- Always place IP allow-lists first (Priority 1) so your admin IPs are never blocked.
- Use Count mode when testing new rules — switch to Block only after validating.
- Set CloudWatch alarms on
BlockedRequeststo detect spikes. - Enable WAF logging to S3 for forensics and compliance.
- Review and tune managed rules — not all rules apply to your stack (e.g., disable WordPress rules if you don’t use WordPress).
- Use Firewall Manager when managing WAF across multiple AWS accounts.
- Place CloudFront in front of ALB and apply WAF at CloudFront for global edge-level protection.