
S3 access logs help you investigate requests to an Amazon S3 bucket: which operation was attempted, which object was involved, and what response S3 returned. They are useful when an application starts receiving errors or you need to examine how a bucket is being used.
This guide sets up direct delivery to a separate S3 bucket, then checks that the resulting files can be read. It also explains when CloudWatch Logs or CloudTrail is a better fit. The examples apply to Amazon S3 general purpose buckets. An S3-compatible service may expose different logging features; do not assume these AWS settings apply to another provider.
Current AWS documentation describes two destinations for server access logs. Direct delivery creates text objects in an S3 bucket. Delivery through CloudWatch Logs provides structured logs and a different setup, with options for KMS encryption and centralized analysis. CloudWatch vended-log ingestion charges apply to that path.
| Need | Starting point | Check before enabling it |
|---|---|---|
| Request records stored as S3 objects | Direct server access logging | Destination bucket, delivery permissions, and retention |
| Interactive log queries or centralized log processing | CloudWatch Logs delivery | Delivery configuration, encryption, ingestion costs, and retention |
| Records of bucket and object API actions for an audit | CloudTrail with the required event coverage | Data-event selectors and the actions actually recorded |
AWS recommends CloudTrail for bucket-level and object-level actions. The services have different coverage. Choosing a server access-log destination does not configure CloudTrail.
In particular, CloudTrail data events are not enabled by default, incur additional charges, and do not appear in CloudTrail Event history. An empty Event history therefore does not establish that nobody read an object. Check the event configuration before drawing that conclusion.
AWS delivers server access records on a best-effort basis. Records can arrive late, appear more than once, or be missing. Delivery commonly takes hours, and configuration changes take time to propagate.
Use these files to investigate observed traffic. Do not treat their absence as proof that a request never happened, or use their row count as an exact billing ledger. If your investigation needs a particular event or identity, verify coverage in the logging service you selected and retain the application’s own request identifiers.
For direct S3 delivery, choose an existing destination in the same AWS account and Region as the source. Use SSE-S3 encryption, leave Requester Pays off, and do not use an Object Lock-enabled destination. Keep server access logging disabled on the destination itself to avoid generating logs about log delivery. These are restrictions of the direct bucket path in the AWS setup guide.
Keep both buckets private. Bucket owner enforced Object Ownership works with the bucket-policy approach below, so there is no reason to enable ACLs for this example. Give the people who investigate incidents permission to read the logs separately from the service’s permission to write them.
Use disposable test buckets first. Replace both demo bucket names and account ID 123456789012 throughout. The logging-admin profile must already be configured for your account and Region, with permission to manage the bucket policy and logging configuration. It also needs the read permissions used in the verification steps.
aws sts get-caller-identity --profile logging-admin
aws s3api get-bucket-logging --bucket amzn-s3-demo-source --expected-bucket-owner 123456789012 --profile logging-admin
Confirm the account returned by the first command. Save any existing logging configuration before changing it. get-bucket-logging reports the direct S3 configuration; it does not verify that files are arriving or audit a separate CloudWatch delivery setup. The expected-owner option provides an additional account check.
Save this as log-delivery-policy.json for a new test destination with no existing policy. It permits the S3 logging service to write beneath logs/, with conditions tied to the source bucket and account.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSourceBucketLogDelivery",
"Effect": "Allow",
"Principal": {
"Service": "logging.s3.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-access-logs/logs/*",
"Condition": {
"ArnLike": {
"aws:SourceArn": "arn:aws:s3:::amzn-s3-demo-source"
},
"StringEquals": {
"aws:SourceAccount": "123456789012"
}
}
}
]
}
For an existing destination, merge the statement into its reviewed policy and preserve its other rules. The put-bucket-policy command writes a bucket policy; it is not an append operation. Our S3 bucket policy guide explains action, resource, and condition matching.
aws s3api put-bucket-policy --bucket amzn-s3-demo-access-logs --policy file://log-delivery-policy.json --expected-bucket-owner 123456789012 --profile logging-admin
This grants delivery access, not public read access. Do not replace the service principal with a wildcard or remove the source conditions to work around a delivery failure. Check the failing condition and any explicit deny instead.
Save the following as logging.json. It selects the destination bucket, a prefix, and date-based object keys. It deliberately omits target grants because the example uses policies with ACLs disabled.
{
"LoggingEnabled": {
"TargetBucket": "amzn-s3-demo-access-logs",
"TargetPrefix": "logs/",
"TargetObjectKeyFormat": {
"PartitionedPrefix": {
"PartitionDateSource": "EventTime"
}
}
}
}
Apply it to the source bucket, then read the configuration back using the logging API’s CLI commands:
aws s3api put-bucket-logging --bucket amzn-s3-demo-source --bucket-logging-status file://logging.json --expected-bucket-owner 123456789012 --profile logging-admin
aws s3api get-bucket-logging --bucket amzn-s3-demo-source --expected-bucket-owner 123456789012 --profile logging-admin
Compare the returned destination, prefix, and key format with the file. A successful configuration request is only the first check. Next, verify delivery and readability.
Place a harmless test object at logging-check/sample.txt in the source bucket beforehand. Run the following after enabling logging, and record the request time and result:
aws s3api head-object --bucket amzn-s3-demo-source --key logging-check/sample.txt --expected-bucket-owner 123456789012 --profile logging-admin
aws s3api list-objects-v2 --bucket amzn-s3-demo-access-logs --prefix logs/ --max-keys 20 --no-paginate --expected-bucket-owner 123456789012 --profile logging-admin
The listing command shows at most one page of 20 objects. Repeat it later or browse the destination in the console; an immediate empty result is not a conclusive failure. Once logs arrive, open a file and find the test key, operation, and approximate request time. Confirm that your intended investigator role can read it. Treat a missing individual record according to the best-effort limitation above.
With EventTime partitioning, the directory date reflects the event day, and the filename’s hours, minutes, and seconds are zero. An illustrative key for a source in eu-west-1 is:
logs/123456789012/eu-west-1/amzn-s3-demo-source/2026/11/02/2026-11-02-00-00-00-EXAMPLE
The exact request time remains inside the record. A file that arrives tomorrow may belong to today’s event partition, so do not search only the delivery date.
The S3 text log format includes the bucket, request time, remote IP, requester, operation, key, HTTP status, error code, and transfer-related fields. A dash can mean unavailable or inapplicable data. Quoted request and user-agent fields can contain spaces, so a simple split on every space is not a reliable parser.
Start an investigation with a concrete question: which requests to a known key returned 403, or which operations changed around a deployment? Keep the request ID alongside the key and timestamp. Avoid treating an IP address alone as a person’s identity.
For larger volumes, use the AWS Athena examples as a starting point. Match the table location and partition configuration to your chosen prefix. Compare parsed rows with raw records before relying on a query result, and constrain the dates scanned. An empty result can reflect a wrong location or schema rather than no activity.
Work through these checks before changing permissions:
TargetPrefix, and verify both source conditions.The AWS troubleshooting reference maps common setup errors to these causes. Keep configuration errors, delivery delays, and read failures separate in the incident record.
Direct delivery has no separate logging fee, but retaining and analyzing the files still costs money. Decide how long you need searchable records, who can read them, and what happens when the retention period ends. Logs can expose object names, request patterns, and network addresses.
An S3 Lifecycle rule can transition or expire log objects. Review its prefix and effect on existing objects before applying it, and account for noncurrent versions if versioning is enabled. Our lifecycle policy guide covers those choices. Keep the selected retention period and the reason for it with the logging configuration.
The useful outcome is a verified path from a known request to a readable record, with documented coverage and retention. Repeat that check after policy, encryption, destination, or logging changes.
Pick one AI, compute, or storage workload and see the difference for yourself. Spin it up in minutes, or let our team map your fastest path to production.