Back to blog
AWSBest PracticesCloud SecurityNetworkingServerless

API Gateway Stage Has No WAF: Why It Matters and How to Fix It

Learn why an API Gateway REST stage without an AWS WAF web ACL is risky, and how to create, attach, and enforce WAF protection with CLI, Terraform, and policy-as-code.

TL;DR

This check flags any API Gateway REST API stage that has no AWS WAF web ACL attached, leaving your API exposed to common web attacks like SQL injection, scraping, and volumetric abuse. Fix it by creating a WAFv2 web ACL with managed rule groups and associating it with the stage ARN.

Your API Gateway endpoint is a front door. It accepts requests from anywhere on the internet, parses them, and passes them to Lambda functions, EC2 backends, or other AWS services. Without a web application firewall in front of that door, every malformed payload, credential-stuffing bot, and Layer 7 flood hits your application logic directly.

The apigw_nowaf check looks at each deployed stage of your REST APIs and confirms whether an AWS WAF web ACL is associated with it. Stages without one get flagged.


What this check detects

API Gateway REST APIs are deployed in stages (for example, prod, staging, v1). Each stage is an independently addressable endpoint. AWS WAF web ACLs are attached at the stage level, not at the API level, so you can apply different rule sets to production and development environments.

This check evaluates every stage and reports the ones that have no webAclArn associated. A flagged stage means traffic reaches your integration without inspection.

Note: This check applies to REST APIs (the apigateway service, also called API Gateway v1). HTTP APIs and WebSocket APIs (API Gateway v2) do not support WAF directly. For those, you typically front them with CloudFront and attach WAF there instead.


Why it matters

API Gateway gives you throttling and authorization, but it does not inspect the content of requests. That gap is exactly what WAF closes. Here is what an unprotected stage is exposed to.

Injection and exploit payloads

If your backend builds SQL queries, shell commands, or template strings from request data, attackers can probe for injection flaws at will. AWS managed rule groups like AWSManagedRulesSQLiRuleSet and AWSManagedRulesKnownBadInputsRuleSet block the most common patterns before they reach your code.

Bots, scraping, and credential stuffing

Public APIs that return data or accept logins are magnets for automated abuse. Bots scrape pricing data, enumerate user IDs, and replay stolen credentials. WAF rate-based rules and the bot control managed group give you a way to slow them down or block them outright.

Layer 7 floods

API Gateway throttling protects your account quota, but a flood of expensive requests can still rack up Lambda invocation costs and downstream database load. A rate-based WAF rule that caps requests per source IP gives you a cheaper, faster cutoff.

Warning: Without WAF, a single misbehaving client or attacker can drive up your bill through high-volume requests to a pay-per-use backend. Throttling alone limits concurrency, not the financial blast radius of sustained abuse.

Compliance findings

Frameworks like PCI DSS and SOC 2 expect a web application firewall in front of internet-facing applications that handle sensitive data. An unprotected production stage is a common audit finding.


How to fix it

Fixing this involves two steps: create a WAFv2 web ACL with sensible rules, then associate it with the stage. WAFv2 is the current generation, so use that rather than the classic WAF API.

Step 1: Create a web ACL

API Gateway is a regional resource, so the web ACL scope must be REGIONAL and created in the same region as the API. Start with a rule set built from AWS managed rule groups.

{
  "Name": "apigw-prod-acl",
  "Scope": "REGIONAL",
  "DefaultAction": { "Allow": {} },
  "Rules": [
    {
      "Name": "AWSCommonRules",
      "Priority": 0,
      "OverrideAction": { "None": {} },
      "Statement": {
        "ManagedRuleGroupStatement": {
          "VendorName": "AWS",
          "Name": "AWSManagedRulesCommonRuleSet"
        }
      },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "AWSCommonRules"
      }
    },
    {
      "Name": "RateLimit",
      "Priority": 1,
      "Action": { "Block": {} },
      "Statement": {
        "RateBasedStatement": {
          "Limit": 2000,
          "AggregateKeyType": "IP"
        }
      },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "RateLimit"
      }
    }
  ],
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "apigw-prod-acl"
  }
}

Save that as acl.json and create the ACL:

aws wafv2 create-web-acl \
  --region us-east-1 \
  --cli-input-json file://acl.json

The response includes the ACL ARN and Id. You need the ARN for the next step.

Step 2: Associate the web ACL with the stage

The stage ARN follows a fixed format. Substitute your region, account ID, REST API ID, and stage name:

aws wafv2 associate-web-acl \
  --region us-east-1 \
  --web-acl-arn "arn:aws:wafv2:us-east-1:111122223333:regional/webacl/apigw-prod-acl/abcd1234-..." \
  --resource-arn "arn:aws:apigateway:us-east-1::/restapis/a1b2c3d4e5/stages/prod"

Warning: Managed rule groups occasionally block legitimate traffic. Before enforcing in production, set the web ACL default action and managed rules to count mode, watch the CloudWatch metrics and sampled requests for a few days, then switch to block. Flipping straight to block on a busy API can cause real outages.

Console steps

  1. Open the WAF & Shield console and choose Create web ACL.
  2. Set the resource type to Regional resources and pick your region.
  3. Add the AWS managed rule groups you want (Common Rule Set, Known Bad Inputs, SQLi).
  4. Add a rate-based rule with a reasonable per-IP limit.
  5. On the associations step, select your API Gateway stage and create the ACL.

Terraform

If you manage infrastructure as code, define both the ACL and the association so the protection is version controlled and reproducible.

resource "aws_wafv2_web_acl" "apigw" {
  name  = "apigw-prod-acl"
  scope = "REGIONAL"

  default_action {
    allow {}
  }

  rule {
    name     = "AWSCommonRules"
    priority = 0

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        vendor_name = "AWS"
        name        = "AWSManagedRulesCommonRuleSet"
      }
    }

    visibility_config {
      sampled_requests_enabled   = true
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSCommonRules"
    }
  }

  visibility_config {
    sampled_requests_enabled   = true
    cloudwatch_metrics_enabled = true
    metric_name                = "apigw-prod-acl"
  }
}

resource "aws_wafv2_web_acl_association" "apigw" {
  resource_arn = aws_api_gateway_stage.prod.arn
  web_acl_arn  = aws_wafv2_web_acl.apigw.arn
}

Tip: You can attach one web ACL to many stages and APIs across a region. Maintain a small set of standard ACLs (for example, one for public APIs and one stricter set for sensitive ones) instead of a unique ACL per stage. Fewer ACLs means fewer rule sets to keep current.


How to prevent it from happening again

One-off fixes drift. New APIs ship, new stages get deployed, and someone forgets the WAF association. Push the requirement into the pipeline.

Catch it in CI/CD with policy-as-code

If you use Terraform, scan plans with a tool like Checkov or OPA before apply. A Checkov policy can require an aws_wafv2_web_acl_association for every public API Gateway stage. Fail the build when one is missing.

checkov -d . --check CKV2_AWS_29

Check CKV2_AWS_29 verifies that API Gateway is protected by WAF. Wire it into your merge pipeline so unprotected stages never reach an account.

Enforce account-wide with Firewall Manager

For organizations with many accounts, AWS Firewall Manager lets you define a WAF policy once and auto-apply it to all in-scope API Gateway stages. New stages that match the policy get the web ACL attached automatically, with no per-team action required.

Note: Firewall Manager requires AWS Organizations and a designated administrator account. It is the most reliable way to guarantee coverage at scale, because it remediates drift continuously rather than at deploy time.

Continuous monitoring

Run the apigw_nowaf check on a schedule in Lensix so any stage that slips through your pipeline gets surfaced quickly. Pair it with an alert so a missing association on a production stage becomes a tracked finding rather than a silent gap.


Best practices

  • Start in count mode. Always tune managed rules against real traffic before enforcing, then promote to block.
  • Layer your rules. Combine the Common Rule Set, Known Bad Inputs, and a SQLi rule group with a rate-based rule. Defense comes from the combination, not any single group.
  • Set realistic rate limits. The rate-based limit is per five-minute window per IP. Base it on your real peak traffic plus headroom, not a guess.
  • Enable logging. Send WAF logs to CloudWatch, S3, or Kinesis Data Firehose so you can investigate blocks and refine rules. Logging is off by default.
  • Do not skip non-production stages. Staging endpoints are often internet-facing and share backend code with production, making them an easy reconnaissance target.
  • Review managed rule updates. AWS updates managed rule groups over time. Watch your block metrics after updates so a new rule does not silently break a client.

A WAF is not a substitute for secure code, input validation, and proper authorization. It is a fast, broad layer that buys you time and blocks the noise so your application defenses handle the rest.

Attaching a web ACL to every public API Gateway stage is one of the cheapest, highest-leverage controls you can apply. The check is binary, the fix is two commands, and the payoff is keeping a whole class of attacks away from your application logic.