GuideStorage

How to Set Up S3 Lifecycle Policies That Cut Storage Costs

S3 lifecycle policies move data to cheaper storage classes automatically. How to write rules that save money without tripping retrieval and early delete fees.

Erik LoydJuly 9, 20267 min read

Key takeaways

  • Standard storage costs $0.023 per GB-month; the archive tiers run as low as $0.00099, a 96 percent drop
  • Most buckets never get a lifecycle policy because the default works and nobody owns storage class decisions
  • Noncurrent versions and abandoned multipart uploads are the two rules every bucket should have, even hot ones
  • Minimum storage durations and per-object transition fees can reverse the savings on small or short-lived objects

S3 charges by the gigabyte-month, and the gap between its storage classes is enormous: Standard costs $0.023 per GB-month in us-east-1, while Glacier Deep Archive costs $0.00099 for the same durability. A lifecycle policy is the mechanism that moves objects down that ladder automatically. Buckets without one pay the Standard rate on every byte forever, including logs nobody has read since the week they were written.

The rules themselves are a few lines of configuration. The work is knowing which buckets need them, which class each dataset belongs in, and which fine print can turn a well-meaning rule into a bigger bill. This guide walks through the whole sequence, from finding the buckets to verifying the savings landed.

Find the buckets paying Standard rates for cold data

Start with the bill, not the bucket list. In Cost Explorer, group S3 spend by usage type: TimedStorage-ByteHrs is Standard storage, and the equivalents for the other classes carry their own usage types (TimedStorage-SIA-ByteHrs for Standard-IA, TimedStorage-GIR-ByteHrs for Glacier Instant, and so on). A bill that is overwhelmingly TimedStorage-ByteHrs in an account holding years of logs, backups, or exports is the signature of missing lifecycle policies.

Then check which buckets actually have policies. There is no console view that lists lifecycle coverage across buckets, which is a big part of why the gap persists, but a short script gets you there:

for b in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
  aws s3api get-bucket-lifecycle-configuration --bucket "$b" \
    --query 'Rules[].ID' --output text 2>/dev/null \
    || echo "NO LIFECYCLE: $b"
done
Sort the no-lifecycle list by size (S3 Storage Lens or the BucketSizeBytes CloudWatch metric) and work from the largest down. One 200 TB log bucket matters more than forty small ones.

Match each bucket's data to a storage class

Every class is priced for a specific access pattern, and the discount pays for tolerance of slower or costlier retrieval. The us-east-1 rates:
ClassPrice per GB-monthBuilt for
Standard$0.023Data read routinely
Standard-IA$0.0125Read less than monthly, needed instantly
Glacier Instant Retrieval$0.004Read rarely (quarterly or less), needed instantly
Glacier Flexible Retrieval$0.0036Restores can wait minutes to hours
Glacier Deep Archive$0.00099Compliance archives; restores can wait 12 hours

The pattern to internalize: the infrequent-access classes keep millisecond reads and charge a per-GB retrieval fee instead ($0.01 per GB from Standard-IA), while the deeper Glacier tiers make you wait for restores. The ladder maps neatly onto household storage: Standard is the closet you reach into weekly, the IA classes are the attic, and Deep Archive is the offsite unit you rent because the rules say you must keep the boxes. Ask of each dataset: how often is it read, and when it is read, how fast does it need to arrive? Application logs might sit in Standard for 30 days while anyone still debugs against them, then step down to Standard-IA, then to a Glacier tier once the only remaining reader is an auditor.

Write the rules, including the two everyone forgets

A lifecycle configuration attaches to the bucket and runs daily with no compute of yours involved. A typical progression for a log bucket:

{
  "Rules": [
    {
      "ID": "age-out-logs",
      "Filter": { "Prefix": "logs/" },
      "Status": "Enabled",
      "Transitions": [
        { "Days": 30, "StorageClass": "STANDARD_IA" },
        { "Days": 90, "StorageClass": "GLACIER_IR" },
        { "Days": 365, "StorageClass": "DEEP_ARCHIVE" }
      ],
      "Expiration": { "Days": 2555 }
    }
  ]
}

Two more rules belong in nearly every bucket, including ones whose live data must stay hot:

Expire noncurrent versions. On a versioned bucket, every overwrite keeps the old copy at full price, invisibly. A NoncurrentVersionTransition or NoncurrentVersionExpiration rule caps how long those superseded copies live. Versioned buckets without one routinely store several old bytes for every current byte.
Abort incomplete multipart uploads. Failed large uploads leave their parts in the bucket, unlisted and billed, until someone removes them. AbortIncompleteMultipartUpload after 7 days cleans them up automatically. This rule has no downside and belongs on every bucket in the account.

Use Intelligent-Tiering when you cannot predict the pattern

If a bucket's access pattern is genuinely unknown, or different objects in it behave differently, S3 Intelligent-Tiering makes the class decision per object. It watches each object and moves anything untouched for 30 days to an infrequent-access tier priced like Standard-IA, then after 90 days to an archive-instant tier priced like Glacier Instant, moving objects back up the moment they are read. There are no retrieval fees between the automatic tiers; the cost is a monitoring fee of $0.0025 per 1,000 objects per month.

That per-object fee is the deciding variable. On buckets full of large objects the fee is noise and Intelligent-Tiering is close to a free option. On buckets holding hundreds of millions of tiny objects, the monitoring fee can exceed what tiering saves, and a prefix-based lifecycle rule you wrote yourself does better. As a rule of thumb, objects need to average above roughly 1 MB before Intelligent-Tiering is comfortably ahead for unpredictable data.

Check the fine print before you flip it on

Three mechanisms can claw back the savings, and all three are avoidable once you know they exist:

Minimum storage durations. Standard-IA and One Zone-IA bill a minimum of 30 days, Glacier Instant and Flexible 90 days, Deep Archive 180 days. Objects deleted or transitioned again before the minimum pay the remainder as an early-delete fee. Data with a short remaining life should skip the intermediate tiers or stay in Standard.
Small-object economics. The IA classes and Glacier Instant bill a minimum object size of 128 KB, and every transition is a request: $0.01 per 1,000 objects into Standard-IA, $0.02 per 1,000 into Glacier Instant, more into the deeper tiers. A prefix holding fifty million 4 KB objects gains almost nothing from tiering and pays real money to transition. Aggregate small objects before archiving them, or leave them where they are.
Retrieval fees on the way back. Reading from Standard-IA costs $0.01 per GB on top of request charges. A dataset that gets a monthly full scan does not belong in an IA class no matter how good the storage rate looks. Model one round trip of your actual read pattern before committing.

Verify the savings landed

Lifecycle transitions run once a day, so the storage-class shift shows up quickly: within a few days, the bucket's TimedStorage-ByteHrs line starts falling and the destination class's usage type starts growing. Check Cost Explorer after the first week, and again after the first full month, comparing total S3 spend for the bucket against the pre-policy baseline. The first month will also carry the one-time transition request charges, so judge the steady state from month two.

If a rule seems to be doing nothing, the usual causes are a filter prefix that does not match the real key layout, or versioning: transitions on a versioned bucket apply to current versions, and the noncurrent copies need their own rule. S3 Storage Lens shows per-bucket, per-class byte counts that settle the question in one screen.

None of this fine print changes the headline: for most accounts, the large, old, quiet buckets are the cheapest savings available on the whole bill, sitting behind a one-time configuration change. Where block storage hides the equivalent waste is covered in our gp2 vs gp3 comparison, and the rest of our storage research maps the full ladder.

See what this looks like on your own bill.