DEV Community

Cover image for "Never expire" log groups: what survives when you delete a Lambda, and the account-level retention AWS added this week
Gonzalo Gjuka
Gonzalo Gjuka

Posted on Edited on Originally published at controlcloud.dev

"Never expire" log groups: what survives when you delete a Lambda, and the account-level retention AWS added this week

The note on what survives a VM has a serverless cousin: the log group. When a Lambda function is invoked for the first time, CloudWatch creates /aws/lambda/<name> with "Never expire" retention, and when you delete the function that log group does not go with it. The Lambda documentation says it in one line: "Log groups aren't deleted automatically when you delete a function". On November 9 AWS published account-level retention policies, the first way to put an expiry on all of them at once. Everything below comes from docs.aws.amazon.com and the pricing page; the sources are at the end.

Why it stays at "Never expire"

CloudWatch keeps log events indefinitely unless someone sets a retention; in the console that state is labelled "Never Expire". Lambda creates the log group for you, on the first invocation, with that value. A Terraform stack that declares the function but not the log group leaves a resource Terraform never had in state: terraform destroy removes the function and the log group stays, with its bytes and its line on the bill. It is the same mechanism as a disk created outside the plan.

Finding them: no retention, sorted by bytes

describe-log-groups returns retentionInDays only when there is a retention; if the group never expires, the field is absent. That lets you filter them with JMESPath by comparing against null, and storedBytes tells you how much each one weighs:

# log groups with no retention, heaviest first (CLI v2 paginates by itself)
aws logs describe-log-groups --output table \
  --query "reverse(sort_by(logGroups[?retentionInDays==null].{name:logGroupName,storedBytes:storedBytes,class:logGroupClass},&storedBytes))"

# Lambda ones only
aws logs describe-log-groups --log-group-name-prefix /aws/lambda \
  --query "logGroups[?retentionInDays==null].[logGroupName,storedBytes]" --output text

# functions that exist today, to cross against the list above
aws lambda list-functions --query "Functions[].FunctionName" --output text
Enter fullscreen mode Exit fullscreen mode

A /aws/lambda/x log group with no function x in the second list is an orphan: the function was deleted and the logs stayed. The ones that do have a function but no retention are the other problem: they grow every month.

What it costs

In us-east-1, the Standard class charges US$ 0.50 per GB ingested and US$ 0.03 per GB-month stored (compressed); the Infrequent Access class, US$ 0.25 per GB ingested and US$ 0.018 per GB-month. The free tier covers 5 GB. Storage is what accumulates: 100 GB nobody reads is US$ 3 a month forever, and a noisy function that logs every request can leave several hundred GB before anyone looks.

Retention per log group

# one log group
aws logs put-retention-policy --log-group-name /aws/lambda/my-function --retention-in-days 30

# every Lambda log group without retention, to 30 days
for g in $(aws logs describe-log-groups --log-group-name-prefix /aws/lambda \
  --query "logGroups[?retentionInDays==null].logGroupName" --output text); do
  aws logs put-retention-policy --log-group-name "$g" --retention-in-days 30
done
Enter fullscreen mode Exit fullscreen mode

The accepted values are fixed: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288 and 3653 days. Expired events can take up to 72 hours to be deleted. In Terraform, an aws_cloudwatch_log_group with retention_in_days and the name /aws/lambda/<function> declared before the function keeps the group in state, and skip_destroy = true preserves it on purpose through a destroy.

Account-level retention: what is new since November 9

An account-level retention policy sets one retention for every log group in the region or for a subset by name prefix, and chooses whether it applies to existing groups or only to new ones. For the Lambda ones:

aws logs put-account-policy \
  --policy-name LambdaRetentionPolicy \
  --policy-type RETENTION_POLICY \
  --policy-document '{"retentionInDays": 90, "enforcementMode": "RETROACTIVE"}' \
  --selection-criteria 'LogGroupNamePrefix="/aws/lambda"'

# --dry-run shows which groups it would apply to without creating anything
Enter fullscreen mode Exit fullscreen mode
  • enforcementMode: RETROACTIVE reaches the groups that already exist and the new ones; NEW_ONLY, only those created afterwards.
  • The minimum is 30 days; the maximum, 3653, or NEVER_EXPIRE. Up to 20 policies per region, and when several match, the longest retention wins.
  • The account policy replaces the log group's own retention, even when the group's was longer. It takes 24 hours to apply.
  • No additional cost. Not available in Bahrain or the UAE.

What the policy does not do is delete the log group: an orphan with 90-day retention stops accumulating bytes and weighs zero in three months, but it is still listed. For it to disappear you need aws logs delete-log-group, and that is best done from the crossed list above, not with a for over everything under /aws/lambda/.

What Control Cloud does with this

Control Cloud's AWS inventory reads instances, volumes, elastic addresses, network interfaces, NAT gateways, load balancers, RDS, EKS, VPCs and subnets through the API, with a read-only credential, and flags what was left without an owner after a delete. It does not read Lambda functions or CloudWatch log groups yet: today, that is what the commands in this note are for. If you want to see the inventory on your account, request access; to connect it with read permissions, see the AWS guide.

Sources

  • AWS Lambda Developer Guide, "Working with the Lambda logging and log groups" ("By default, CloudWatch automatically creates a log group named /aws/lambda/…") and "Logging in Node.js" ("Log groups aren't deleted automatically when you delete a function").
  • Amazon CloudWatch Logs User Guide, "Working with log groups and log streams" (indefinite retention by default, "Never Expire", account-level retention policies with the put-account-policy example).
  • AWS What's New, November 9, 2026, "Amazon CloudWatch Logs now supports account-level log retention policies"; CLI reference logs put-account-policy (RETROACTIVE / NEW_ONLY, 30-day minimum, 20 policies, 24 hours, replaces the group's).
  • CLI reference: logs put-retention-policy (accepted values, deletion up to 72 hours later) and logs describe-log-groups (retentionInDays absent when never expire, storedBytes).
  • Amazon CloudWatch Pricing, US East (N. Virginia): US$ 0.50 and US$ 0.25 per GB ingested, US$ 0.03 and US$ 0.018 per GB-month, 5 GB free.
  • Terraform Registry, aws_cloudwatch_log_group (retention_in_days, skip_destroy); hashicorp/terraform-provider-aws, issue #454 ("When you delete the lambda, it doesn't delete anything from CloudWatch").

Top comments (0)