This check flags S3 buckets that have server access logging turned off, which leaves you blind during audits and incident investigations. Enable logging by pointing the bucket at a dedicated log target bucket with the right permissions.
When something goes wrong with an S3 bucket, the first question anyone asks is "who accessed this, and when?" If server access logging is disabled, you have no answer. The data simply does not exist. The Lensix s3_access_logging_disabled check looks for buckets that are not recording access requests, so you can fix the gap before you need those records.
What this check detects
The check inspects each S3 bucket in your AWS account and reports any bucket where server access logging is not configured. Specifically, it looks at the bucket's logging configuration and flags a finding when no target bucket is set.
Server access logging is a native S3 feature. When enabled, S3 writes detailed records of every request made to a bucket, including the requester, bucket name, request time, action, response status, and error codes. These logs land in a separate destination bucket that you nominate.
Note: S3 server access logging is different from CloudTrail data events. Server access logs are delivered on a best-effort basis and capture HTTP-level request details. CloudTrail captures API activity with stronger delivery guarantees and integrates with your broader audit trail. Many teams run both, since they answer slightly different questions.
Why it matters
Logging feels optional right up until the moment you need it, and by then it is too late to enable it retroactively. Here is where the absence of access logs hurts.
Incident response goes dark
Say an object containing customer data is found exposed, or a bucket is suspected of being browsed by an unauthorized party. Without access logs, you cannot reconstruct who read which objects. You are left guessing about the scope of exposure, which usually forces a worst-case assumption and a far more expensive disclosure process.
Compliance gaps
Frameworks like PCI DSS, HIPAA, SOC 2, and ISO 27001 expect access to sensitive data to be logged and reviewable. An auditor asking for evidence of access monitoring on a storage bucket will not accept "we don't log that." A disabled-logging finding can turn into an audit exception.
No forensic timeline
Access logs give you the requester IP, user agent, and request timestamps. That detail is what lets you separate a curious internal script from a credential-theft attack originating outside your network. Without it, the timeline is blank.
Warning: Logs are not retroactive. Enabling server access logging today does nothing for an incident that happened last week. This is exactly why the check matters as a preventive control rather than a reactive one.
How to fix it
You need two things: a destination bucket to hold the logs, and a logging configuration on the source bucket that points to it. Do not log a bucket into itself, since that creates a feedback loop of log entries about log deliveries.
Step 1: Create or choose a target log bucket
Use a dedicated bucket for log storage. Keep it in the same Region as the source bucket, since S3 server access logging requires the target to be in the same Region.
aws s3api create-bucket \
--bucket my-org-s3-access-logs \
--region us-east-1
Step 2: Grant S3 log delivery permission
Modern S3 logging uses bucket policies with the logging.s3.amazonaws.com service principal rather than the legacy log-delivery ACL group. Apply a policy on the target bucket that allows the logging service to write objects, scoped to your source account and bucket.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3ServerAccessLogsPolicy",
"Effect": "Allow",
"Principal": {
"Service": "logging.s3.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-org-s3-access-logs/*",
"Condition": {
"ArnLike": {
"aws:SourceArn": "arn:aws:s3:::my-source-bucket"
},
"StringEquals": {
"aws:SourceAccount": "111122223333"
}
}
}
]
}
aws s3api put-bucket-policy \
--bucket my-org-s3-access-logs \
--policy file://log-bucket-policy.json
Step 3: Enable logging on the source bucket
Create a logging configuration file and apply it. The TargetPrefix keeps logs from different source buckets organized inside the same target.
{
"LoggingEnabled": {
"TargetBucket": "my-org-s3-access-logs",
"TargetPrefix": "my-source-bucket/"
}
}
aws s3api put-bucket-logging \
--bucket my-source-bucket \
--bucket-logging-status file://logging-config.json
Step 4: Verify
aws s3api get-bucket-logging --bucket my-source-bucket
A configured bucket returns a LoggingEnabled block. An empty response means logging is still off.
Console steps
- Open the S3 console and select the source bucket.
- Go to the Properties tab.
- Find Server access logging and choose Edit.
- Enable it, pick your target bucket, set a prefix, and save.
Tip: Set a lifecycle rule on the log bucket to transition older logs to S3 Glacier and expire them after your retention window. Access logs can grow quickly on busy buckets, and storing every entry in Standard storage forever adds up.
Fixing it with infrastructure as code
If you manage buckets in Terraform, wire logging in at definition time so it is never forgotten.
resource "aws_s3_bucket" "logs" {
bucket = "my-org-s3-access-logs"
}
resource "aws_s3_bucket_policy" "logs" {
bucket = aws_s3_bucket.logs.id
policy = data.aws_iam_policy_document.log_delivery.json
}
resource "aws_s3_bucket_logging" "app_data" {
bucket = aws_s3_bucket.app_data.id
target_bucket = aws_s3_bucket.logs.id
target_prefix = "app-data/"
}
For CloudFormation, the equivalent lives under LoggingConfiguration on the source bucket resource:
AppDataBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: my-source-bucket
LoggingConfiguration:
DestinationBucketName: !Ref LogBucket
LogFilePrefix: app-data/
How to prevent it from happening again
Fixing one bucket is easy. Keeping every future bucket compliant is the real work. Push the control left so misconfigured buckets never reach production.
Scan IaC in CI
Run a policy-as-code scanner against your Terraform or CloudFormation in the pipeline. Both checkov and tfsec ship a rule for this out of the box.
# Checkov rule CKV_AWS_18 checks for S3 access logging
checkov -d ./infra --check CKV_AWS_18
Fail the build when the check fails, so a pull request that adds an unlogged bucket cannot merge.
Enforce with a Service Control Policy or org guardrail
For broader coverage, use an OPA or Sentinel policy in your IaC workflow that rejects any aws_s3_bucket without an attached aws_s3_bucket_logging resource. This catches buckets created outside the standard module.
Continuous detection with Lensix
The s3_access_logging_disabled check runs against your live environment, so it catches buckets created by hand, by a forgotten script, or by a team that bypassed your module. CI gates protect what flows through the pipeline; continuous scanning protects everything else.
Tip: Pair this check with an automated remediation. When a bucket is flagged, trigger a Lambda that applies your standard logging configuration and posts to your incident channel. This turns a recurring manual fix into a self-healing control.
Best practices
- Use one or a few central log buckets per Region rather than scattering logs everywhere. Prefixes keep sources separated and queries simple.
- Lock down the log bucket. Block public access, enable default encryption, and restrict read access to your security team. Access logs themselves are sensitive.
- Enable versioning and Object Lock on the log bucket if you need tamper-evident retention for compliance.
- Combine with CloudTrail data events for buckets holding regulated data, so you have both best-effort access logs and guaranteed API audit records.
- Set retention deliberately. Match your log lifecycle to your compliance and incident-response needs, then expire the rest to control cost.
- Query with Athena. Point an Athena table at the log prefix so you can run ad-hoc SQL over access patterns instead of grepping raw files during an incident.
Danger: Never make a log bucket public or grant broad write access to it. An attacker who can write to your log destination can forge or delete records, destroying the very evidence you enabled logging to capture.
Access logging is one of those controls that costs almost nothing to enable and pays for itself the first time you have to answer "what happened?" Turn it on for every bucket that holds anything you would not want exposed, and bake the requirement into your IaC so the next bucket is logged by default.

