TL;DR: Most S3 data is read constantly for a few weeks, then almost never again — while you keep paying the premium S3 Standard rate for it forever. A lifecycle policy moves objects down the storage-class ladder automatically as they age. It's a one-time setup, it runs daily with no code, and on log/backup/archive buckets it routinely cuts the storage line by 50–70%.
The numbers
| Storage class | $/GB-month (us-east-1) | vs Standard |
|---|---|---|
| S3 Standard | $0.023 | — |
| S3 Standard-IA | $0.0125 | −46% |
| S3 One Zone-IA | $0.01 | −57% |
| Glacier Flexible Retrieval | $0.0036 | −84% |
| Glacier Deep Archive | $0.00099 | −96% |
Standard-IA and the Glacier tiers charge per-GB retrieval fees and have minimum storage durations (30/90/180 days) — that's the trade you're making for the lower rate.
Do this
-
Find your biggest buckets. S3 console → Storage Lens (free tier), or CloudWatch
BucketSizeBytes. The top three buckets are usually 80% of the bill. -
Add the free win first — expire incomplete multipart uploads. Failed uploads leave invisible fragments that bill forever on every bucket:
aws s3api put-bucket-lifecycle-configuration --bucket YOUR-BUCKET \ --lifecycle-configuration '{"Rules":[{"ID":"kill-incomplete-mpu", "Status":"Enabled","Filter":{}, "AbortIncompleteMultipartUpload":{"DaysAfterInitiation":7}}]}' -
Add age-based transitions on buckets with predictable aging (logs, backups, exports):
aws s3api put-bucket-lifecycle-configuration --bucket YOUR-BUCKET \ --lifecycle-configuration '{"Rules":[{"ID":"age-out","Status":"Enabled", "Filter":{"Prefix":"logs/"}, "Transitions":[{"Days":30,"StorageClass":"STANDARD_IA"}, {"Days":90,"StorageClass":"GLACIER"}], "Expiration":{"Days":365}}]}' -
Not sure how your data is accessed? Turn on S3 Storage Class Analysis or Storage Lens for 30 days and design rules from real access data instead of guesses.
-
Versioned buckets: add a separate rule for noncurrent versions — old versions are the classic hidden hoard. Archiving noncurrent versions immediately is usually safe.
Gotchas
- Transitions are one-way. There's no automatic promotion back to Standard; retrieving from Glacier is a manual, per-object restore. Plan the ladder before you build it.
- Minimum storage durations bite. Glacier Flexible has a 90-day minimum: transition an object and delete it on day 60, and you're billed for 90 days anyway. Don't send short-lived data to Glacier.
- Small objects can cost more after transition. Each transitioned object carries a per-object request fee plus ~40 KB of metadata overhead in Glacier classes. Under ~128 KB average object size, do the math first.
- Transition requests aren't free (about $0.01 per 1,000 to IA, more to Glacier). A one-time migration of 100M objects is a real number — spread it or filter by prefix.
Skip this if
- Your bucket's objects are all short-lived (< 30 days) — expiration alone is fine, transitions won't pay.
- Access is genuinely unpredictable across all ages — use S3 Intelligent-Tiering instead; it moves objects both directions with no retrieval fees.
- The bucket is tiny (a few GB) — the savings won't cover the attention.