Mitigating DDoS with AWS WAF Rate-Based Rules
Use AWS WAF rate-based rules to limit excessive requests and mitigate application-layer DDoS attacks.
AWS WAF protects web applications from layer 7 attacks such as SQL injection and cross-site scripting (XSS). Its rate-based rules can also limit excessive requests and help mitigate application-layer 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.

As of January 2023, keep the following behavior and limits in mind:
- The minimum configurable rate is 100 requests per five-minute period for each tracked IP address.
- 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.
Configuration
The template below configures a rate limit of 100 requests per five-minute period for each source IP address.
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
Because AWS WAF evaluates the request rate every 30 seconds, send one request per second for at least 130 seconds. After the configured limit is exceeded and evaluated, subsequent requests receive 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
The WAF rate-based rule blocks requests to the CloudFront distribution with a 403 response after a source IP exceeds the configured limit of 100 requests in five minutes.
The 30-second evaluation interval can cause confusion during testing. Requests can continue to pass until AWS WAF performs its next evaluation, even after the five-minute request count exceeds the configured limit.
The evaluation delay and the 10,000-IP tracking limit are why this is a mitigation rather than complete DDoS protection. Rate-based rules complement services such as AWS Shield Advanced; they do not replace them.
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
Containerizing a FastAPI backend and deploying it to a single Lambda function with Lambda Web Adapter and AWS CDK.
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 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.
