コンテンツにスキップ

Custom Rules

Custom rules complement managed rules by addressing application-specific threats and scenarios that pre-built rule groups do not cover. Use custom rules to enforce business logic, handle false positives from managed rules, and protect sensitive endpoints with fine-grained conditions.

For guidance on which AWS Managed Rules to pair with your custom rules, see AWS Managed Rules. For log analysis techniques to identify when custom rules are needed, see Monitoring WAF Rules.

Common Rules for Most Deployments

Most WAF deployments benefit from the following types of custom rules.

Rate-based rules

Rate-based rules are one of the most common and valuable custom rules for any type of application or endpoint. They limit the number of requests a single client can make over a time window, protecting against volumetric abuse, brute force, and DDoS. See Recommended WAF Rule Order for where to place rate-based rules in your protection pack.

Choosing a threshold

To set an appropriate threshold, start by analyzing your existing traffic patterns using the rate-based rule queries in Querying and Visualizing WAF Logs. Those queries show you the peak request rates per source IP across your normal traffic.

Once you know your normal peak, add a 50–100% buffer as your starting threshold. For example, if your highest legitimate source IP sends 1,000 requests in a 5-minute window, start with a threshold of 1,500–2,000. The goal when getting started is to start larger and avoid blocking legitimate traffic — you can tighten the threshold over time as you gain confidence in your traffic patterns.

To safely experiment with lower thresholds without impacting traffic, create multiple rate-based rules with different values. Set the lowest threshold rule to Count mode so it logs matches without blocking, while your higher threshold rule remains in Block mode. Monitor the Count rule's metrics over days or weeks — if it isn't matching legitimate traffic, you can lower your blocking threshold to that value. This approach lets you iterate toward an optimal threshold with zero risk to production traffic.

Choosing an evaluation window

The EvaluationWindowSec parameter controls how long WAF accumulates requests before comparing the count to your threshold. AWS WAF supports windows of 60, 120, 300, and 600 seconds. A shorter window detects bursts faster but is more sensitive to normal traffic spikes; a longer window smooths out short bursts but takes longer to react to sustained abuse.

When to use a shorter window (60–120 seconds): Use a shorter window when you need to detect and stop short, intense bursts quickly. Attackers targeting sensitive endpoints often send hundreds of requests in under a minute and move on. A 300-second window would accumulate those requests across a longer period, potentially diluting the burst below the threshold or reacting too late after the damage is done.

When to use a longer window (300–600 seconds): Use a longer window when your application has legitimate traffic that arrives in short bursts — for example, a client that loads a page and fires many API calls in a few seconds, then goes idle. A 60-second evaluation window might trigger on this normal burst, while a 300-second evaluation window sees it as a small fraction of the total allowed requests only triggering once there is a sustained burst beyond what your application usually does.

Blanket rate-based rule — Limits the total request rate from any single IP across all endpoints. This is your first line of defense against volumetric abuse.

{
  "Name": "blanket-rate-limit",
  "Priority": 1,
  "Statement": {
    "RateBasedStatement": {
      "Limit": 2000,
      "EvaluationWindowSec": 300,
      "AggregateKeyType": "IP"
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "blanket-rate-limit"
  }
}

URI-specific rate-based rule — Applies a stricter rate limit to a sensitive endpoint like /login. Attackers frequently target authentication endpoints with credential stuffing or brute force at rates well below a blanket threshold.

{
  "Name": "login-rate-limit",
  "Priority": 8,
  "Statement": {
    "RateBasedStatement": {
      "Limit": 100,
      "EvaluationWindowSec": 300,
      "AggregateKeyType": "IP",
      "ScopeDownStatement": {
        "ByteMatchStatement": {
          "SearchString": "/login",
          "FieldToMatch": {
            "UriPath": {}
          },
          "TextTransformations": [
            {
              "Priority": 0,
              "Type": "LOWERCASE"
            }
          ],
          "PositionalConstraint": "STARTS_WITH"
        }
      }
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "login-rate-limit"
  }
}

Composite key rate-based rule (IP + cookie) — Aggregates requests by both source IP and a cookie value. This is useful when multiple users share the same IP (e.g., behind a corporate NAT) — the cookie differentiates individual clients so legitimate users aren't penalized by a neighbor's behavior. This works with any cookie your application sets; the example below uses the aws-waf-token cookie but you can replace the cookie name with your own session cookie.

{
  "Name": "composite-ip-token-rate-limit",
  "Priority": 2,
  "Statement": {
    "RateBasedStatement": {
      "Limit": 500,
      "EvaluationWindowSec": 300,
      "AggregateKeyType": "CUSTOM_KEYS",
      "CustomKeys": [
        {
          "IP": {}
        },
        {
          "Cookie": {
            "Name": "aws-waf-token",
            "TextTransformations": [
              {
                "Priority": 0,
                "Type": "NONE"
              }
            ]
          }
        }
      ]
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "composite-ip-token-rate-limit"
  }
}

Rate-based rule scoped to Anonymous IP label — Applies a stricter rate limit to requests that the Anonymous IP List managed rule group has labeled as coming from anonymizing sources. This lets you keep the Anonymous IP rule group in Count mode while still limiting the rate of requests from those sources rather than blocking them outright.

{
  "Name": "rate-limit-anonymous-ips",
  "Priority": 8,
  "Statement": {
    "RateBasedStatement": {
      "Limit": 200,
      "EvaluationWindowSec": 300,
      "AggregateKeyType": "IP",
      "ScopeDownStatement": {
        "LabelMatchStatement": {
          "Scope": "LABEL",
          "Key": "awswaf:managed:aws:anonymous-ip-list:AnonymousIPList"
        }
      }
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "rate-limit-anonymous-ips"
  }
}

Geographic restrictions

Block or rate-limit requests based on geographic origin when your application only serves specific regions. You can use this when your audience is from a specific list of countries or you have a business or compliance requirement to block access from specific countries.

Note: Geo and region based IP evaluation is not perfect, there is no single authoritative source saying which country or region IPs are assigned to. It is uncommon but you should expect will eventually happen that an IP will be mapped to the incorrect geo or region, this is especially true if the IP is close to a geographic boarder. If you are using Geo restrictions based on where you expect your audience to be, consider including near by countries for Geo based controls or not outright blocking those nearby countries. For example, if you only expect clients from US (United States), you may want to consider including CA (Canada) and MX (Mexico) to reduce the risk of blocking users that are close to these boarders.

It is also important to acknowledge that Geo based restrictions are relevative easy to overcome using VPNs, TOR, and cloud/hosting providers. Legitmiate users as well as bad actors use these tool and can appear as if they are coming fom another country based on their IP, bypassing Geo based restrictions. If you require authoritative proof a user is from a specific country/region, you cannot depend on their IP address alone to authoritatively verify this.

Block requests from specific countries — Blocks all requests originating from countries where your application has no legitimate users or comply with compliance requirement such as country embargo lists.

{
  "Name": "block-restricted-countries",
  "Priority": 3,
  "Statement": {
    "GeoMatchStatement": {
      "CountryCodes": ["CN", "RU", "KP"]
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "block-restricted-countries"
  }
}

Allow only specific countries — If your application exclusively serves a known set of regions, block everything else by inverting the match with a NOT statement.

{
  "Name": "allow-only-approved-countries",
  "Priority": 3,
  "Statement": {
    "NotStatement": {
      "Statement": {
        "GeoMatchStatement": {
          "CountryCodes": ["US", "CA", "GB", "DE", "FR"]
        }
      }
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "allow-only-approved-countries"
  }
}

IP allowlists and denylists

Explicitly allow trusted sources or block known bad actors using IP sets.

IP allowlist — Allows requests from trusted sources (e.g., monitoring systems, partner integrations) before any other rules evaluate them. Because Allow is a terminating action, allowed requests bypass all subsequent rules — including managed rules that detect web exploits and DDoS patterns. Even trusted systems should not normally be sending traffic that resembles an attack, so broadly allowing IPs removes a layer of protection you likely still want. Instead of a blanket Allow rule, consider scoping the IP set to specific rules that are causing issues — for example, excluding trusted IPs from a rate-based rule using a scope-down statement, or using a label-based exception to bypass a specific managed rule for those IPs. Reserve Allow-based IP allowlists for cases where you are certain the source should bypass all inspection.

{
  "Name": "allow-trusted-ips",
  "Priority": 4,
  "Statement": {
    "IPSetReferenceStatement": {
      "ARN": "arn:aws:wafv2:us-east-1:123456789012:regional/ipset/trusted-ips/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111"
    }
  },
  "Action": {
    "Allow": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "allow-trusted-ips"
  }
}

IP denylist — Blocks requests from known bad actors. Useful for quickly blocking IPs identified through log analysis or threat intelligence that aren't yet on the AWS IP reputation list.

{
  "Name": "block-denied-ips",
  "Priority": 2,
  "Statement": {
    "IPSetReferenceStatement": {
      "ARN": "arn:aws:wafv2:us-east-1:123456789012:regional/ipset/denied-ips/a1b2c3d4-5678-90ab-cdef-EXAMPLE22222"
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "block-denied-ips"
  }
}

Header validation

Validate expected HTTP request fields to reject obviously malformed or unexpected requests before they reach more expensive managed rule evaluations.

Block requests missing a required header — If your application expects a specific header on all requests (e.g., an API key header, a custom application header), block requests that don't include it.

{
  "Name": "require-api-key-header",
  "Priority": 5,
  "Statement": {
    "NotStatement": {
      "Statement": {
        "SizeConstraintStatement": {
          "FieldToMatch": {
            "SingleHeader": {
              "Name": "x-api-key"
            }
          },
          "ComparisonOperator": "GT",
          "Size": 0,
          "TextTransformations": [
            {
              "Priority": 0,
              "Type": "NONE"
            }
          ]
        }
      }
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "require-api-key-header"
  }
}

Block requests missing a custom application header — If your application injects a custom header at the CDN or load balancer layer (e.g., x-app-origin) to verify requests came through your infrastructure, block any request that doesn't include it.

{
  "Name": "require-app-origin-header",
  "Priority": 5,
  "Statement": {
    "NotStatement": {
      "Statement": {
        "SizeConstraintStatement": {
          "FieldToMatch": {
            "SingleHeader": {
              "Name": "x-app-origin"
            }
          },
          "ComparisonOperator": "GT",
          "Size": 0,
          "TextTransformations": [
            {
              "Priority": 0,
              "Type": "NONE"
            }
          ]
        }
      }
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "require-app-origin-header"
  }
}

Scenario-Based Custom Rule Examples

This section provides realistic examples of common scenarios where custom rules are needed, including:

  • Composite key rate-based rules for more granular rate limiting
  • URI-specific rules for protecting sensitive endpoints
  • Rules that use AWS Managed Rule labels for fine-grained control
  • Combining multiple conditions with AND/OR logic

Using Rule Labels

A label is metadata added to a web request by a matching rule. Use labels to make the results of one rule available to other rules. Labels can be inspected by other rules lower (not above) in the same protection pack by using the label match statement.

Labels added by rules with terminating actions cannot be inspected by other rules. These labels are included in WAF log records and CloudWatch metric dimensions so you can analyze and visualize the behavior of terminating rules.

Labels are commonly used to augment the behavior of a managed rule. The first step is to switch the managed rule's action from Block to Count. Then create another rule below that matches on the managed rule's label along with other conditions that determine if the request should be blocked.

See How to customize behavior of AWS Managed Rules for AWS WAF for more information on using labels.

False Positive Handling

When a WAF rule matches legitimate traffic (a false positive), you need to create an exception that preserves protection for all other traffic while allowing the specific legitimate requests through. This is done using the label-based exception pattern — setting the problematic rule to Count mode and writing a custom rule that re-implements the block with exclusions for the false positive conditions.

For the full exception pattern with examples (single exception, multiple exceptions for the same rule, and Firewall Manager scenarios), see Creating Exceptions.

Handling Large HTTP Requests

AWS WAF has limits on the size and number of HTTP request components it can inspect. See Handling oversize web request components in AWS WAF for more details. The default and max body size that can be inspected depends on the resource AWS WAF is associated with.

  • For CloudFront, API Gateway, Amazon Cognito, App Runner, and Verified Access protection packs, AWS WAF can inspect request bodies up to 16 KB by default. You can increase this limit up to 64 KB in the protection pack configuration, for additional processing fees.
  • For Application Load Balancer and AWS AppSync protection packs, the body inspection limit is fixed at 8 KB. This limit cannot be increased today. If you need a larger amount of body inspected, consider adding CloudFront or using another AWS WAF suppported regional service such as API Gateway.

Allowing Specific Oversized Requests

If specific endpoints legitimately receive oversized requests (e.g., file upload endpoints), you should block oversized requests everywhere except those endpoints rather than allowing them. This keeps the rest of your protection pack rules in effect for those requests.

{
  "Name": "block-oversized-body-except-uploads",
  "Priority": 5,
  "Statement": {
    "AndStatement": {
      "Statements": [
        {
          "SizeConstraintStatement": {
            "FieldToMatch": { "Body": {} },
            "ComparisonOperator": "GT",
            "Size": 8192,
            "TextTransformations": [{ "Priority": 0, "Type": "NONE" }],
            "OversizeHandling": "MATCH"
          }
        },
        {
          "NotStatement": {
            "Statement": {
              "ByteMatchStatement": {
                "SearchString": "/upload",
                "FieldToMatch": { "UriPath": {} },
                "TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
                "PositionalConstraint": "STARTS_WITH"
              }
            }
          }
        }
      ]
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "block-oversized-body-except-uploads"
  }
}