S3 Access Logs: Enable, Read, and Query With Athena
Last verified on Nov 8, 2026.
S3 server access logging records requests made to a bucket and delivers them as log records, either to another S3 bucket as text files or to a CloudWatch Logs log group as JSON. It is off by default. This guide shows how to turn it on, what a record contains, how to query the files with Athena, and when CloudTrail is the better tool.
Server Access Logs vs CloudTrail
AWS recommends CloudTrail for bucket-level and object-level actions, and you can run both. The differences that matter in practice:
By default, CloudTrail trails do not log data events such as GetObject, so object reads show up in CloudTrail only after you turn data events on. Server access logs are the only one of the two that capture requests that fail authentication.
Enabling Server Access Logging
You need a destination bucket in the same AWS Region and account as the source bucket. A dedicated logging bucket per Region is the simplest layout. Three constraints from AWS:
- The destination must not have Object Lock or Requester Pays enabled.
- It should not log to itself, since that creates an endless loop of logs about logs.
- It must use SSE-S3 default encryption. With SSE-KMS, S3 might write objects encrypted with a key you cannot read.
In the console, open the source bucket, choose Properties, find Server access logging, and choose Edit. The console updates the destination bucket policy for you. From the CLI you must grant the logging service principal write access yourself:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3ServerAccessLogsPolicy",
"Effect": "Allow",
"Principal": { "Service": "logging.s3.amazonaws.com" },
"Action": ["s3:PutObject"],
"Resource": "arn:aws:s3:::amzn-s3-demo-destination-bucket/logs/*",
"Condition": {
"ArnLike": { "aws:SourceArn": "arn:aws:s3:::amzn-s3-demo-source-bucket" },
"StringEquals": { "aws:SourceAccount": "111122223333" }
}
}
]
}aws s3api put-bucket-policy --bucket amzn-s3-demo-destination-bucket --policy file://policy.json
cat > logging.json <<'JSON'
{
"LoggingEnabled": {
"TargetBucket": "amzn-s3-demo-destination-bucket",
"TargetPrefix": "logs/"
}
}
JSON
aws s3api put-bucket-logging --bucket amzn-s3-demo-source-bucket \
--bucket-logging-status file://logging.jsonLog objects are named TargetPrefix plus a timestamp and a unique string, for example logs/2026-11-08-04-11-52-UniqueString. You can choose date-based partitioning instead, which writes account/region/source-bucket/YYYY/MM/DD/ folders and pairs with a partitioned Athena table. Changes take effect over time: for about an hour after you enable logging or change the destination, some requests are logged and others are not.
Delivering to CloudWatch Logs Instead
You can also send the same records to a CloudWatch Logs log group in the bucket's Region, using vended log delivery. You configure it with CloudWatch Logs APIs, not S3 APIs, and you need logs:PutDeliverySource, logs:PutDeliveryDestination, logs:CreateDelivery, and s3:AllowVendedLogDeliveryForResource on the bucket.
aws logs put-delivery-source --name my-sal-source \
--resource-arn arn:aws:s3:::my-bucket --log-type S3_SERVER_ACCESS_LOGS
aws logs put-delivery-destination --name my-sal-destination \
--delivery-destination-configuration '{"destinationResourceArn": "arn:aws:logs:us-east-1:123456789012:log-group:my-sal-logs"}'
aws logs create-delivery --delivery-source-name my-sal-source \
--delivery-destination-arn arn:aws:logs:us-east-1:123456789012:delivery-destination:my-sal-destinationPropagation takes 15 to 20 minutes, delivery then begins within a few hours, and nothing is backfilled. Each record is a JSON object, so CloudWatch Logs Insights can query it directly. CloudWatch vended log ingestion rates apply here, where bucket delivery has no delivery charge. See CloudWatch pricing.
filter http_status >= 400
| stats count(*) as errors by error_code, operation
| sort errors descReading a Record
Bucket delivery produces one space-delimited line per request. The first fields are bucket owner, bucket, time in brackets (UTC), remote IP, requester, request ID, operation, key, the quoted request URI, HTTP status, and error code. Then come bytes sent, object size, total time and turn-around time in milliseconds, referer, user agent, version ID, host ID, signature version, cipher suite, authentication type, host header, and TLS version. A dash means the value is unknown or not applicable.
79a59df9...ef2be amzn-s3-demo-bucket1 [06/Feb/2019:00:00:38 +0000] 192.0.2.3 79a59df9...ef2be 3E57427F3EXAMPLE REST.GET.VERSIONING - "GET /amzn-s3-demo-bucket1?versioning HTTP/1.1" 200 - 113 - 7 - "-" "S3Console/0.4" -AWS says it may add new fields to the end of each line, so write parsers that tolerate trailing fields they do not know. Records are best effort: one can arrive late, twice, or not at all, so do not use these logs as a complete accounting of requests.
Querying S3 Access Logs With Athena
Athena reads the files in place. Create a database in the same Region as the bucket, then a table over the log prefix. This is AWS's non-partitioned definition, and LOCATION points at your logging prefix:
CREATE EXTERNAL TABLE `s3_access_logs_db.mybucket_logs`(
`bucketowner` STRING, `bucket_name` STRING, `requestdatetime` STRING,
`remoteip` STRING, `requester` STRING, `requestid` STRING, `operation` STRING,
`key` STRING, `request_uri` STRING, `httpstatus` STRING, `errorcode` STRING,
`bytessent` BIGINT, `objectsize` BIGINT, `totaltime` STRING, `turnaroundtime` STRING,
`referrer` STRING, `useragent` STRING, `versionid` STRING, `hostid` STRING,
`sigv` STRING, `ciphersuite` STRING, `authtype` STRING, `endpoint` STRING,
`tlsversion` STRING, `accesspointarn` STRING, `aclrequired` STRING, `sourceregion` STRING)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
'input.regex'='([^ ]*) ([^ ]*) \\[(.*?)\\] ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) (\"[^\"]*\"|-) (-|[0-9]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) (\"[^\"]*\"|-) ([^ ]*)(?: ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*))?.*$')
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://amzn-s3-demo-bucket1-logs/logs/'Then use queries such as these:
-- Who deleted an object, and when
SELECT requestdatetime, remoteip, requester, key
FROM s3_access_logs_db.mybucket_logs
WHERE key = 'images/picture.jpg' AND operation LIKE '%DELETE%';
-- Everything one IAM user did
SELECT * FROM s3_access_logs_db.mybucket_logs
WHERE requester = 'arn:aws:iam::123456789123:user/user_name';
-- Bytes sent to one address in June 2022
SELECT coalesce(SUM(bytessent), 0) AS bytessenttotal
FROM s3_access_logs_db.mybucket_logs
WHERE remoteip = '192.0.2.1'
AND parse_datetime(requestdatetime, 'dd/MMM/yyyy:HH:mm:ss Z')
BETWEEN parse_datetime('2022-06-01', 'yyyy-MM-dd') AND parse_datetime('2022-07-01', 'yyyy-MM-dd');For 5xx errors, filter httpstatus LIKE '5%' and keep requestid and hostid so you can trace each failing request. The date-partitioned table definition uses a timestamp partition column, so a WHERE timestamp BETWEEN '2024/01/29' AND '2024/01/30' clause limits the scan to those days.
Searching S3 Activity With Sazabi
Sazabi, an AI-native observability and security platform, reads AWS logs through CloudWatch Logs subscription filters. If you deliver server access logs to a CloudWatch Logs log group, add that group as a stream under Settings > Log streams after connecting the CloudWatch source. For object-level activity, turn on CloudTrail data events, send the trail to CloudWatch Logs, and connect the CloudTrail source. Sazabi's docs say reading trails straight from their S3 bucket is not available yet, so use the CloudWatch Logs route for both. See the CloudTrail integration and the CloudWatch integration.
Frequently Asked Questions
How do I enable S3 access logs?
Open the bucket in the S3 console, choose Properties, then Server access logging, and pick a destination bucket in the same Region and account. From the CLI, grant the logging.s3.amazonaws.com principal s3:PutObject on the destination and run aws s3api put-bucket-logging.
How long do S3 access logs take to appear?
AWS delivers most records within a few hours, on a best-effort basis. Completeness and timeliness are not guaranteed, and a record can be late, duplicated, or missing.
Should I use S3 server access logs or CloudTrail?
AWS recommends CloudTrail data events for bucket and object actions because they are easier to set up and carry more detail. Server access logs are still useful for authentication failures, lifecycle actions, and low-cost traffic analysis in your own bucket.
Why are my S3 access logs not being delivered?
Check that the destination bucket is in the same Region and account, that the logging service principal can write to it, that it uses SSE-S3 rather than SSE-KMS, and that it does not have Object Lock or Requester Pays enabled. Then wait, because changes take effect over about an hour.
Sources
- Server access logging delivers to a CloudWatch Logs log group or an S3 general purpose bucket, best-effort delivery within a few hours, and settings changes taking effect over time (accessed Nov 8, 2026)
- Logging is off by default, same-Region and same-account destination bucket, the logging.s3.amazonaws.com principal, SSE-S3 requirement, put-bucket-logging command, and delivered key formats (accessed Nov 8, 2026)
- Delivering server access logs to CloudWatch Logs with put-delivery-source, put-delivery-destination, and create-delivery, required permissions, and the 15 to 20 minute propagation (accessed Nov 8, 2026)
- JSON field names for server access logs delivered to CloudWatch Logs (accessed Nov 8, 2026)
- Space-delimited record fields, the dash for unknown values, and trailing fields that may be added (accessed Nov 8, 2026)
- Athena table definition and example queries for deletes, requester activity, bytes sent, and 5xx errors; AWS recommends CloudTrail data events for identifying requests (accessed Nov 8, 2026)
- Comparison of CloudTrail and server access logs: delivery speed, format, prefix filtering, authentication failures, price (accessed Nov 8, 2026)
- Trails do not log data events by default and data events carry additional charges (accessed Nov 8, 2026)
- Sazabi reads CloudTrail through the trail's CloudWatch Logs delivery and does not read trails from S3 yet (accessed Nov 8, 2026)