CloudWatch log groups that never expire: How to Find Them and What They Cost

CloudWatch log groups that never expire are log groups created with the default retention of Never Expire, accumulating ingestion and storage charges indefinitely. They are one of the most reliable sources of pure waste in an AWS account, because nothing in the console flags them and nothing fails when they exist — the bill simply stays higher than it needs to be.

This page covers what they cost, the command that lists yours, and — importantly — what to check before deleting anything.

What they are and why they linger

CloudWatch Logs charges for ingestion — a one-off charge per gigabyte as logs arrive — and for storage, charged monthly for as long as the data is retained.

The default retention on a new log group is Never Expire, and log groups are created automatically by Lambda, ECS, API Gateway, VPC Flow Logs, EKS control plane logging, and others. Nobody chooses the default; it simply applies. Years later the account holds every debug line ever emitted.

What they cost

Ingestion is roughly $0.50 per GB and storage roughly $0.03 per GB-month. A service emitting 10 GB a day costs about $150 a month to ingest, and the stored data grows by 300 GB a month — around $9 a month in storage, added again every month, indefinitely.

After three years that accumulated storage alone is roughly $300 a month for logs nobody has queried since the week they were written.

Note that setting a retention period does not refund ingestion, which is already spent. It caps the storage growth, which is the part that compounds.

Finding yours

# Log groups with no retention set, largest first.
aws logs describe-log-groups \
  --query 'reverse(sort_by(logGroups[?retentionInDays==null],&storedBytes))[*].[logGroupName,storedBytes]' \
  --output table | head -30

# Total bytes held with no expiry.
aws logs describe-log-groups \
  --query 'sum(logGroups[?retentionInDays==null].storedBytes)' --output text

# Set retention on all of them at once (30 days shown — choose deliberately).
aws logs describe-log-groups --query 'logGroups[?retentionInDays==null].logGroupName' --output text \
  | tr '\t' '\n' \
  | xargs -I{} aws logs put-retention-policy --log-group-name {} --retention-in-days 30

Run that last command only after reading the caveats.

Before you delete

Setting a retention period deletes anything already older than it, and the deletion is immediate and irreversible. Applying 30-day retention to a group holding three years of logs destroys those three years within hours.

Before applying a policy in bulk:

  • Identify groups with a compliance or audit retention requirement. Security, access, and audit logs frequently have one measured in years.
  • Export anything worth keeping to S3 first — S3 storage is roughly a tenth the price, and Athena queries it perfectly well.
  • Apply a generous retention first (say a year), then tighten once you know what is actually queried.
aws logs create-export-task --log-group-name /aws/lambda/my-function \
  --from 1577836800000 --to 1735689600000 \
  --destination my-log-archive-bucket --destination-prefix lambda/my-function

Stopping them coming back

Set retention at creation. In Terraform, aws_cloudwatch_log_group with an explicit retention_in_days; in CDK, logRetention on the construct. For log groups AWS creates implicitly, an EventBridge rule on CreateLogGroup that invokes a Lambda to apply a default is a well-worn pattern and worth the twenty lines.

Reduce ingestion as well as retention: sampling, log levels, and structured logging cut the larger of the two charges. Debug-level logging left on in production is usually the single biggest contributor.

For logs you must keep but rarely query, the Infrequent Access log class halves the ingestion rate to roughly $0.25 per GB. It gives up Live Tail, metric filters, and subscription filters, which makes it a poor fit for anything driving an alarm and a good fit for audit and compliance logs that exist to be searched twice a year.


Prices quoted are us-east-1 list rates, last checked on 2026-08-22, and are shown with the arithmetic so you can substitute the rates for your own region. Always confirm against the AWS pricing page before acting on a number.