This check flags Application Load Balancers that have no AWS WAF web ACL attached, leaving your apps exposed to common web attacks like SQL injection and L7 floods. Fix it by associating a WAFv2 web ACL with the ALB and starting from the AWS managed rule groups.
An Application Load Balancer (ALB) sits at the front door of most web applications running on AWS. It terminates TLS, routes requests to your targets, and handles raw traffic from the public internet. What it does not do on its own is inspect that traffic for malicious patterns. That job belongs to AWS WAF, and when no web ACL is attached, every request reaches your application untouched.
The lb_nowaf check in the lb_checks module looks for exactly this gap: an internet-facing or internal ALB with no AWS WAF web ACL associated with it.
What this check detects
The check inspects each Application Load Balancer in your account and verifies whether a WAFv2 web ACL is associated with it. If the association is missing, the ALB is flagged.
Two things are worth clarifying up front:
- It applies to ALBs, not NLBs. Network Load Balancers operate at layer 4 and cannot have a WAF attached. AWS WAF integrates with ALBs, API Gateway, AppSync, CloudFront, Cognito, and a few other layer 7 services.
- It checks for association, not rule quality. An empty web ACL with no rules still passes this particular check. That is by design, since the goal here is to catch load balancers with zero protection. Rule coverage is a separate concern worth auditing on its own.
Note: AWS WAF has two versions. WAFv2 (the current API, sometimes shown as "AWS WAF") is what you should be using. WAF Classic is deprecated and cannot be associated with newer resources. All examples here use WAFv2.
Why it matters
Without a WAF, your application code is the only thing standing between the public internet and your data. That is a fragile position. Most real-world breaches at the web tier do not require sophisticated exploits, they require a single unpatched endpoint and an automated scanner.
Here are the concrete risks an unprotected ALB carries:
- Injection attacks. SQL injection, command injection, and cross-site scripting payloads pass straight through to your backend. AWS managed rules block the most common variants before they ever reach your code.
- Bot and scraper traffic. Credential stuffing, content scraping, and inventory hoarding all run on automated bots. With no WAF, you have no rate limiting or bot control at the edge.
- Layer 7 floods. AWS Shield Standard protects against volumetric layer 3 and 4 attacks for free, but application-layer floods (millions of expensive HTTP requests) need a WAF rate-based rule to mitigate.
- Known bad inputs. Log4Shell, path traversal attempts, and scanner signatures get caught by the managed "Known Bad Inputs" rule group, buying you time to patch.
There is also a compliance angle. PCI DSS requirement 6.4.2 effectively mandates a WAF or equivalent for public-facing web applications. SOC 2 and ISO 27001 auditors routinely ask how you protect the application layer. "We have an ALB" is not an answer they accept.
Warning: AWS WAF is billed per web ACL, per rule, and per million requests inspected. A high-traffic ALB with several managed rule groups can run into hundreds of dollars a month. Estimate your request volume before enabling, and prefer fewer, well-chosen rule groups over enabling everything.
How to fix it
Fixing this involves two steps: create a web ACL with at least a baseline set of rules, then associate it with the ALB. You can do this in the console, the CLI, or infrastructure as code.
Option 1: Console
- Open the AWS WAF console and choose Create web ACL.
- Set the resource type to Regional resources and pick the region your ALB lives in.
- Add your ALB under Associated AWS resources.
- Under Rules, add the managed rule groups AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet to start.
- Set the default action to Allow, review, and create.
Option 2: AWS CLI
First create the web ACL with two managed rule groups. Save this rules definition to rules.json:
[
{
"Name": "AWS-CommonRuleSet",
"Priority": 0,
"OverrideAction": { "None": {} },
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesCommonRuleSet"
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "CommonRuleSet"
}
},
{
"Name": "AWS-KnownBadInputs",
"Priority": 1,
"OverrideAction": { "None": {} },
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesKnownBadInputsRuleSet"
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "KnownBadInputs"
}
}
]
Then create the web ACL:
aws wafv2 create-web-acl \
--name alb-baseline-acl \
--scope REGIONAL \
--region us-east-1 \
--default-action Allow={} \
--rules file://rules.json \
--visibility-config \
SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=alb-baseline-acl
Note the ARN returned in the response. Now associate it with your ALB. The resource ARN is the ALB's full ARN:
aws wafv2 associate-web-acl \
--web-acl-arn "arn:aws:wafv2:us-east-1:111122223333:regional/webacl/alb-baseline-acl/abcd1234-..." \
--resource-arn "arn:aws:elasticloadbalancing:us-east-1:111122223333:loadbalancer/app/my-alb/0123456789abcdef" \
--region us-east-1
Tip: Deploy new managed rule groups in count mode first. Set the rule action override to Count, watch the CloudWatch sampled requests for a week, then switch to block. This catches false positives before they break legitimate traffic.
Option 3: Terraform
If you manage infrastructure as code, define the web ACL and its association alongside the ALB so it is never created without protection:
resource "aws_wafv2_web_acl" "alb" {
name = "alb-baseline-acl"
scope = "REGIONAL"
default_action {
allow {}
}
rule {
name = "AWS-CommonRuleSet"
priority = 0
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
sampled_requests_enabled = true
cloudwatch_metrics_enabled = true
metric_name = "CommonRuleSet"
}
}
visibility_config {
sampled_requests_enabled = true
cloudwatch_metrics_enabled = true
metric_name = "alb-baseline-acl"
}
}
resource "aws_wafv2_web_acl_association" "alb" {
resource_arn = aws_lb.app.arn
web_acl_arn = aws_wafv2_web_acl.alb.arn
}
Danger: Switching a managed rule group straight to block mode on a live, high-traffic ALB without testing can drop legitimate requests at scale. Always validate in count mode against real traffic first, and have a rollback plan to disassociate the web ACL if something goes wrong.
How to prevent it from happening again
One-off remediation does not stick. The fix needs to be enforced at the point where load balancers get created.
Gate it in CI/CD with policy as code
If you use Terraform, add a check with OPA Conftest or Checkov that fails the plan when an ALB has no associated web ACL. Here is a Rego rule that requires the association resource to exist:
package main
deny[msg] {
some name
input.resource.aws_lb[name].load_balancer_type == "application"
not has_waf_association(name)
msg := sprintf("ALB '%s' has no WAF web ACL association", [name])
}
has_waf_association(alb) {
some assoc
input.resource.aws_wafv2_web_acl_association[assoc].resource_arn
}
Enforce at runtime with AWS Firewall Manager
For multi-account organizations, Firewall Manager lets you write a single WAF policy that automatically applies a web ACL to any in-scope ALB across every account. New load balancers inherit protection without anyone remembering to attach it.
Tip: Firewall Manager can auto-remediate. Set the policy to automatically create and associate web ACLs, and it will fix non-compliant ALBs on a schedule rather than just reporting them.
Detect drift continuously
Use AWS Config with the managed rule alb-waf-enabled, or rely on Lensix to flag lb_nowaf on every scan. Either way you want a recurring signal, not a quarterly manual audit, so a newly launched ALB never sits unprotected for weeks.
Best practices
- Start with the AWS managed rule groups. Common Rule Set plus Known Bad Inputs covers the majority of opportunistic attacks with almost no tuning. Layer on SQL Injection and bot control rule groups as your needs grow.
- Add a rate-based rule. A simple rate limit of, say, 2000 requests per five minutes per IP blunts most layer 7 floods and brute-force attempts.
- Turn on logging. Send WAF logs to CloudWatch Logs, S3, or Kinesis Data Firehose so you can investigate blocked requests and refine rules. Without logs you are blocking blind.
- Consider CloudFront in front of the ALB. Attaching the WAF at the CloudFront edge blocks malicious traffic closer to the source and reduces load on your origin. For internet-facing apps this is often the better architecture.
- Review rules quarterly. AWS updates managed rule groups, and your traffic patterns change. Revisit your count-mode samples and false positive exclusions on a regular cadence.
- Do not treat WAF as your only defense. A WAF reduces risk, it does not eliminate it. Keep your application patched, validate input server-side, and follow least privilege on your targets.
Associating a web ACL takes a few minutes and a baseline set of managed rules costs little. Given that an unprotected ALB exposes your entire application to the open internet, this is one of the highest-leverage fixes you can make at the web tier.

