Skip to content
AWS aws storage 5 min read

S3 Lifecycle Policies

Data has a life. A log file is hot the day it’s written and cold a month later. A draft uploaded by a user might never be touched again. Paying full-price Amazon S3 (Simple Storage Service — AWS’s object storage) prices to keep cold data on hot storage forever is one of the most common ways cloud bills quietly grow. An S3 lifecycle policy is a set of rules that automatically moves objects to cheaper storage classes or deletes them after a number of days, with zero code and zero ongoing effort once it’s set up.

What a lifecycle policy actually does

A lifecycle policy is attached to a bucket and contains one or more rules. Each rule does one or both of these things:

  • Transition — move an object to a cheaper storage class after it has aged a set number of days (for example, move to Glacier after 90 days).
  • Expiration — permanently delete an object after a set number of days.

Rules can apply to the whole bucket or be narrowed by an object key prefix (a folder-like path such as logs/) or by an object tag (a key-value label you attach to objects). This lets you treat logs/ differently from invoices/ in the same bucket.

When to use this (and when not to)

Use lifecycle rules when you have predictable, time-based access patterns: logs, backups, raw data dumps, old user uploads. Use them to enforce data-retention rules (delete after 7 years for compliance) automatically.

Do not use rules to micro-optimise objects whose access pattern is unpredictable — for that, use S3 Intelligent-Tiering, a storage class that moves objects between tiers automatically based on real access, with no per-transition charge. Lifecycle rules are for known schedules; Intelligent-Tiering is for unknown ones.

The cost gotchas you must know first

Lifecycle transitions are not free, and beginners often lose money on them.

Cost warning: Every object transition is billed as a request (roughly $0.01 per 1,000 transition requests to Glacier-class storage in 2026). If you transition millions of tiny objects, the one-time transition cost plus the per-object overhead of the colder class can cost more than just leaving them where they are.

Two hard rules enforced by S3:

  • Minimum object size for auto-tiering benefit. Objects smaller than 128 KB are not transitioned to Intelligent-Tiering’s deeper tiers, and small objects in Glacier classes carry a per-object metadata overhead (about 8 KB charged at S3 Standard rates plus 32 KB in Glacier). A 2 KB file in Glacier can cost more than in Standard.
  • Minimum age before transition. You generally cannot transition an object to Standard-IA or One Zone-IA until it is at least 30 days old. S3 ignores or rejects shorter windows.

Versioning trap: If the bucket has versioning enabled, deleting or overwriting an object does not free space — it creates a noncurrent version that you keep paying for forever. You must add a NoncurrentVersionExpiration rule, or your bill will climb silently. See S3 versioning.

Storage class transition targets

Storage classBest forMin storage durationRetrieval
Standard-IA (Infrequent Access)Data accessed monthly30 daysInstant
One Zone-IARe-creatable data, one AZ30 daysInstant
Glacier Instant RetrievalArchives needing instant reads90 daysInstant
Glacier Flexible RetrievalArchives, minutes-to-hours reads90 daysMinutes–hours
Glacier Deep ArchiveLong-term cold storage180 daysUp to 12 hours

A common, sensible chain: Standard → Standard-IA at 30 days → Glacier Flexible at 90 days → expire at 365 days.

Create a rule in the AWS Management Console

  1. Open the S3 console and click your bucket name.
  2. Go to the Management tab.
  3. Under Lifecycle rules, click Create lifecycle rule.
  4. Give it a name like archive-and-expire-logs.
  5. Under Choose a rule scope, pick Limit the scope and enter a prefix such as logs/ (or pick Apply to all objects and tick the acknowledgement box).
  6. Under Lifecycle rule actions, tick: Transition current versions of objects, Expire current versions of objects, and Permanently delete noncurrent versions (if versioning is on).
  7. Add a transition: Standard-IA after 30 days; add another: Glacier Flexible Retrieval after 90 days.
  8. Set Expire current versions to 365 days.
  9. Set Delete noncurrent versions to 30 days after they become noncurrent.
  10. Review the summary timeline and click Create rule.

Create the same rule with the AWS CLI

First write the rule as JSON. This targets the logs/ prefix, transitions and expires current objects, and cleans up noncurrent versions.

{
  "Rules": [
    {
      "ID": "archive-and-expire-logs",
      "Filter": { "Prefix": "logs/" },
      "Status": "Enabled",
      "Transitions": [
        { "Days": 30, "StorageClass": "STANDARD_IA" },
        { "Days": 90, "StorageClass": "GLACIER" }
      ],
      "Expiration": { "Days": 365 },
      "NoncurrentVersionExpiration": { "NoncurrentDays": 30 }
    }
  ]
}

Apply it (AWS CLI v2):

aws s3api put-bucket-lifecycle-configuration \
  --bucket my-app-logs-bucket \
  --lifecycle-configuration file://lifecycle.json

This command produces no output on success. Read it back to confirm:

aws s3api get-bucket-lifecycle-configuration \
  --bucket my-app-logs-bucket

Output:

{
    "Rules": [
        {
            "ID": "archive-and-expire-logs",
            "Filter": { "Prefix": "logs/" },
            "Status": "Enabled",
            "Transitions": [
                { "Days": 30, "StorageClass": "STANDARD_IA" },
                { "Days": 90, "StorageClass": "GLACIER" }
            ],
            "Expiration": { "Days": 365 },
            "NoncurrentVersionExpiration": { "NoncurrentDays": 30 }
        }
    ]
}

To scope by tag instead of prefix, replace the Filter with { "Tag": { "Key": "archive", "Value": "true" } }.

Defining it as Infrastructure as Code

If you manage buckets with Terraform, declare the lifecycle inline so it lives in version control:

resource "aws_s3_bucket_lifecycle_configuration" "logs" {
  bucket = aws_s3_bucket.logs.id

  rule {
    id     = "archive-and-expire-logs"
    status = "Enabled"

    filter { prefix = "logs/" }

    transition {
      days          = 30
      storage_class = "STANDARD_IA"
    }
    transition {
      days          = 90
      storage_class = "GLACIER"
    }
    expiration { days = 365 }

    noncurrent_version_expiration { noncurrent_days = 30 }
  }
}

Best Practices

  • Always pair versioning with a NoncurrentVersionExpiration rule so old versions cannot accumulate forever.
  • Skip transitions for buckets full of tiny (under 128 KB) objects — the per-object overhead and request charges usually outweigh the storage savings.
  • Use prefixes or tags to apply different schedules within one bucket instead of creating many buckets.
  • Add an AbortIncompleteMultipartUpload action (7 days is typical) so failed uploads do not leave invisible billable fragments.
  • Respect the minimum durations: 30 days for IA classes, 90 for Glacier instant/flexible, 180 for Deep Archive — you pay the full minimum even if you delete sooner.
  • For unpredictable access, prefer Intelligent-Tiering over hand-written transition rules.
  • Review the rule’s timeline summary in the console before saving — it shows exactly when each object will move or be deleted.
Last updated June 15, 2026
Was this helpful?