Elastic Load Balancer Access Logs to S3
Overview
This check verifies that your Elastic Load Balancers (ELBs) have access logging enabled and configured to deliver logs to Amazon S3. Access logs capture detailed information about requests sent to your load balancer, including client IP addresses, request paths, HTTP status codes, and TLS details.
Risk
Without access logs enabled, you lose visibility into traffic flowing through your load balancer. This creates several security and operational risks:
- Reduced threat detection: You cannot identify reconnaissance attempts, suspicious traffic patterns, or exploitation attempts
- Impaired incident response: When security incidents occur, you lack the forensic data needed to understand what happened and when
- Compliance gaps: Many compliance frameworks require logging of network traffic for audit purposes
- Delayed outage diagnosis: Troubleshooting availability issues becomes more difficult without request-level data
Remediation Steps
Prerequisites
- Access to the AWS Console with permissions to modify load balancer settings
- An S3 bucket in the same region as your load balancer (you can create one during setup)
S3 bucket policy requirements
Your S3 bucket needs a policy that allows Elastic Load Balancing to write logs. AWS provides region-specific ELB account IDs for this purpose.
Bucket policy for Application/Network Load Balancers:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "logdelivery.elasticloadbalancing.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<your-bucket-name>/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
}
]
}
Bucket policy for Classic Load Balancers:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::127311923021:root"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<your-bucket-name>/*"
}
]
}
Note: The account ID 127311923021 is the ELB service account for us-east-1. Other regions use different account IDs.
AWS Console Method
For Application Load Balancers (ALB) and Network Load Balancers (NLB):
- Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/
- In the left navigation pane, choose Load Balancers
- Select the load balancer you want to configure
- Choose the Attributes tab
- Click Edit in the Attributes section
- Under Monitoring, find Access logs and toggle it to On
- For S3 URI, enter the path where logs should be stored (e.g.,
s3://my-elb-logs-bucket/my-app/) - Click Save changes
For Classic Load Balancers:
- Open the Amazon EC2 console at https://console.aws.amazon.com/ec2/
- In the left navigation pane, choose Load Balancers
- Select your Classic Load Balancer
- Choose the Attributes tab
- Click Edit attributes
- Select Enable access logs
- Specify the S3 bucket and optional prefix for log storage
- Choose the logging interval (5 or 60 minutes)
- Click Save
AWS CLI (optional)
For Application Load Balancers (ALB) and Network Load Balancers (NLB):
First, get your load balancer ARN:
aws elbv2 describe-load-balancers \
--region us-east-1 \
--query "LoadBalancers[*].[LoadBalancerName,LoadBalancerArn]" \
--output table
Enable access logs:
aws elbv2 modify-load-balancer-attributes \
--region us-east-1 \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 \
--attributes \
Key=access_logs.s3.enabled,Value=true \
Key=access_logs.s3.bucket,Value=my-elb-logs-bucket \
Key=access_logs.s3.prefix,Value=my-app
For Classic Load Balancers:
aws elb modify-load-balancer-attributes \
--region us-east-1 \
--load-balancer-name my-classic-lb \
--load-balancer-attributes "{\"AccessLog\":{\"Enabled\":true,\"S3BucketName\":\"my-elb-logs-bucket\",\"EmitInterval\":60,\"S3BucketPrefix\":\"my-app\"}}"
Parameters:
S3BucketName: The name of your S3 bucket (required when enabling)EmitInterval: How often to publish logs -5or60minutes (default: 60)S3BucketPrefix: Optional path prefix within the bucket
CloudFormation (optional)
Application Load Balancer with access logs:
AWSTemplateFormatVersion: '2010-09-09'
Description: ALB with access logging enabled
Parameters:
LogsBucketName:
Type: String
Description: Name of the S3 bucket for access logs
Resources:
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: my-application-lb
Type: application
Scheme: internet-facing
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
SecurityGroups:
- !Ref ALBSecurityGroup
LoadBalancerAttributes:
- Key: access_logs.s3.enabled
Value: 'true'
- Key: access_logs.s3.bucket
Value: !Ref LogsBucketName
- Key: access_logs.s3.prefix
Value: alb-logs
# S3 Bucket for logs with required policy
ELBLogsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref LogsBucketName
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
LifecycleConfiguration:
Rules:
- Id: ExpireOldLogs
Status: Enabled
ExpirationInDays: 90
ELBLogsBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref ELBLogsBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: logdelivery.elasticloadbalancing.amazonaws.com
Action: s3:PutObject
Resource: !Sub 'arn:aws:s3:::${LogsBucketName}/*'
Condition:
StringEquals:
s3:x-amz-acl: bucket-owner-full-control
Classic Load Balancer with access logs:
AWSTemplateFormatVersion: '2010-09-09'
Description: Classic Load Balancer with access logging enabled
Parameters:
LogsBucketName:
Type: String
Description: Name of the S3 bucket for access logs
Resources:
ClassicLoadBalancer:
Type: AWS::ElasticLoadBalancing::LoadBalancer
Properties:
LoadBalancerName: my-classic-lb
Scheme: internet-facing
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
SecurityGroups:
- !Ref ELBSecurityGroup
Listeners:
- LoadBalancerPort: 80
InstancePort: 80
Protocol: HTTP
AccessLoggingPolicy:
Enabled: true
S3BucketName: !Ref LogsBucketName
S3BucketPrefix: classic-lb-logs
EmitInterval: 60
Terraform (optional)
Application Load Balancer with access logs:
# S3 bucket for ELB access logs
resource "aws_s3_bucket" "elb_logs" {
bucket = "my-elb-access-logs-bucket"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "elb_logs" {
bucket = aws_s3_bucket.elb_logs.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "elb_logs" {
bucket = aws_s3_bucket.elb_logs.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "elb_logs" {
bucket = aws_s3_bucket.elb_logs.id
rule {
id = "expire-old-logs"
status = "Enabled"
expiration {
days = 90
}
}
}
resource "aws_s3_bucket_policy" "elb_logs" {
bucket = aws_s3_bucket.elb_logs.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "logdelivery.elasticloadbalancing.amazonaws.com"
}
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.elb_logs.arn}/*"
Condition = {
StringEquals = {
"s3:x-amz-acl" = "bucket-owner-full-control"
}
}
}
]
})
}
# Application Load Balancer with access logs enabled
resource "aws_lb" "main" {
name = "my-application-lb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnet_ids
access_logs {
bucket = aws_s3_bucket.elb_logs.id
prefix = "alb-logs"
enabled = true
}
tags = {
Name = "my-application-lb"
}
}
Classic Load Balancer with access logs:
resource "aws_elb" "main" {
name = "my-classic-lb"
subnets = var.public_subnet_ids
security_groups = [aws_security_group.elb.id]
listener {
instance_port = 80
instance_protocol = "HTTP"
lb_port = 80
lb_protocol = "HTTP"
}
access_logs {
bucket = aws_s3_bucket.elb_logs.id
bucket_prefix = "classic-lb-logs"
interval = 60
enabled = true
}
tags = {
Name = "my-classic-lb"
}
}
Verification
After enabling access logs, verify the configuration:
- In the AWS Console, navigate to EC2 > Load Balancers
- Select your load balancer and check the Attributes tab
- Confirm that Access logs shows as Enabled with the correct S3 bucket
Logs typically appear within 5-15 minutes after traffic flows through the load balancer.
CLI verification commands
For ALB/NLB:
aws elbv2 describe-load-balancer-attributes \
--region us-east-1 \
--load-balancer-arn <your-load-balancer-arn> \
--query "Attributes[?Key=='access_logs.s3.enabled' || Key=='access_logs.s3.bucket']"
Expected output shows access_logs.s3.enabled as true.
For Classic Load Balancer:
aws elb describe-load-balancer-attributes \
--region us-east-1 \
--load-balancer-name <your-lb-name> \
--query "LoadBalancerAttributes.AccessLog"
Verify logs are being written:
aws s3 ls s3://<your-bucket-name>/<prefix>/ --recursive | head -20
Additional Resources
- Enable access logs for your Application Load Balancer
- Enable access logs for your Network Load Balancer
- Enable access logs for your Classic Load Balancer
- Access log entries explained
Notes
- S3 bucket location: The S3 bucket must be in the same AWS region as the load balancer
- Costs: You are charged for S3 storage of the logs, but there is no additional charge from ELB for log delivery
- Log delivery timing: Logs are delivered every 5 minutes (configurable for Classic LB). There may be a delay of up to 15 minutes before logs appear
- Bucket policy required: The S3 bucket must have a policy allowing the ELB service to write objects
- Encryption recommended: Enable S3 server-side encryption on your logs bucket for security
- Retention policy: Consider adding S3 lifecycle rules to automatically expire old logs and manage storage costs
- Classic LB emit interval: Classic Load Balancers support 5-minute or 60-minute intervals; shorter intervals provide more timely data but generate more files