Back to blog
AWSBest PracticesCloud SecurityMonitoring & LoggingNetworking

Load Balancer Access Logging Disabled on AWS: Why It Matters and How to Fix It

Learn why disabled AWS load balancer access logging leaves you blind during incidents, plus step-by-step CLI, Terraform, and policy-as-code fixes.

TL;DR

This check flags AWS load balancers that have access logging turned off, leaving you blind during incident investigations and traffic analysis. Enable access logs to an S3 bucket so every request is captured and stored for forensics.

Access logs are one of those things you never think about until you need them, and by then it is too late. When a customer reports a strange 502, when your security team is reconstructing a breach timeline, or when you are trying to figure out which client IPs hammered your API during an outage, the load balancer access log is often the single source of truth. If logging was off, that data never existed.

The Lensix lb_accesslogging check (module lb_checks) looks at your AWS load balancers and reports any that do not have access logging enabled. It is a low-effort, high-value fix that most teams skip during initial setup and never revisit.


What this check detects

The check inspects your Application Load Balancers (ALBs) and Classic Load Balancers and verifies whether access logging is enabled. For ALBs and Network Load Balancers, AWS exposes this through the access_logs.s3.enabled attribute. When that attribute is false or unset, the check fails.

Access logs capture detailed information about requests sent to your load balancer, including:

  • Client IP address and port
  • Request timestamp and processing time
  • The target that handled the request
  • HTTP method, path, and protocol
  • Response status codes from both the load balancer and the target
  • TLS cipher and protocol version (for HTTPS listeners)
  • The User-Agent and trace identifiers

Note: Access logs are different from CloudWatch metrics. Metrics give you aggregate counts and latencies, but they do not record individual requests. Only access logs let you trace a specific request back to a specific client.


Why it matters

Without access logging, you lose visibility at exactly the layer where most external traffic enters your environment. Here is what that costs you in practice.

Incident response goes blind

Suppose an attacker probes your application with a credential-stuffing attack or scrapes an exposed endpoint. With access logs, your security team can pull the offending source IPs, see which paths were targeted, and measure the volume of the attack. Without them, you are left guessing. The window to act on this data is short, and you cannot retroactively turn logging on to recover requests that already happened.

Compliance gaps

Frameworks like PCI DSS, SOC 2, and HIPAA expect you to log access to systems that handle sensitive data. A load balancer fronting a payment API or a health records service with no access logs is an easy finding for an auditor and a real gap in your evidence trail.

Debugging production issues

Access logs record the elb_status_code and target_status_code separately. That distinction tells you whether a 504 came from your application timing out or from the load balancer never reaching a healthy target. That single field can save hours during an outage.

Warning: Access logs are delivered on a best-effort basis and are not real-time. AWS publishes them in batches roughly every 5 minutes. They are excellent for forensics and analysis but should not be your only mechanism for real-time alerting.


How to fix it

Enabling access logging takes two steps: create an S3 bucket with the right permissions, then point the load balancer at it.

Step 1: Create and configure the S3 bucket

First, create a bucket to hold the logs. Pick a region close to your load balancer to keep delivery latency low.

aws s3api create-bucket \
  --bucket my-org-alb-access-logs \
  --region us-east-1

The load balancer service needs permission to write to this bucket. The principal you grant access to depends on your region. For most modern regions, AWS uses the logdelivery.elasticloadbalancing.amazonaws.com service principal. For older regions, you grant access to a specific ELB account ID. The example below uses the service principal approach, which is the current recommendation.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "logdelivery.elasticloadbalancing.amazonaws.com"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::my-org-alb-access-logs/AWSLogs/123456789012/*"
    }
  ]
}

Save that as bucket-policy.json and apply it:

aws s3api put-bucket-policy \
  --bucket my-org-alb-access-logs \
  --policy file://bucket-policy.json

Note: Replace 123456789012 with your AWS account ID. If you are in an older region such as us-west-1 launched before August 2022, check the AWS docs for the regional ELB account ID and grant that account the s3:PutObject permission instead of the service principal.

Step 2: Enable logging on the load balancer

For an Application or Network Load Balancer, set the access log attributes. You will need the load balancer ARN, which you can find with aws elbv2 describe-load-balancers.

aws elbv2 modify-load-balancer-attributes \
  --load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/50dc6c495c0c9188 \
  --attributes \
    Key=access_logs.s3.enabled,Value=true \
    Key=access_logs.s3.bucket,Value=my-org-alb-access-logs \
    Key=access_logs.s3.prefix,Value=my-alb

For a Classic Load Balancer, the command is different:

aws elb modify-load-balancer-attributes \
  --load-balancer-name my-classic-lb \
  --load-balancer-attributes "{\"AccessLog\":{\"Enabled\":true,\"S3BucketName\":\"my-org-alb-access-logs\",\"EmittedInterval\":5,\"S3BucketPrefix\":\"my-classic-lb\"}}"

Console steps

If you prefer the console:

  1. Open the EC2 console and go to Load Balancers.
  2. Select your load balancer and open the Attributes tab.
  3. Click Edit, toggle Access logs on, and choose your S3 bucket and prefix.
  4. Save. AWS validates that it can write to the bucket before enabling.

Terraform

If you manage infrastructure as code, enable logging directly in the resource definition:

resource "aws_lb" "main" {
  name               = "my-alb"
  internal           = false
  load_balancer_type = "application"
  subnets            = var.public_subnet_ids
  security_groups    = [aws_security_group.alb.id]

  access_logs {
    bucket  = aws_s3_bucket.alb_logs.id
    prefix  = "my-alb"
    enabled = true
  }
}

Tip: Set an S3 lifecycle rule to transition logs to S3 Glacier after 30 days and expire them after your retention requirement. Access logs accumulate quickly on high-traffic load balancers, and storage costs can creep up if you keep everything in Standard storage forever.


How to prevent it from happening again

Fixing one load balancer is easy. Making sure the next 50 are created with logging on is the real win. Bake the requirement into your provisioning pipeline so it cannot be skipped.

Catch it in CI with a policy check

If you use Terraform, a tool like Checkov, tfsec, or OPA can fail a pull request when a load balancer is defined without access logs. Here is a Checkov example you can drop into a CI step:

checkov -d ./infra --check CKV_AWS_91

CKV_AWS_91 specifically verifies that ELB access logging is enabled. Wire this into your pipeline so a non-compliant plan never reaches an apply.

Enforce with an OPA Rego policy

package terraform.lb

deny[msg] {
  resource := input.resource.aws_lb[name]
  not resource.access_logs[_].enabled
  msg := sprintf("Load balancer '%s' must have access logging enabled", [name])
}

Detect drift continuously

Policy-as-code stops bad config at the gate, but resources still drift when someone makes a console change during an incident. A continuous check, whether through AWS Config rules or a platform like Lensix, catches load balancers that had logging turned off after the fact and surfaces them before they become an audit finding.

Tip: Pair the CI gate with a scheduled scan. The CI gate covers what you provision, and the scan covers everything else, including resources created by other teams, click-ops changes, and legacy infrastructure that predates your IaC.


Best practices

  • Centralize logs in one account. Ship access logs from every account into a dedicated logging account with a locked-down S3 bucket. This separates log storage from the workloads being logged, which is exactly what you want if a workload account is compromised.
  • Encrypt the log bucket. Use SSE-S3 or SSE-KMS so logs are encrypted at rest. Note that ALB access logs require SSE-S3 or an S3-managed key, since the delivery service does not support customer-managed KMS keys for ALB logs at the time of writing.
  • Block public access on the bucket. Access logs can contain client IPs and request paths. Enable S3 Block Public Access and confirm the bucket policy only grants write access to the ELB delivery principal.
  • Set retention deliberately. Match your retention to your compliance and forensic needs, then use lifecycle rules to control cost.
  • Make the logs queryable. Point Amazon Athena at the log bucket so you can run SQL queries during an investigation instead of grepping through raw files. A partitioned Athena table over your access logs turns a painful manual search into a 30-second query.
  • Enable logging on internal load balancers too. Internal-facing load balancers handle east-west traffic that is just as relevant during a breach as external traffic. Do not assume internal means trusted.

Access logging is cheap insurance. The storage cost is minimal, the configuration is a one-time setup, and the data is irreplaceable when you actually need it. Enable it everywhere, enforce it in your pipeline, and verify it continuously.