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.
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.
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-09Description: AWS WAF Rate-based rule sampleResources: 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.htmlDeploy the stack:
aws cloudformation deploy \ --region us-east-1 \ --stack-name aws-waf-rate-based-rule-sample \ --template-file template.yaml
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:
echo '<html><body>Hello World!</body></html>' > index.htmlaws s3 cp index.html s3://aws-waf-rate-based-rule-sample-<ACCOUNT_ID>-us-east-1Testing
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.
for i in `seq 1 130`; do echo "Request: $i" curl https://<CLOUDFRONT_DOMAIN>/ echo "\n" sleep 1doneExample 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:
aws s3 rm --recursive s3://aws-waf-rate-based-rule-sample-<ACCOUNT_ID>-us-east-1aws cloudformation delete-stack \ --region us-east-1 \ --stack-name aws-waf-rate-based-rule-sampleConclusion
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.
Related posts
Sign in with Slack Using Cognito User Pools and OIDC
Federating Cognito user pools with Slack over OIDC and wiring "Sign in with Slack" into a Next.js app with Amplify.
Deploying FastAPI on AWS Lambda with Lambda Web Adapter
This example guides you through the process of developing API backends with FastAPI using Lambda Web Adapter.
API Gateway WebSocket: Implementing a Mock Integration
Building an API Gateway WebSocket API entirely with mock integrations, returning canned responses with no backend Lambda involved.
Uploading to S3 Through CloudFront Pre-Signed URLs
CloudFront signed URLs let you upload to S3 through a custom domain—useful when direct S3 pre-signed URLs are not an option.
AWS EventBridge Scheduler: Starting and Stopping EC2 on a Schedule
Starting and stopping EC2 instances on a cron schedule with EventBridge Scheduler calling the EC2 API directly, no Lambda involved.
