Back to blog
AWSBest PracticesCost OptimizationMonitoring & LoggingOperations & Compliance

CloudWatch Log Group Has No Retention Policy

Learn why CloudWatch log groups with no retention policy waste money and create compliance risk, plus CLI, Terraform, and policy-as-code fixes to enforce log retention.

TL;DR

A CloudWatch log group with no retention policy keeps logs forever, quietly inflating your storage bill and complicating compliance. Set a retention period with aws logs put-retention-policy and enforce it across all log groups so logs expire on a schedule that matches your needs.

CloudWatch Logs is where most AWS workloads end up dumping their output. Lambda invocations, VPC flow logs, ECS containers, API Gateway access logs, and anything you wire up through the unified CloudWatch agent all flow into log groups. By default, those log groups have no expiration date attached, which means every log event you ever generate stays put until someone deletes it manually.

This check flags any log group where the retention setting is "Never expire." It sounds harmless, but indefinite retention has real consequences for cost, compliance, and operational hygiene.

Note: A CloudWatch retention policy controls how long log events are kept inside a log group. It does not delete the log group itself, only the data older than the configured window. When set to "Never expire," AWS keeps the data indefinitely and bills you for the storage every month.


What this check detects

The account_logretention check inspects every CloudWatch log group in an AWS account and looks at the retentionInDays attribute. If that attribute is absent, the log group never expires events, and the check reports a finding.

You can reproduce the same query with the CLI. Log groups without a retention policy simply have no retentionInDays field in the response:

aws logs describe-log-groups \
  --query "logGroups[?retentionInDays==null].[logGroupName,storedBytes]" \
  --output table

Any log group that shows up here is storing data forever.


Why it matters

Cost creep that never stops

CloudWatch Logs charges for ingestion and for archival storage. Storage is billed per GB per month, and with no retention policy that number only goes up. A chatty Lambda function or a debug-level application log can quietly accumulate hundreds of gigabytes over a couple of years. Nobody notices because no single month's bill jumps, but the baseline keeps climbing.

Warning: Verbose log groups are the usual culprits. VPC flow logs and DNS query logs can ingest tens of GB per day on a busy account. Left to never expire, a single flow log group can become one of the largest line items in your CloudWatch bill.

Compliance and data governance risk

Many regulatory frameworks specify how long you may retain certain data, not just how long you must keep it. GDPR data minimization, PCI DSS log retention windows, and internal data governance policies often require you to delete logs after a defined period. Logs that live forever can include IP addresses, request payloads, user identifiers, and other data you are supposed to age out. "We kept everything indefinitely" is a hard position to defend in an audit.

Slower investigations and noisier searches

When you run CloudWatch Logs Insights queries during an incident, a log group with years of unbounded history makes scans slower and more expensive. Retention that matches your actual investigation window keeps queries fast and keeps the data you scan relevant.

Note: If you genuinely need long-term log retention for compliance, the right pattern is to export logs to S3 with a lifecycle policy and cheap storage classes, not to leave them sitting in CloudWatch at full price. More on that below.


How to fix it

Set retention on a single log group

The fastest fix is a one-line CLI command. Pick a retention period in days that matches your needs. Valid values are 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, and 3653.

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

Danger: Setting a retention period applies to existing log events, not just new ones. If you set 30 days on a log group that has two years of history, CloudWatch will delete everything older than 30 days, and that deletion is irreversible. Confirm you do not need the older data, or export it to S3 first.

Fix every log group at once

Most accounts have dozens of offending log groups. This loop finds every group with no retention policy and applies a default. Adjust the number to suit your environment.

aws logs describe-log-groups \
  --query "logGroups[?retentionInDays==null].logGroupName" \
  --output text | tr '\t' '\n' | while read -r lg; do
    echo "Setting 90-day retention on $lg"
    aws logs put-retention-policy \
      --log-group-name "$lg" \
      --retention-in-days 90
done

Tip: Run the loop with the echo line only first, so you can review the full list of log groups before any retention is applied. Once you are happy with the list, uncomment the put-retention-policy call.

Console steps

  1. Open the CloudWatch console and go to Logs > Log groups.
  2. Add the Retention column or sort by it to find groups marked "Never expire."
  3. Select a log group, choose Actions > Edit retention setting.
  4. Pick a retention period and save.

Fix it in infrastructure as code

If your log groups are created by Terraform, set retention_in_days explicitly. The most common cause of this finding is a log group that AWS auto-created for a Lambda function, which never gets a retention policy. Declare the log group yourself so you control it:

resource "aws_cloudwatch_log_group" "lambda" {
  name              = "/aws/lambda/my-function"
  retention_in_days = 90
}

For CloudFormation:

{
  "Type": "AWS::Logs::LogGroup",
  "Properties": {
    "LogGroupName": "/aws/lambda/my-function",
    "RetentionInDays": 90
  }
}

How to prevent it from happening again

Fixing the current findings is the easy part. The harder problem is that new log groups appear constantly, especially auto-created ones, and they default to never expire. You need automation to keep this in check.

Catch it in CI/CD with policy as code

Block Terraform plans that create log groups without retention. Here is an Open Policy Agent rule for use with Conftest or a similar gate:

package main

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_cloudwatch_log_group"
  not resource.change.after.retention_in_days
  msg := sprintf("Log group '%s' has no retention_in_days set", [resource.address])
}

With checkov, the built-in check CKV_AWS_338 flags log groups with less than one year of retention, and you can configure your own minimum.

Auto-remediate with an EventBridge rule

For log groups created outside your IaC pipeline, set up an EventBridge rule that triggers on the CreateLogGroup API call and invokes a Lambda function to apply a default retention policy. This catches the auto-created Lambda log groups that slip past code review entirely.

Tip: Lensix runs the account_logretention check continuously, so any new log group without a retention policy surfaces automatically without you having to script periodic scans. Pair that with an EventBridge auto-remediation Lambda and the problem fixes itself.

Sweep periodically as a backstop

Even with gates in place, run the describe-and-fix loop from above on a schedule, perhaps as a weekly scheduled Lambda. It is a cheap safety net that catches anything the other layers miss.


Best practices

  • Match retention to purpose. Application debug logs might need 14 to 30 days. Security and audit logs often need a year or more. Do not apply one blanket number to everything without thinking about each log group's role.
  • Archive long-term logs to S3. If you need years of retention for compliance, export to S3 and use lifecycle rules to move data to Glacier. CloudWatch storage is far more expensive than S3 Glacier for cold data.
  • Standardize a default. Pick an organizational default such as 90 days and apply it everywhere unless a specific reason justifies otherwise. Predictability beats per-team guesswork.
  • Control ingestion, not just retention. Retention caps how long data lives, but reducing log verbosity at the source cuts both ingestion and storage costs. Drop debug logging in production.
  • Tag and review. Tag log groups by owner and environment so you can attribute cost and quickly identify which team owns an unbounded log group.

Warning: Do not blindly set aggressive retention on log groups feeding a metric filter or a downstream SIEM. Make sure your alerting and forwarding happen before the data ages out, or you may lose visibility into events you still rely on.

Setting a retention policy is one of the lowest-effort, highest-return cleanups in AWS. It trims cost, tightens compliance, and keeps your logs relevant, all from a single API call. The real win is making sure no new log group ever escapes without one.