Every AWS security incident post-mortem starts the same way: someone opens CloudTrail logs to figure out who did what, when, and from where. If you have not set up AWS CloudTrail properly before that moment arrives, you will be reconstructing an attack timeline from partial data instead of a complete one. AWS CloudTrail is the audit trail behind almost every AWS account, and getting the configuration right the first time saves hours of pain during an incident, an audit, or a routine cost review.
This tutorial walks through setting up AWS CloudTrail from a blank account to a production-grade logging pipeline: a multi-region organization trail, encrypted and validated log storage, CloudWatch alarms, CloudTrail Lake for SQL queries, Athena for historical searches, and EventBridge automation for real-time response. Thirteen steps, roughly 70 minutes if you follow along in a sandbox AWS account, longer if you are rolling this out across an AWS Organization with dozens of member accounts.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What Is AWS CloudTrail and Why It Matters in 2026
AWS CloudTrail records API activity across AWS services, giving every account a standardized audit log of who called which API, when, from what IP address, and with what result. It is the service that answers “who deleted that S3 bucket” or “who changed this security group” after the fact, and it is the foundational data source most AWS security tooling (GuardDuty, Security Hub, third-party SIEMs) builds on top of.
CloudTrail organizes activity into three event types. Management events cover control-plane actions such as creating an EC2 instance or modifying an IAM policy. Data events cover high-volume, object- or item-level activity, like reads and writes against a specific S3 object or DynamoDB table. Network activity events cover API calls made through VPC endpoints, useful for tracking traffic that never technically leaves your private network. Understanding which category a given action falls into matters because it directly affects both what you can see and what you pay.
2025 and 2026 have been active years for the service. AWS expanded data event coverage multiple times last year, including Amazon Q Business, Amazon Bedrock, Amazon SageMaker AI, and Amazon EKS, and added new network activity event support in May and October 2025. At re:Invent 2025, AWS introduced CloudTrail aggregated events, which roll up high-volume data events into five-minute summaries for security monitoring, and CloudTrail insights for data events, which flags unusual activity based on API call rate and error rate patterns. CloudTrail Lake also picked up custom dashboards, a Highlights dashboard, natural-language query generation, and an AI-powered query summarization feature currently in preview. One change worth flagging early: AWS has announced that CloudTrail Lake will stop accepting new customers after May 31, 2026, though existing Lake customers can continue using it. If you plan to adopt Lake, that date matters for your timeline.
Prerequisites: What You Need Before You Start
You do not need to install anything exotic to follow this AWS CloudTrail tutorial, but a few things need to be in place before Step 1.
- An AWS account with administrator access, or an IAM role/user with
cloudtrail:*,s3:*on your logging bucket,kms:*on your logging key,iam:CreateRole/iam:PutRolePolicy, andlogs:*for the CloudWatch Logs integration. - AWS CLI v2 installed and configured (
aws --versionshould return 2.x). Runaws configureif you have not already set your access key, secret key, and default region. - If you are rolling this out across an organization, AWS Organizations must already be set up with all-features mode enabled (not just consolidated billing), and you need to run the organization trail step from the management account or a delegated administrator account.
- An S3 bucket naming convention decided in advance. Bucket names are globally unique, so pick something like
your-org-cloudtrail-logs-ACCOUNTIDbefore you start. - A rough idea of your monthly API call volume if you plan to enable data events broadly, since that is what drives cost more than anything else in this setup.
- Optional but recommended: an existing Amazon Athena or CloudTrail Lake use case in mind, since Steps 10 and 11 configure query access and it helps to know what questions you want to be able to answer later (failed console logins in the last 90 days, all S3 deletes by a specific role, and so on).
One thing to know before you begin: every AWS account already has 90 days of management event history available for free through the CloudTrail Event History view, with no trail required. What you are building in this tutorial goes beyond that default: durable log storage, multi-region and multi-account coverage, data events, and alerting, none of which the default event history provides.
Step 1: Check Your Default Event History
Before creating anything, confirm what you already have. Sign in to the AWS Management Console, search for “CloudTrail” in the top search bar, and open the service. Click Event history in the left navigation. You will see the last 90 days of management events with no configuration required. This exists in every AWS account automatically and does not count toward CloudTrail pricing.
This view is useful for quick lookups but has real limits: it only covers management events, only holds 90 days, cannot be queried at scale, and disappears if you need it during an actual incident review that spans a longer window. It is a starting point, not a logging strategy. Confirming it works, though, tells you CloudTrail is active in the account (it always is, by default) and gives you a baseline to compare against once your custom trail starts recording.
You can also check this from the CLI, which is worth getting comfortable with since most of the remaining steps use it:
aws cloudtrail lookup-events \
--max-results 5 \
--region us-east-1
If that command returns a JSON array of recent events, default event history is working as expected and you are ready to move on.
Step 2: Plan Your Trail Architecture
This is the step people skip and regret. Before creating a trail, decide on scope, because changing it later means re-auditing everything downstream.
You have three architectural choices. A single-region trail logs activity in one region only, rarely the right call unless you have a strict single-region workload and want to minimize cost. An all-region trail (the default recommendation for a single AWS account) automatically logs activity in every region, including any new region AWS launches in the future, without you having to remember to add it. An organization trail, created from the AWS Organizations management account, applies an all-region trail across every member account automatically, which is the standard pattern for any account with more than a handful of AWS accounts.
Also decide now whether you want one trail doing everything, or separate trails for separate purposes. For example, one trail could capture management events organization-wide, while a second, narrower trail captures high-volume S3 data events only for your production data buckets. Splitting trails by purpose makes cost easier to reason about later, since data event volume can dwarf management event volume in a busy account. For most teams starting fresh, the practical answer is: one all-region (or organization) trail for management events, and a second purpose-built trail for data events on the specific resources that actually need object-level auditing.
Step 3: Create Your First Trail
In the CloudTrail console, click Trails in the left navigation, then Create trail. Give it a descriptive name, something like org-management-trail, rather than the console’s default suggestion, since you will be reading this name in alerts and Athena query results later. Under general details, select Enable for all accounts in my organization only if you are creating an organization trail from the management account. Otherwise, leave it unchecked.
Choose Create new S3 bucket if this is a fresh setup, or point to an existing bucket if your organization already has a centralized logging account (a common and recommended pattern: send logs to a dedicated, locked-down logging account rather than storing them alongside the resources they are auditing). Leave trail log bucket and folder defaults unless you have a specific prefix convention.
Doing this from the CLI instead is often faster once you know the parameters:
aws cloudtrail create-trail \
--name org-management-trail \
--s3-bucket-name your-org-cloudtrail-logs-ACCOUNTID \
--is-multi-region-trail \
--enable-log-file-validation \
--include-global-service-events
aws cloudtrail start-logging \
--name org-management-trail
Note the second command. Creating a trail does not automatically start it: start-logging is a separate, easy-to-forget call. This is one of the most common reasons a “configured” trail turns out to have recorded nothing.
Step 4: Configure the S3 Bucket for Log Storage
CloudTrail needs a bucket policy granting it permission to write logs, and you need that bucket locked down so nobody else can read, modify, or delete those logs. If you created the bucket through the console trail wizard, AWS attaches a working policy automatically. If you are using an existing bucket or building this via CLI/infrastructure-as-code, you need to attach it yourself.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailAclCheck",
"Effect": "Allow",
"Principal": { "Service": "cloudtrail.amazonaws.com" },
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::your-org-cloudtrail-logs-ACCOUNTID"
},
{
"Sid": "AWSCloudTrailWrite",
"Effect": "Allow",
"Principal": { "Service": "cloudtrail.amazonaws.com" },
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::your-org-cloudtrail-logs-ACCOUNTID/AWSLogs/ACCOUNTID/*",
"Condition": {
"StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" }
}
}
]
}
Beyond the bucket policy, enable S3 Block Public Access at the bucket level (all four settings on, no exceptions), turn on versioning so an accidental or malicious delete does not destroy history, and add a lifecycle rule to transition older logs to S3 Glacier or Glacier Deep Archive after 90 days if you need long retention for compliance without paying standard storage rates indefinitely. If your compliance framework requires it, enable MFA Delete on the bucket, which requires an additional authentication factor before anyone can permanently delete an object or change versioning state, a meaningful control against an attacker who has compromised standard IAM credentials.
Step 5: Enable Encryption With AWS KMS
By default, CloudTrail logs are encrypted with S3-managed keys (SSE-S3), which is adequate but gives you no control over who can decrypt the data and no audit trail of decryption events. For anything touching production or compliance scope, switch to a customer-managed KMS key (SSE-KMS) so key usage itself becomes auditable and you can restrict decryption to specific roles.
Create a dedicated KMS key for CloudTrail rather than reusing one from another workload, and attach a key policy that explicitly allows the CloudTrail service to encrypt and grants your security team decrypt access:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudTrailEncrypt",
"Effect": "Allow",
"Principal": { "Service": "cloudtrail.amazonaws.com" },
"Action": ["kms:GenerateDataKey*", "kms:DescribeKey"],
"Resource": "*"
},
{
"Sid": "AllowSecurityTeamDecrypt",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::ACCOUNTID:role/SecurityAudit" },
"Action": ["kms:Decrypt", "kms:DescribeKey"],
"Resource": "*"
}
]
}
Attach the key to your trail with aws cloudtrail update-trail --name org-management-trail --kms-key-id alias/cloudtrail-key. Keep in mind that KMS itself bills per API request beyond its own free tier, so a very high-volume data-event trail encrypted with a customer-managed key adds a small but non-zero KMS cost on top of standard CloudTrail pricing, worth knowing before you enable it broadly rather than discovering it on a bill.
Step 6: Turn On Log File Validation
Log file validation gives you cryptographic proof that a CloudTrail log file has not been altered or deleted since CloudTrail delivered it, critical if logs ever need to hold up as evidence, whether in an internal investigation, a legal proceeding, an AWS compliance audit, or an insurance claim after a breach. CloudTrail creates a digitally signed digest file alongside your logs at set intervals, and you can validate any log file against that digest using the CLI.
Enable it when creating the trail (the --enable-log-file-validation flag used in Step 3) or after the fact:
aws cloudtrail update-trail \
--name org-management-trail \
--enable-log-file-validation
To actually validate a range of logs later, for example right before submitting them as evidence, run:
aws cloudtrail validate-logs \
--trail-arn arn:aws:cloudtrail:us-east-1:ACCOUNTID:trail/org-management-trail \
--start-time 2026-05-01T00:00:00Z \
--end-time 2026-06-01T00:00:00Z
There is no meaningful downside to enabling this, and it costs nothing extra. There is no good reason to leave it off.
Step 7: Configure Management, Data, and Network Activity Events
By default, a new trail logs management events (both read and write) and nothing else. Data events and network activity events are opt-in, and this is the step where cost decisions get made, since data events in particular can generate orders of magnitude more volume than management events in an active account.
In the console, edit your trail, scroll to Data events, and add event selectors scoped to specific resources rather than “all S3 buckets” or “all Lambda functions” unless you genuinely need that. AWS expanded data event coverage significantly in 2025 (Amazon S3, Amazon EKS, Amazon Aurora DSQL, Amazon Bedrock, Amazon SageMaker AI, and Amazon Q Business all gained support during the year), so check the current supported services list before assuming a resource type is not covered.
Via CLI, an advanced event selector scoped to one S3 bucket looks like this:
aws cloudtrail put-event-selectors \
--trail-name org-management-trail \
--advanced-event-selectors '[
{
"Name": "Log S3 object writes on production-data bucket",
"FieldSelectors": [
{ "Field": "eventCategory", "Equals": ["Data"] },
{ "Field": "resources.type", "Equals": ["AWS::S3::Object"] },
{ "Field": "resources.ARN", "StartsWith": ["arn:aws:s3:::production-data/"] },
{ "Field": "readOnly", "Equals": ["false"] }
]
}
]'
That last field selector, filtering to readOnly: false, is doing real work. It means you only pay for and store write events, not every read, which is usually the majority of traffic on a busy bucket. Network activity events, which cover VPC endpoint traffic, are configured the same way through advanced event selectors and are worth enabling selectively on VPC endpoints handling sensitive traffic, particularly after AWS’s October 2025 expansion of which services support this event type.
Step 8: Send Events to CloudWatch Logs and Set Alarms
S3 storage is durable but not immediate. You would not want to grep through S3 objects to catch a live incident. Streaming CloudTrail events to CloudWatch Logs gives you near-real-time visibility and the ability to build metric filters and alarms on specific event patterns. AWS simplified this integration in late 2025, but you still need an IAM role granting CloudTrail permission to write to your log group.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "cloudtrail.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
Attach a permissions policy scoped to logs:CreateLogStream and logs:PutLogEvents on your specific log group ARN, then wire it into the trail:
aws cloudtrail update-trail \
--name org-management-trail \
--cloud-watch-logs-log-group-arn arn:aws:logs:us-east-1:ACCOUNTID:log-group:CloudTrail/OrgTrail:* \
--cloud-watch-logs-role-arn arn:aws:iam::ACCOUNTID:role/CloudTrail-CloudWatchLogs
Once events are flowing, create metric filters for the patterns you actually care about: root account usage, IAM policy changes, failed console sign-ins, security group changes, and CloudTrail itself being disabled (a common early move by an attacker trying to cover their tracks). Attach a CloudWatch alarm to each filter that notifies an SNS topic your on-call team actually watches. A metric filter for root account activity, for example, looks for $.userIdentity.type = "Root" in the log JSON. If that alarm ever fires outside of a planned maintenance window, treat it as a priority-one investigation.
Step 9: Create an Organization Trail for Multi-Account Visibility
If you manage more than one AWS account through AWS Organizations, an organization trail is close to mandatory rather than optional. It logs every member account automatically, including accounts created after the trail exists, which means nobody can quietly spin up a fresh account outside your audit coverage.
Sign in to the management account (or a delegated administrator account, if you have configured one), and either check Enable for all accounts in my organization during trail creation or run:
aws cloudtrail create-trail \
--name org-wide-trail \
--s3-bucket-name your-org-cloudtrail-logs-ACCOUNTID \
--is-organization-trail \
--is-multi-region-trail \
--enable-log-file-validation
Member accounts cannot disable or delete an organization trail. Only the management account can, which is exactly the control you want against a compromised or rogue member account. Route the destination bucket to a dedicated logging account with no other workloads in it, restrict who can access that account, and you have a reasonably strong guarantee that audit history survives even if a workload account is fully compromised.
Step 10: Set Up CloudTrail Lake for SQL and Natural-Language Queries
CloudTrail Lake is a managed, queryable event data store that sits on top of your trail data (or ingests events directly, bypassing S3 entirely if you prefer). Instead of downloading and parsing gzipped JSON files, you write SQL directly against your event history, and as of the 2025 updates, you can also generate queries from natural-language prompts and get AI-summarized results, currently in preview.
In the console, go to Lake in the left navigation and create an event data store, choosing which event categories to ingest (management, data, network activity, or a combination) and your retention window. Via CLI:
aws cloudtrail create-event-data-store \
--name security-investigations \
--advanced-event-selectors '[
{ "Name": "Management events only",
"FieldSelectors": [{ "Field": "eventCategory", "Equals": ["Management"] }] }
]' \
--retention-period 365 \
--termination-protection-enabled
A basic Lake query to find every console sign-in failure in the last week looks like this once your event data store is populated:
SELECT eventTime, userIdentity.userName, sourceIPAddress, errorMessage
FROM event_data_store_id
WHERE eventName = 'ConsoleLogin'
AND errorMessage IS NOT NULL
AND eventTime > date_add('day', -7, now())
ORDER BY eventTime DESC
The one planning note worth repeating from earlier: AWS has said CloudTrail Lake will stop accepting new customers after May 31, 2026. If you are reading this before that date, this is still a fully supported, actively developed feature to build on. If you are past it and have not already set up Lake, check current AWS documentation before designing new tooling around it, since your options may differ from what is described here.
Step 11: Query Historical Logs With Athena
Athena remains the standard way to query CloudTrail logs stored in S3 directly, independent of Lake, and it is worth setting up even if you also adopt Lake, since Athena queries the raw log files at whatever retention your S3 lifecycle policy allows, potentially years, at Glacier storage cost. From the Athena console, create a table pointing at your CloudTrail S3 prefix. AWS publishes a ready-made table definition. The shortened version looks like this:
CREATE EXTERNAL TABLE cloudtrail_logs (
eventversion STRING,
useridentity STRUCT<type:STRING,arn:STRING,accountid:STRING,username:STRING>,
eventtime STRING,
eventsource STRING,
eventname STRING,
sourceipaddress STRING,
errorcode STRING,
errormessage STRING
)
ROW FORMAT SERDE 'com.amazon.emr.hive.serde.CloudTrailSerde'
STORED AS INPUTFORMAT 'com.amazon.emr.cloudtrail.CloudTrailInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://your-org-cloudtrail-logs-ACCOUNTID/AWSLogs/ACCOUNTID/CloudTrail/';
Once the table exists, a query like “every IAM policy attached in the last 30 days” is standard SQL:
SELECT eventtime, useridentity.username, eventname, sourceipaddress
FROM cloudtrail_logs
WHERE eventname IN ('AttachUserPolicy','AttachRolePolicy','PutUserPolicy')
AND eventtime > '2026-05-01'
ORDER BY eventtime DESC;
Athena bills per query based on data scanned, so partition your table by date and region if your log volume is significant. An unpartitioned table on a year of high-volume logs turns a five-second question into an expensive full scan.
Step 12: Automate Alerts and Response With EventBridge and Lambda
CloudWatch alarms (Step 8) are good for “notify a human.” For “take action automatically,” route CloudTrail events through EventBridge, which can match specific API calls in near real time and trigger a Lambda function, Step Functions workflow, or SNS notification without you writing a polling loop.
A common pattern: automatically revoke a security group rule that opens SSH to the world the moment it is created. The EventBridge rule pattern matches the specific API call:
{
"source": ["aws.ec2"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventName": ["AuthorizeSecurityGroupIngress"]
}
}
Register it and target a Lambda function that inspects the event, checks whether the new rule opens a sensitive port to 0.0.0.0/0, and revokes it if so:
aws events put-rule \
--name auto-revoke-open-ssh \
--event-pattern file://ssh-rule-pattern.json
aws events put-targets \
--rule auto-revoke-open-ssh \
--targets "Id"="1","Arn"="arn:aws:lambda:us-east-1:ACCOUNTID:function:revoke-open-ingress"
Start with alerting-only automations before moving to auto-remediation. A Lambda function that is slightly too aggressive about “fixing” security group rules can break a legitimate deployment just as easily as it stops an attacker. Test in a non-production account first, and log every automated action CloudTrail itself would then capture, which is a useful sanity check that your automation is itself auditable.
Step 13: Control Costs With Filtering, Lifecycle Rules, and Budget Alarms
The last step is making sure this setup does not surprise anyone on next month’s AWS bill. Management events are cheap by the numbers, but data events, if enabled broadly with no filtering, are where costs escalate quickly in an active account. Three concrete controls:
- Scope data events narrowly. Use advanced event selectors (Step 7) to log specific buckets, tables, or functions rather than entire resource types, and filter to write-only activity where read auditing is not a compliance requirement.
- Set S3 lifecycle rules. Transition logs to S3 Glacier Instant Retrieval after 90 days and Glacier Deep Archive after a year, unless active investigations need faster access to older data.
- Create an AWS Budget with an alarm specifically tagged to CloudTrail and related services (S3, KMS, CloudWatch Logs) so a misconfigured broad data-event selector shows up as a cost anomaly within days, not at the end of the billing cycle.
It is also worth revisiting your event selectors quarterly. Teams frequently turn on broad data-event logging during an incident investigation, get the answer they needed, and forget to scope it back down. That is a recurring, avoidable cost leak worth a calendar reminder.
Complete Working Project: End-to-End Security Monitoring Pipeline
Putting Steps 1 through 13 together, here is what a complete, minimal-but-production-ready CloudTrail setup looks like as a single CLI sequence you can adapt and run against a sandbox account. This assumes the S3 bucket, KMS key, and IAM roles referenced already exist from the earlier steps.
#!/bin/bash
set -e
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
BUCKET="your-org-cloudtrail-logs-${ACCOUNT_ID}"
# 1. Create the organization (or account) trail
aws cloudtrail create-trail \
--name org-wide-trail \
--s3-bucket-name "$BUCKET" \
--is-multi-region-trail \
--enable-log-file-validation \
--kms-key-id alias/cloudtrail-key \
--cloud-watch-logs-log-group-arn "arn:aws:logs:us-east-1:${ACCOUNT_ID}:log-group:CloudTrail/OrgTrail:*" \
--cloud-watch-logs-role-arn "arn:aws:iam::${ACCOUNT_ID}:role/CloudTrail-CloudWatchLogs"
# 2. Start logging (required — trails do not start automatically)
aws cloudtrail start-logging --name org-wide-trail
# 3. Scope data events to production data only, write-only
aws cloudtrail put-event-selectors \
--trail-name org-wide-trail \
--advanced-event-selectors file://data-event-selectors.json
# 4. Create a Lake event data store for SQL investigation
aws cloudtrail create-event-data-store \
--name security-investigations \
--retention-period 365 \
--termination-protection-enabled
# 5. Confirm the trail is active and logging
aws cloudtrail get-trail-status --name org-wide-trail \
--query '{Logging:IsLogging,LatestDeliveryTime:LatestDeliveryTime}'
Running get-trail-status at the end is not decorative. It is the single fastest way to confirm the whole pipeline is actually working end to end, and it is the first command to run when something seems off later. IsLogging: true with a recent LatestDeliveryTime means events are flowing. Anything else means one of the steps above needs a second look.
AWS CloudTrail Pricing Breakdown for 2026
CloudTrail pricing is usage-based and, for most accounts doing standard management-event logging, close to free. Costs climb with data events, network activity events, and any add-on services (S3 storage, KMS requests, CloudWatch Logs ingestion) layered on top. Current U.S. list pricing:
| Event Type / Feature | Free Tier | Price After Free Tier | Notes |
|---|---|---|---|
| Management events | First copy per AWS Region, free | $2.00 per 100,000 events | Applies to additional copies beyond the first free delivery |
| Data events | None | $0.10 per 100,000 events | Billed per event once enabled on a resource |
| Network activity events | None | $0.10 per 100,000 events | VPC endpoint API traffic |
| S3 log storage | AWS S3 standard free tier applies | Standard S3 storage rates | Reduce with lifecycle rules to Glacier |
| CloudTrail Lake | Varies by ingestion/storage | Check current AWS pricing page | New customer enrollment ends May 31, 2026 |
| KMS encryption (optional) | AWS KMS free tier applies | Standard KMS request pricing | Only if using a customer-managed key |
The practical takeaway: a typical small-to-mid-size account logging only management events across all regions will stay within or very close to the free tier every month. The bill grows when data events get switched on broadly rather than scoped to the specific buckets, tables, or functions that actually need object-level auditing, which is exactly why Steps 7 and 13 spend so much time on filtering. Always confirm current rates on the official AWS CloudTrail pricing page before budgeting, since AWS updates pricing pages independently of documentation.
AWS CloudTrail vs Azure Monitor vs Google Cloud Audit Logs
If you operate across multiple clouds, or you are evaluating AWS against a migration target, here is how CloudTrail’s model compares to its closest equivalents: Azure Monitor Activity Log and Google Cloud Audit Logs.
| Feature | AWS CloudTrail | Azure Monitor Activity Log | Google Cloud Audit Logs |
|---|---|---|---|
| Core control-plane logging | Management events, first copy per region free | Activity log itself is free, retained 90 days at no charge | Admin Activity audit logs are free |
| High-volume data-plane logging | Data events, $0.10 per 100,000 | Requires export to Log Analytics workspace (billed as log ingestion) | Data Access audit logs, billed as part of Cloud Logging usage |
| Query interface | CloudTrail Lake (SQL + natural language), Athena over S3 | Log Analytics with Kusto Query Language (KQL) | Cloud Logging with Logging Query Language |
| Long-term retention | Indefinite in S3, cost scales with storage class | First 5 GB/month per billing account free in Analytics Logs tier, then ingestion-based | Ingestion-based after free allotment (exact current rate on Google Cloud pricing page) |
| Multi-account / multi-subscription rollout | Organization trail from management account | Azure Policy + diagnostic settings at scale | Aggregated sinks across a Google Cloud organization |
The underlying philosophy is similar across all three: control-plane and admin-activity logging is effectively free everywhere, and you pay once you need high-volume data-plane visibility or long-term queryable retention. Where they differ is ergonomics. CloudTrail’s advanced event selectors give more granular per-resource filtering out of the box than the equivalent Azure or GCP configuration typically requires, at the cost of a steeper initial learning curve. If you are choosing a platform based on audit logging alone, that is rarely the deciding factor. If you are already committed to AWS, the setup in this tutorial is the standard, well-documented path. For a deeper look at how CloudTrail data typically feeds into a broader security stack, see our comparison of Microsoft Sentinel vs Splunk vs Elastic for SIEM options that ingest CloudTrail logs directly.
Common Pitfalls When Setting Up AWS CloudTrail
Most CloudTrail problems are configuration gaps discovered during an incident rather than day-to-day operation, which makes them worse than average bugs. You find out at the worst possible time. These are the mistakes that show up most often.
- Creating a trail but never calling start-logging. The console wizard handles this automatically, but CLI and infrastructure-as-code setups frequently miss it, leaving a trail that looks fully configured and records nothing.
- Storing logs in the same account they audit. If an attacker gets administrator access to the account being logged, they can delete the evidence of how they got in. Route logs to a separate, tightly restricted logging account.
- Enabling data events on every S3 bucket and Lambda function “just in case.” This is the single most common cause of CloudTrail-related bill shock, and it usually happens because nobody scoped the event selectors before flipping the feature on.
- Forgetting that member accounts cannot see or modify an organization trail’s core settings. This is intentional, but it confuses teams who expect account-level control and then file tickets wondering why their trail configuration will not save.
- Skipping log file validation because it “isn’t needed yet.” Validation can only prove integrity from the point it was enabled forward. You cannot retroactively validate log files from before you turned it on, so there is no reason to delay.
- Not testing the S3 bucket policy before relying on it. An overly restrictive policy silently drops log delivery, and an overly permissive one exposes audit data. Verify with
get-trail-statusshortly after any bucket policy change. - Assuming CloudTrail alone is a complete security monitoring solution. It is a data source, not a detection engine. Pair it with GuardDuty, Security Hub, or a SIEM that actually alerts on the patterns that matter to your environment.
Troubleshooting AWS CloudTrail Issues
When something in this setup does not behave as expected, these are the issues that come up most often and how to resolve them.
| Symptom | Likely Cause | Fix |
|---|---|---|
| Trail shows as created but no logs appear in S3 | start-logging was never called | Run aws cloudtrail start-logging --name TRAIL_NAME and confirm with get-trail-status |
| AccessDenied error when CloudTrail tries to write to S3 | Bucket policy missing or scoped to the wrong account ID / prefix | Re-check the bucket policy against the exact trail ARN and account ID, redeploy |
| CloudWatch Logs group receives no events | IAM role for the CloudWatch Logs integration lacks logs:PutLogEvents permission, or the role ARN is wrong on the trail | Verify the role’s trust policy and permissions policy, re-attach with update-trail |
| Data events not appearing for a specific S3 bucket | Advanced event selector ARN prefix does not match the actual object paths | Confirm the resources.ARN StartsWith value matches real object keys, not just the bucket root |
| Organization trail not visible in member accounts | Expected behavior: member accounts cannot see the trail configuration, only its logs if granted access | No fix needed. This is by design. Grant read access to the central log bucket if members need visibility |
| Athena query returns zero rows despite logs existing in S3 | Table LOCATION path or partition projection settings do not match the actual S3 prefix structure | Verify the exact AWSLogs/ACCOUNTID/CloudTrail/ path and re-create the table |
| CloudTrail Lake query fails or times out | Event data store still ingesting historical backfill, or query lacks a time-range filter | Add an eventTime WHERE clause and retry after ingestion completes |
| Unexpectedly high CloudTrail line item on the AWS bill | Data events enabled broadly without resource-level scoping | Review advanced event selectors, narrow to specific resources, set a Budget alarm going forward |
| KMS-related AccessDeniedException on log delivery | Key policy does not grant kms:GenerateDataKey* to cloudtrail.amazonaws.com | Update the KMS key policy per Step 5 and retry log delivery |
If you are still stuck after working through this list, aws cloudtrail get-trail-status and the CloudTrail console’s own Trail health indicator are the two fastest ways to narrow down whether the problem is delivery, permissions, or query configuration.
Advanced Tips for Production CloudTrail Deployments
Once the core setup is running, a few refinements separate a functional deployment from a genuinely production-ready one. Use CloudTrail aggregated events, introduced at re:Invent 2025, for high-volume data-event trails where you need security monitoring but do not need every individual event. The five-minute summaries cut both noise and cost while preserving the signal most detection use cases actually need. Pair this with CloudTrail insights for data events, also new in the 2025 release cycle, which automatically flags spikes in API call rate or error rate without you having to hand-write every anomaly detection rule.
For teams managing infrastructure as code, define your trail, bucket policy, KMS key, and CloudWatch integration in Terraform or CloudFormation rather than the console, so the configuration is versioned and reviewable. If you are already using Terraform for other AWS infrastructure, see our guide to setting up Terraform for AWS for the underlying provider configuration this builds on.
Consider a dedicated, tag-enforced logging account as part of a broader AWS Organizations landing zone, with service control policies that prevent any account, including administrators, from disabling or modifying the organization trail. For response automation beyond the EventBridge pattern in Step 12, a Lambda function reacting to CloudTrail-sourced EventBridge events is a natural fit if you are already comfortable with building AWS Lambda functions. And if your organization runs workloads on Amazon EKS, make sure your EKS control plane logging is enabled alongside CloudTrail. Kubernetes API server audit logs and CloudTrail management events answer different questions and both matter during a cluster-level incident.
Finally, treat your CloudTrail configuration itself as something to monitor. A metric filter that alerts when a trail is stopped, deleted, or has its event selectors modified closes the loop on one of the most common attacker moves: disabling logging before doing anything else.
Frequently Asked Questions
Is AWS CloudTrail enabled by default?
Yes, in a limited form. Every AWS account automatically records 90 days of management event history at no charge, viewable in the CloudTrail console’s Event History. That default does not include data events, does not persist beyond 90 days, and is not a substitute for a configured trail with S3 storage.
How much does AWS CloudTrail cost per month?
For accounts logging only management events, often close to free, since the first copy of management events per region is included at no charge. Costs rise with data events ($0.10 per 100,000 events), network activity events ($0.10 per 100,000 events), S3 storage, and any KMS or CloudWatch Logs usage layered on top. Check the official AWS CloudTrail pricing page for current rates.
What is the difference between a trail and CloudTrail Lake?
A trail delivers log files to an S3 bucket (and optionally CloudWatch Logs) that you can query with tools like Athena. CloudTrail Lake is a managed, queryable event data store with built-in SQL support and, as of 2025, natural-language query generation. They are complementary, not mutually exclusive: many teams run both.
Can I recover CloudTrail logs older than 90 days without a trail?
No. The free 90-day Event History is the only default retention, and it is not retroactive to before you had an account, nor extendable. Without a configured trail delivering to S3 (or an event data store) during that period, logs older than 90 days are simply not recoverable. This is precisely why Step 3 of this tutorial should happen on day one of any new AWS account, not after an incident makes it urgent.
Do I need CloudTrail if I already use GuardDuty?
Yes. GuardDuty is a threat detection service that consumes CloudTrail (along with VPC Flow Logs and DNS logs) as one of its data sources. It does not replace the underlying audit trail. You need CloudTrail configured regardless of which detection or SIEM tooling sits on top of it.
Will CloudTrail Lake still be available after May 2026?
AWS has stated that CloudTrail Lake will stop accepting new customers after May 31, 2026, while existing customers can continue using it. If you are setting up a new CloudTrail deployment after that date, verify current availability and any replacement guidance in official AWS documentation before building new tooling around Lake specifically.
Does an organization trail cost more than individual account trails?
Pricing is based on event volume and type, not on whether a trail happens to be organization-scoped, so an organization trail generally costs about the same as configuring equivalent individual trails in every member account, with far less operational effort and no risk of a forgotten account going unmonitored.
Can a member account disable an organization trail?
No. Only the AWS Organizations management account (or a delegated administrator) can modify or delete an organization trail. Member accounts can see that the trail exists but cannot alter its configuration, which is a deliberate security control against a compromised member account covering its tracks.
AWS CloudTrail is not a “set it and forget it” service. The 2025 changes to data event coverage, aggregated events, and Lake alone show how much the feature set still moves year to year. Revisit your trail configuration, event selectors, and cost alarms on a regular schedule, and the 70 minutes spent on this setup will keep paying off well beyond the first audit or incident it helps you close out.
Related Coverage
- How to Set Up Terraform for AWS: 13 Steps, 75 Min [2026]
- How to Build an AWS Lambda API: 12 Steps, 60 Min [2026]
- How to Set Up Amazon EKS: 12 Steps, 90 Min [2026]
- Microsoft Sentinel vs Splunk vs Elastic: $24K-250K [2026]
- How to Set Up TOTP MFA: 12 Steps, 60 Min [2026]
- AWS Reserved vs Savings Plans vs Spot: 90% Off [2026]


