Skip to main content

Your CloudWatch Logs never expire - and that is a growing bill

· 3 min read
Founder, Dragon Fractal · ex-AWS engineer

Short version: By default, a CloudWatch log group keeps logs forever. Stored logs cost $0.03/GB-month, and every Lambda, ECS task, and API Gateway you have been running for years has been piling them up. Setting a retention policy is a one-line fix per log group. Here is how to find the never-expiring ones and cap them.

Why this quietly grows

Two things bill on CloudWatch Logs: ingestion ($0.50/GB, one-time) and storage ($0.03/GB-month, forever). New log groups are created with retentionInDays unset, meaning never expire, so storage grows without bound. Nobody notices because it is a slowly rising line, not a spike. Debug logs from 2022 are still costing you money today.

Step 1 - Find log groups with no retention set

aws logs describe-log-groups \
--query 'logGroups[?retentionInDays==`null`].{Name:logGroupName,Bytes:storedBytes}' \
--output table

Sort by size to find the worst offenders first:

aws logs describe-log-groups \
--query 'reverse(sort_by(logGroups[?retentionInDays==`null`], &storedBytes))[:20].[logGroupName,storedBytes]' \
--output table

Step 2 - Set a retention policy

Pick a sane default (30/90/365 days depending on the log's purpose) and apply it:

aws logs put-retention-policy \
--log-group-name /aws/lambda/my-function \
--retention-in-days 90

Apply 90 days to every never-expiring group at once:

for lg in $(aws logs describe-log-groups \
--query 'logGroups[?retentionInDays==`null`].logGroupName' --output text); do
echo "setting 90d on $lg"
aws logs put-retention-policy --log-group-name "$lg" --retention-in-days 90
done

Retention applies going forward and prunes existing logs older than the window, so storage drops on its own after you set it.

The caveat: audit and compliance logs

Do not blanket-expire everything. CloudTrail, VPC Flow Logs, and anything under a compliance regime (PCI, HIPAA, SOC 2) may have a required minimum retention. For those, keep a longer window - or better, export to S3 (or S3 Glacier) for long-term retention at a fraction of CloudWatch's storage price, then expire the CloudWatch copy. Separate "I might debug this" logs (short retention) from "I must retain this" logs (export + policy).

Do it automatically

Cloud Cost Analyzer's cloudwatch-logs-retention rule flags log groups with infinite (or excessive) retention and estimates the savings from capping them - alongside 88 other cost rules:

curl -sSL https://releases.dragonfractal.com/install.sh | sh
cca scan --provider aws

The agent runs in your environment with read-only access, so your AWS credentials never leave it. See the AWS setup and required IAM permissions

CLI Reference

  • aws logs describe-log-groups
  • aws logs put-retention-policy