Cut S3 Costs with Lifecycle Rules
25 Dec 2025Objects sitting in S3 Standard that nobody has read in six months are pure waste. Lifecycle rules move them to cheaper storage automatically — set it up once, stop thinking about it.
The storage classes that matter
| Class | Use for | Retrieval |
|---|---|---|
| Standard | Actively accessed data | Instant |
| Standard-IA | Accessed monthly, not daily | Instant, per-GB retrieval fee |
| Glacier Instant Retrieval | Rarely accessed, needs to load fast when it does | Instant, higher retrieval fee than IA |
| Glacier Flexible Retrieval | True archives, occasional access | Minutes to hours |
| Glacier Deep Archive | Compliance retention, essentially never read | Up to 12 hours |
Standard-IA and above are meaningfully cheaper per GB than Standard, but they charge a per-GB retrieval fee on top. That tradeoff only pays off if the object is genuinely accessed rarely — moving frequently-read data to IA to save on storage can cost you more overall once retrieval fees are counted.
Setting up the rule
Console: bucket → Management → Lifecycle rules → Create. Or via CLI with a JSON policy:
{
"Rules": [
{
"ID": "age-out-uploads",
"Filter": { "Prefix": "uploads/" },
"Status": "Enabled",
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER" }
],
"Expiration": { "Days": 730 }
}
]
}
aws s3api put-bucket-lifecycle-configuration \
--bucket my-bucket \
--lifecycle-configuration file://lifecycle.json
Scope rules with a Prefix filter. Applying one rule to an entire bucket when only one folder’s contents actually age out will misclassify data you still access regularly.
The minimum object size trap
S3 charges a minimum storage duration on IA and Glacier tiers — 30 days for Standard-IA, 90 for Glacier, 180 for Deep Archive. Delete or transition an object before that minimum and you’re still billed as if it sat there the full duration. Don’t set a lifecycle rule that transitions to IA at day 20 and expires at day 25 — you’ll pay the 30-day minimum for nothing.
There’s also a small per-object overhead charge for IA and Glacier transitions, which makes transitioning huge numbers of tiny objects (a few KB each) not worth it — the overhead can exceed the storage savings. Lifecycle rules pay off on larger objects (logs, backups, media files), not a bucket full of small JSON blobs.
Incomplete multipart uploads are a silent cost
Failed or abandoned multipart uploads leave orphaned parts in your bucket that don’t show up in a normal object listing but still bill you. Add this to every bucket, it costs nothing to have and nothing but money to skip:
{
"Rules": [
{
"ID": "clean-incomplete-uploads",
"Status": "Enabled",
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
}
]
}
Check before you commit
Before setting up transitions blind, look at S3 Storage Lens (free tier available in every account) or Storage Class Analysis on the prefix you’re targeting. It tells you actual access patterns over the last 30-90 days, so your transition timing is based on real data instead of a guess about when things go cold.
The actual habit
Set expiration on anything genuinely temporary — build artifacts, upload staging areas, logs past your retention requirement — at creation time, not as cleanup later. A 730-day Expiration rule on a prefix you know is disposable is the single highest-leverage line in this whole setup, because storage that gets deleted costs nothing forever, and storage that just gets cheaper still costs something forever.