REST API Rate-Limit Rule Syntax Across WAF Vendors

June 5, 2026 | 14 min read | WAF Configuration Reference

Every WAF vendor has different syntax for the same rate limiting concept. This is a reference for engineers who need to implement identical rate limiting policies across different platforms -- whether because an organization uses multiple WAFs across its stack, or because a DDactic assessment recommendation needs to be translated into the specific syntax the target environment uses.

The scenario used throughout: rate limit the REST API endpoint /api/v1/transactions to 30 POST requests per minute per authenticated user identifier, with a 1-hour block on violation, returning 429 with a Retry-After: 60 header.

Cloudflare WAF (Rules Engine)

# Cloudflare — Rate limit /api/v1/transactions POST to 30/min per user token
# Rule expression (WAF Custom Rules):
(http.request.uri.path eq "/api/v1/transactions" and http.request.method eq "POST")

# Rate limiting action settings:
# Characteristics: http.request.headers["authorization"]
#   (hash of the Authorization header value, stable per token)
# Period: 60 seconds
# Requests per period: 30
# Mitigation action: Block
# Mitigation timeout: 3600 seconds
# Response: 429

# Add Retry-After header via Transform Rule:
# (http.response.code eq 429)
# Set: Retry-After = "60"

# Via Terraform (cloudflare_ruleset):
resource "cloudflare_ruleset" "api_rate_limit" {
  zone_id = var.zone_id
  name    = "API Rate Limiting"
  kind    = "zone"
  phase   = "http_ratelimit"
  rules {
    expression = "(http.request.uri.path eq \"/api/v1/transactions\" and http.request.method eq \"POST\")"
    action     = "block"
    ratelimit {
      characteristics      = ["http.request.headers[\"authorization\"]"]
      period               = 60
      requests_per_period  = 30
      mitigation_timeout   = 3600
    }
  }
}

AWS WAF v2

# AWS WAF v2 — Rate limit via rate-based rule
# Limitation: minimum window is 300 seconds (5 min); set limit at 5x per-minute value
# For 30/min: set limit = 150 over 300 seconds

aws wafv2 create-rule-group \
  --name "TransactionRateLimit" \
  --scope REGIONAL \
  --capacity 25 \
  --rules '[{
    "Name": "LimitTransactionPost",
    "Priority": 1,
    "Action": {"Block": {"CustomResponse": {
      "ResponseCode": 429,
      "ResponseHeaders": [{"Name":"Retry-After","Value":"60"}]
    }}},
    "Statement": {
      "RateBasedStatement": {
        "Limit": 150,
        "AggregateKeyType": "HEADER",
        "AggregateKeyConfig": {
          "Header": {"Name": "Authorization", "OversizeHandling": "COUNT"}
        },
        "ScopeDownStatement": {
          "AndStatement": {"Statements": [
            {"ByteMatchStatement": {
              "SearchString": "/api/v1/transactions",
              "FieldToMatch": {"UriPath": {}},
              "TextTransformations": [{"Priority":0,"Type":"NONE"}],
              "PositionalConstraint": "EXACTLY"
            }},
            {"ByteMatchStatement": {
              "SearchString": "POST",
              "FieldToMatch": {"Method": {}},
              "TextTransformations": [{"Priority":0,"Type":"NONE"}],
              "PositionalConstraint": "EXACTLY"
            }}
          ]}
        }
      }
    },
    "VisibilityConfig": {
      "SampledRequestsEnabled": true,
      "CloudWatchMetricsEnabled": true,
      "MetricName": "LimitTransactionPost"
    }
  }]'

Nginx

# nginx.conf
http {
    # Key on Authorization header value (or use $remote_addr for IP-based)
    map $http_authorization $auth_key {
        default $http_authorization;
        ""      $binary_remote_addr;  # fallback to IP if no auth header
    }

    limit_req_zone $auth_key zone=transactions:20m rate=30r/m;

    server {
        location = /api/v1/transactions {
            limit_except POST { deny all; }
            limit_req zone=transactions burst=5 nodelay;
            limit_req_status 429;
            add_header Retry-After 60 always;
        }
    }
}

Akamai Kona Site Defender

# Akamai rate policy via API (simplified JSON body):
POST /appsec/v1/configs/{configId}/versions/{versionNumber}/rate-policies
{
  "name": "TransactionPostLimit",
  "description": "30 POSTs per minute per auth token on /api/v1/transactions",
  "averageThreshold": 30,
  "burstThreshold": 35,
  "clientIdentifier": "REQUEST_HEADERS:Authorization",
  "requestType": "CLIENT_REQ",
  "path": {
    "values": ["/api/v1/transactions"],
    "positiveMatch": true
  },
  "method": {
    "values": ["POST"],
    "positiveMatch": true
  },
  "action": "deny",
  "penaltyPeriod": 3600,
  "timeWindow": 60
}

Key difference: time window granularity

Cloudflare and Nginx allow per-minute (60-second) windows. AWS WAF v2's minimum rate-based window is 5 minutes -- multiply your per-minute threshold by 5. Akamai supports per-minute windows directly via the timeWindow parameter. This difference matters for burst protection: a 5-minute window allows 5x the per-minute rate during the first minute of an attack before the limit fires.

Get Vendor-Specific Hardening Commands for Your Environment

DDactic assessments deliver per-endpoint rate limit recommendations in the exact syntax of your WAF vendor, ready to implement.

Run a Free Scan
WAF SyntaxRate LimitingCloudflareAWS WAFNginxAkamaiREST API