Mitigating DDoS with AWS WAF Rate-Based Rules

Mitigating DDoS with AWS WAF Rate-Based Rules

AWS WAF provides protection against layer 7 attacks such as SQL injection and XSS. In addition, you can use rate-based rules to mitigate DDoS attacks.

Takahiro Iwasa
4 min read

AWS WAF provides protection against layer 7 attacks such as SQL injection and XSS. In addition, you can use rate-based rules to mitigate DDoS attacks.

🔥 Caution

AWS WAF rate-based rules can help mitigate DDoS attacks, but they cannot provide complete protection. For more comprehensive protection, consider using AWS Shield Advanced.

Considerations:

  • The minimum rate that you can set is 100.
  • AWS WAF checks the rate of requests every 30 seconds, and counts requests for the prior 5 minutes each time. Therefore, it may take up to 30 seconds for AWS WAF to detect and restrict the traffic.
  • AWS WAF has a limit of 10,000 IP addresses for rate limiting. When more than 10,000 addresses exceed the rate, AWS WAF restricts those with the highest rates.

Building

The template below configures a rate limit of 100.

AWSTemplateFormatVersion: 2010-09-09
Description: AWS WAF Rate-based rule sample
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub aws-waf-rate-based-rule-sample-${AWS::AccountId}-${AWS::Region}
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: TRUE
BlockPublicPolicy: TRUE
IgnorePublicAcls: TRUE
RestrictPublicBuckets: TRUE
S3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub arn:aws:s3:::${S3Bucket}/*
Condition:
StringEquals:
"AWS:SourceArn": !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
# AWS::WAFv2::WebACL must be deployed in us-east-1.
WAFv2WebACL:
Type: AWS::WAFv2::WebACL
Properties:
Name: aws-waf-rate-based-rule-sample
DefaultAction:
Allow: { }
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: aws-waf-rate-based-rule-sample
Scope: CLOUDFRONT
Rules:
- Name: rate-based-rule
Priority: 0
Action:
Block: { }
Statement:
RateBasedStatement:
Limit: 100
AggregateKeyType: IP
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: rate-based-rule
CloudFrontOriginAccessControl:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: aws-waf-rate-based-rule-sample
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
DependsOn: CloudFrontOriginAccessControl
Properties:
DistributionConfig:
Origins:
- Id: !GetAtt S3Bucket.DomainName
DomainName: !GetAtt S3Bucket.DomainName
OriginAccessControlId: !Ref CloudFrontOriginAccessControl
S3OriginConfig:
OriginAccessIdentity: ''
DefaultCacheBehavior:
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
TargetOriginId: !GetAtt S3Bucket.DomainName
ViewerProtocolPolicy: allow-all
Enabled: true
ViewerCertificate:
CloudFrontDefaultCertificate: true
MinimumProtocolVersion: TLSv1
WebACLId: !GetAtt WAFv2WebACL.Arn
DefaultRootObject: index.html

Deploy the stack:

Terminal window
aws cloudformation deploy \
--region us-east-1 \
--stack-name aws-waf-rate-based-rule-sample \
--template-file template.yaml
Important

AWS WAFv2 web ACL rules containing Scope: CLOUDFRONT must be deployed in the us-east-1 region.

Upload a sample index.html to the S3 bucket:

Terminal window
echo '<html><body>Hello World!</body></html>' > index.html
aws s3 cp index.html s3://aws-waf-rate-based-rule-sample-<ACCOUNT_ID>-us-east-1

Testing

Since the AWS WAF rate-checking interval is 30 seconds, send requests every second for 130 seconds or longer. Requests exceeding the configured limit will be blocked with a 403 Forbidden response.

https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-rate-based.html

AWS WAF checks the rate of requests every 30 seconds, and counts requests for the prior five minutes each time.

Terminal window
for i in `seq 1 130`; do
echo "Request: $i"
curl https://<CLOUDFRONT_DOMAIN>/
echo "\n"
sleep 1
done

Example blocked response:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>403 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Request blocked.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: xxxxxxxxxxxxxxxxxxxx
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>

Cleaning Up

Remove the resources provisioned by this example with:

Terminal window
aws s3 rm --recursive s3://aws-waf-rate-based-rule-sample-<ACCOUNT_ID>-us-east-1
aws cloudformation delete-stack \
--region us-east-1 \
--stack-name aws-waf-rate-based-rule-sample

Conclusion

Deploying a CloudFront distribution behind a WAF rate-based rule with a limit of 100 requests confirmed that traffic exceeding the threshold gets blocked with a 403 response. The 30-second evaluation window is the detail most likely to cause confusion when testing this rule: a burst of requests well over the limit can still pass for the first half-minute, which looks like the rule isn’t working when it’s actually just evaluating on its own schedule. That built-in latency, along with the 10,000 IP cap on rate limiting, is exactly why the caution at the top frames this as DDoS mitigation rather than DDoS prevention — for anything beyond blunting a single source hammering an endpoint, this rule is meant to complement AWS Shield Advanced, not replace it.

About the author

Takahiro Iwasa

Takahiro Iwasa

Software Developer

This blog shares technical notes from hands-on projects—architecture, implementation, and AWS service integrations.