This check flags CloudFront distributions running without access logging. Without those logs you lose visibility into who hit your edge, what they requested, and how attacks unfolded. Enable standard logging to an S3 bucket (or a Kinesis stream via real-time logs) and you get a forensic trail for every request.
CloudFront sits at the front door of most AWS-hosted web apps. It serves your static assets, fronts your APIs, and absorbs traffic from every region. When something goes wrong, whether that is a credential-stuffing attack, a scraper hammering your origin, or a sudden bandwidth spike that shows up on the bill, the first question is always the same: what requests came through, and from where?
If access logging is off, you cannot answer that question. This check, cloudfront_accesslogging, catches distributions that have logging disabled so you can turn it on before you actually need the data.
What this check detects
The check inspects each CloudFront distribution in your account and reports any distribution where access logging is not configured. Specifically, it looks at the distribution's logging configuration and fails when:
- Standard (legacy) access logging is disabled, meaning no S3 log bucket is set, and
- No alternative request-level logging path is in place for that distribution.
CloudFront offers two logging mechanisms, and it helps to know the difference before you remediate.
Note: CloudFront has standard logs (delivered to an S3 bucket on a delay, free aside from S3 storage) and real-time logs (streamed to Kinesis Data Streams within seconds, billed per log line). Standard logs cover the common compliance and forensics use case. Real-time logs suit live dashboards and rapid detection.
Why it matters
Access logs are not a nice-to-have. They are the raw material for security investigation, abuse detection, and cost analysis. Here is what you lose without them.
No forensic trail after an incident
Say an attacker discovers an unauthenticated endpoint behind your distribution and starts enumerating user data. If logging is off, you have no record of the source IPs, the request paths, the user agents, or the timing. You cannot scope the breach, you cannot prove what was accessed, and you cannot meet the notification obligations that most data-protection regimes impose. The investigation stalls before it starts.
Blind spots during DDoS and abuse
CloudFront access logs capture every request, including the edge location that served it, the result type (hit, miss, error), and the bytes transferred. During a layer 7 flood or an aggressive scraper run, those fields are how you identify the offending patterns and build a WAF rule that actually targets the abuse. Without logs you are guessing.
Unexplained bandwidth costs
CloudFront data transfer can become a meaningful line item. When a distribution suddenly costs three times its usual amount, access logs let you trace the bytes back to specific URIs and clients. A hotlinked asset, a misbehaving client retrying endlessly, or a content-theft campaign all look the same on the bill and very different in the logs.
Compliance gaps
Frameworks like PCI DSS, SOC 2, and ISO 27001 expect logging on internet-facing systems. An auditor who finds a public distribution with no request logging will write it up, and you will scramble to backfill evidence you never collected.
Warning: Logging is not retroactive. The moment you enable it, CloudFront begins recording new requests, but it cannot reconstruct traffic from before that point. If you turn it on during an active incident, you only capture what happens next.
How to fix it
You have a few options. The most common is standard logging to an S3 bucket. The steps below cover the console, the CLI, and infrastructure as code.
Option 1: Standard logging to S3 (console)
- Open the CloudFront console and select the distribution.
- Go to the General tab, then Settings, and choose Edit.
- Under Standard logging, switch it to On.
- Pick an S3 bucket for the logs. Use a dedicated bucket, not the bucket serving your content.
- Optionally set a prefix such as
cloudfront/<distribution-id>/so logs from multiple distributions stay separated. - Save changes. The distribution status moves to Deploying, then logs begin arriving within minutes.
Option 2: Standard logging to S3 (CLI)
First create and lock down a dedicated log bucket. CloudFront delivers logs using a service-linked mechanism, so the bucket owner needs the right ACL grant.
# Create a dedicated log bucket
aws s3api create-bucket \
--bucket my-cloudfront-logs \
--region us-east-1
# Enable ACLs so CloudFront's log delivery account can write
aws s3api put-bucket-ownership-controls \
--bucket my-cloudfront-logs \
--ownership-controls 'Rules=[{ObjectOwnership=BucketOwnerPreferred}]'
# Grant the awslogsdelivery canonical account write access
aws s3api put-bucket-acl \
--bucket my-cloudfront-logs \
--grant-full-control id=c4c1ede66af53448b93c283ce9448c4ba468c9432aa01d700d3878632f77d2d0
Now update the distribution. CloudFront updates require you to fetch the current config, modify it, and pass it back with the matching ETag.
# 1. Fetch the current config and ETag
aws cloudfront get-distribution-config \
--id E1ABCDEFGHIJKL \
--query 'DistributionConfig' \
--output json > dist-config.json
aws cloudfront get-distribution-config \
--id E1ABCDEFGHIJKL \
--query 'ETag' \
--output text
# returns something like: E2QWERTY1234
Edit the Logging block inside dist-config.json so it reads like this:
"Logging": {
"Enabled": true,
"IncludeCookies": false,
"Bucket": "my-cloudfront-logs.s3.amazonaws.com",
"Prefix": "cloudfront/E1ABCDEFGHIJKL/"
}
Then push the update, using the ETag as the --if-match value:
aws cloudfront update-distribution \
--id E1ABCDEFGHIJKL \
--distribution-config file://dist-config.json \
--if-match E2QWERTY1234
Danger: update-distribution replaces the entire distribution config, not just the logging block. If your edited JSON drops a field, you can break origins, cache behaviors, or TLS settings on a production distribution. Always edit a freshly fetched config and diff it before applying.
Option 3: Terraform
If you manage CloudFront with Terraform, the logging configuration lives in a logging_config block:
resource "aws_s3_bucket" "cf_logs" {
bucket = "my-cloudfront-logs"
}
resource "aws_s3_bucket_ownership_controls" "cf_logs" {
bucket = aws_s3_bucket.cf_logs.id
rule {
object_ownership = "BucketOwnerPreferred"
}
}
resource "aws_cloudfront_distribution" "this" {
# ... origins, default_cache_behavior, etc.
logging_config {
bucket = aws_s3_bucket.cf_logs.bucket_domain_name
prefix = "cloudfront/"
include_cookies = false
}
}
Option 4: Real-time logs (when you need speed)
For live detection, attach a real-time log configuration that streams to Kinesis. This is per cache behavior, so you can scope it to the paths you care about.
aws cloudfront create-realtime-log-config \
--name api-realtime-logs \
--sampling-rate 100 \
--fields timestamp c-ip cs-method cs-uri-stem sc-status \
--end-points 'StreamType=Kinesis,KinesisStreamConfig={RoleARN=arn:aws:iam::111122223333:role/cf-realtime-role,StreamARN=arn:aws:kinesis:us-east-1:111122223333:stream/cf-logs}'
Warning: Real-time logs bill per log line delivered to Kinesis, on top of the Kinesis stream and consumer costs. A high-traffic distribution at 100 percent sampling can run up real charges fast. Lower the --sampling-rate or scope real-time logs to sensitive behaviors only.
How to prevent it from happening again
Fixing one distribution is easy. Keeping every distribution compliant as your team ships new ones is the real work. Push the control left.
Gate it in CI with policy-as-code
If you use Terraform, a Checkov or OPA policy can fail the pipeline when a distribution lacks logging_config. Checkov ships a built-in rule for this:
# CKV_AWS_86 checks that CloudFront access logging is enabled
checkov -d ./infra --check CKV_AWS_86
Wire that into your pull request checks so an unlogged distribution never merges.
Enforce with a Conftest / OPA rule
For plan-based enforcement, a Rego policy against the Terraform plan JSON works well:
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_cloudfront_distribution"
not resource.change.after.logging_config
msg := sprintf("CloudFront distribution '%s' has no logging_config", [resource.address])
}
Detect drift continuously
Policy gates only cover what flows through your pipeline. Console clicks, emergency changes, and resources created by other teams slip past. Continuous scanning closes that gap. Lensix runs the cloudfront_accesslogging check across your accounts on a schedule and surfaces any distribution where logging has been turned off, including ones created outside your IaC.
Tip: Pair a CI gate with continuous scanning. The gate stops most misconfigurations at the source, and the scanner catches the rest, including drift introduced after deploy. Neither alone gives full coverage.
Best practices
- Use a dedicated, locked-down log bucket. Keep CloudFront logs separate from content buckets, block public access, and restrict read access to your security and analytics roles.
- Set lifecycle rules. Logs accumulate. Transition them to S3 Infrequent Access or Glacier after 30 to 90 days and expire them per your retention policy to control storage cost.
- Enable default encryption on the log bucket with SSE-S3 or SSE-KMS so request data is encrypted at rest.
- Make the logs queryable. Point Athena at the log bucket so you can run SQL against requests during an investigation instead of grepping gzipped files by hand.
- Decide on cookie logging deliberately. Leave
IncludeCookiesoff unless you genuinely need it. Cookies can contain session tokens and other sensitive values you do not want sitting in logs. - Log every internet-facing distribution. Treat logging as the default for anything serving public traffic, not a per-distribution decision.
Access logging costs almost nothing to enable and pays for itself the first time you need to reconstruct what hit your edge. Turn it on everywhere, gate it in CI, and scan for drift so it stays on.

