Back to blog
AWSBest PracticesCloud SecurityNetworkingOperations & Compliance

CloudFront Distribution Has No WAF: Why It Matters and How to Fix It

Learn why a CloudFront distribution with no AWS WAF web ACL is a security and cost risk, plus step-by-step CLI, console, and Terraform remediation.

TL;DR

This check flags CloudFront distributions with no AWS WAF web ACL attached, which means malicious requests like SQL injection, bad bots, and L7 floods hit your origin unfiltered. Fix it by creating a web ACL with sensible managed rule groups and associating it with the distribution.

CloudFront sits in front of a lot of internet-facing infrastructure: static sites, single-page apps, API gateways, application load balancers, S3 buckets. It is the first AWS-managed hop a request takes before it reaches your origin. That makes it the ideal place to inspect and drop bad traffic before it ever costs you compute or bandwidth on the backend.

When a distribution has no WAF web ACL attached, none of that inspection happens. Every request that resolves to your CloudFront domain is forwarded toward your origin, good or bad. The cloudfront_nowaf check exists to catch exactly that gap.


What this check detects

The check inspects each CloudFront distribution in your account and looks at the WebACLId field in its configuration. If the field is empty, the distribution has no AWS WAF web ACL associated and the check fails.

A passing distribution has a WAFv2 web ACL ARN (or a legacy WAF Classic web ACL ID) attached at the distribution level. The web ACL is evaluated against viewer requests before CloudFront does anything else with them.

Note: CloudFront is a global service, so its WAF web ACLs live in the us-east-1 region with scope CLOUDFRONT. This is different from regional resources like ALBs and API Gateway, which use web ACLs scoped to REGIONAL in the same region as the resource. Mixing these up is the most common reason an association command fails.


Why it matters

Without a web ACL, CloudFront is a fast, well-distributed pipe straight to your application. That is great for legitimate users and equally great for attackers. Here is what you lose by not filtering at the edge.

Application-layer attacks reach your origin

SQL injection, cross-site scripting, path traversal, and command injection attempts all flow through untouched. If your application has a vulnerability, an unfiltered edge means the attacker gets unlimited free attempts to find and exploit it. AWS managed rule groups block a large fraction of these automated probes before your code ever sees them.

Bots and scrapers run unchecked

Credential stuffing tools, content scrapers, and vulnerability scanners hammer endpoints continuously. Without rate limiting or bot control at the edge, that traffic consumes origin capacity and can drive up your bills.

Layer 7 floods are harder to absorb

CloudFront and Shield Standard give you baseline protection against network and transport layer DDoS. But application-layer floods, like thousands of requests per second to an expensive search endpoint, need WAF rate-based rules to mitigate. No web ACL means no rate-based rules.

Warning: Unfiltered traffic is not just a security problem, it is a cost problem. CloudFront charges for requests and data transfer out. A bot swarm against a distribution with no WAF can generate a meaningful bill while also degrading service for real users.

Compliance gaps

Frameworks like PCI DSS, SOC 2, and the AWS Foundational Security Best Practices standard expect public-facing applications to have edge filtering. An unprotected distribution is a common audit finding.


How to fix it

The fix has two parts: create a WAFv2 web ACL with at least a couple of managed rule groups, then associate it with the distribution. All of this happens in us-east-1.

Step 1: Create a web ACL (CLI)

Start with the AWS managed common rule set and the known bad inputs rule set. Save the following as waf-rules.json:

[
  {
    "Name": "AWS-AWSManagedRulesCommonRuleSet",
    "Priority": 0,
    "Statement": {
      "ManagedRuleGroupStatement": {
        "VendorName": "AWS",
        "Name": "AWSManagedRulesCommonRuleSet"
      }
    },
    "OverrideAction": { "None": {} },
    "VisibilityConfig": {
      "SampledRequestsEnabled": true,
      "CloudWatchMetricsEnabled": true,
      "MetricName": "CommonRuleSet"
    }
  },
  {
    "Name": "AWS-AWSManagedRulesKnownBadInputsRuleSet",
    "Priority": 1,
    "Statement": {
      "ManagedRuleGroupStatement": {
        "VendorName": "AWS",
        "Name": "AWSManagedRulesKnownBadInputsRuleSet"
      }
    },
    "OverrideAction": { "None": {} },
    "VisibilityConfig": {
      "SampledRequestsEnabled": true,
      "CloudWatchMetricsEnabled": true,
      "MetricName": "KnownBadInputs"
    }
  }
]

Then create the web ACL:

aws wafv2 create-web-acl \
  --name cloudfront-edge-acl \
  --scope CLOUDFRONT \
  --region us-east-1 \
  --default-action Allow={} \
  --rules file://waf-rules.json \
  --visibility-config \
    SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=cloudfront-edge-acl

Note the ARN in the response. You will need it for the association.

Tip: Deploy new managed rule groups in count mode first by setting OverrideAction to { "Count": {} }. Watch the sampled requests and CloudWatch metrics for a few days to confirm you are not blocking legitimate traffic, then switch to { "None": {} } to enforce blocking.

Step 2: Associate the web ACL with the distribution

For CloudFront, you attach the web ACL by updating the distribution config rather than calling associate-web-acl (that command is for regional resources). First pull the current config and its ETag:

aws cloudfront get-distribution-config \
  --id E1A2B3C4D5E6F7 \
  --output json > dist-config.json

Edit dist-config.json: set the WebACLId field inside DistributionConfig to the web ACL ARN, and keep the ETag value handy. Then apply the update:

aws cloudfront update-distribution \
  --id E1A2B3C4D5E6F7 \
  --distribution-config file://dist-config-body.json \
  --if-match ETAG_VALUE_FROM_GET

Warning: The update-distribution body must be the DistributionConfig object only, not the full wrapper returned by get-distribution-config. Strip the outer ETag and DistributionConfig keys so the body starts at the config itself. Mismatched payloads are the usual cause of InvalidArgument errors here.

Console steps

  1. Open the CloudFront console and select your distribution.
  2. On the General tab, click Edit under Settings.
  3. Find the AWS WAF web ACL dropdown and choose an existing web ACL, or select Enable security protections to have CloudFront create a starter web ACL for you.
  4. Save changes and wait for the distribution status to return to Deployed.

Terraform

If you manage infrastructure as code, wire the web ACL ARN directly into the distribution:

resource "aws_wafv2_web_acl" "edge" {
  provider = aws.us_east_1
  name     = "cloudfront-edge-acl"
  scope    = "CLOUDFRONT"

  default_action {
    allow {}
  }

  rule {
    name     = "common-rules"
    priority = 0

    override_action {
      none {}
    }

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

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "common-rules"
      sampled_requests_enabled   = true
    }
  }

  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = "cloudfront-edge-acl"
    sampled_requests_enabled   = true
  }
}

resource "aws_cloudfront_distribution" "site" {
  # ... existing distribution config ...

  web_acl_id = aws_wafv2_web_acl.edge.arn
}

Note: The WAFv2 web ACL for CloudFront must be created with a provider aliased to us-east-1, but web_acl_id on the distribution accepts the full ARN, so the distribution resource itself can live in any region.


How to prevent it from happening again

A one-time fix is not enough. New distributions get created by different teams, and a manual checklist will eventually miss one. Bake the requirement into your delivery pipeline.

Catch it in CI with policy as code

If you use Terraform, an OPA or Conftest policy can fail the plan when a distribution has no web_acl_id:

# rego rule sketch
deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_cloudfront_distribution"
  not resource.change.after.web_acl_id
  msg := sprintf("CloudFront distribution %s has no WAF web ACL", [resource.address])
}

Run this against terraform show -json plan.out in your pipeline and block merges that would create an unprotected distribution.

Detect drift continuously

Pipelines only cover resources created through your pipeline. Click-ops distributions and emergency changes slip past. Run a continuous scan across all accounts so a distribution that loses its web ACL gets flagged within hours, not at the next audit. This is exactly what the cloudfront_nowaf check in Lensix does, with no agents to install.

Use an AWS Config rule or SCP guardrail

An AWS Config custom rule can evaluate every CloudFront distribution and mark non-compliant ones. For stricter shops, a service control policy can restrict who is allowed to create distributions at all, funneling creation through approved IaC modules that always attach a web ACL.


Best practices

  • Start with AWS managed rule groups. The Common Rule Set, Known Bad Inputs, and Amazon IP Reputation list give broad coverage with low effort. Add SQL database and PHP rule sets if your stack uses them.
  • Add a rate-based rule. Even a generous limit, say 2000 requests per five minutes per IP, blunts the most common L7 floods without affecting normal users.
  • Always test in count mode first. Managed rules occasionally match legitimate traffic. Observe before you enforce.
  • Enable logging. Send WAF logs to S3 or CloudWatch so you can investigate blocked requests and tune false positives. Without logs you are flying blind.
  • Reuse one web ACL across distributions. A shared edge web ACL keeps rules consistent and reduces per-ACL costs. You can still layer distribution-specific custom rules where needed.
  • Pair WAF with origin restrictions. WAF at the edge is only effective if requests cannot bypass CloudFront and hit your origin directly. Lock down the origin to accept traffic only from CloudFront using origin access control and a custom header check.

Tip: If you protect more than a handful of distributions or multiple accounts, AWS Firewall Manager lets you define one WAF policy and automatically apply it to every in-scope CloudFront distribution, including ones created later. That turns "attach a web ACL" from a recurring task into a one-time setup.

Danger: Do not flip a brand new web ACL straight into blocking mode on a high-traffic production distribution. An overly aggressive rule, especially a misconfigured rate limit or a managed group with unexpected matches, can lock out real users instantly. Roll out in count mode, review sampled requests, and enforce gradually.


Attaching a WAF web ACL to your CloudFront distributions is one of the highest-leverage edge security moves you can make. It filters bad traffic before it costs you compute, bandwidth, or a breach, and the managed rule groups do most of the heavy lifting. Set it up once, gate it in your pipeline, and scan continuously so it stays that way.