An S3 lifecycle policy moves objects between storage classes on a schedule, or deletes them. The '40%' people quote is real, but only for one specific bucket shape, and only on the storage line. On AWS us-east-1 list prices, pushing 30% of a 100 TB bucket to Standard-IA and 40% to Glacier Instant Retrieval drops the storage line from $2,304.00 to $1,254.40 — a 45.6% cut. The arithmetic is the boring part. The saving leaks back out in the quarter after you ship the rule, in the line items nobody re-opens.
Every number here is pulled from the AWS Price List Bulk API (us-east-1 offer file, publicationDate 2026-08-07) or worked out from those rates. If a rate isn't in the offer file, I say so. I'm not inventing a number to fill the gap.
Key Stats
| Metric | Figure | Source |
|---|---|---|
| S3 Standard, first 50 TB/mo | $0.023 / GB-mo | AWS Price List API, us-east-1 |
| S3 Standard-IA | $0.0125 / GB-mo + $0.01/GB retrieval | AWS Price List API |
| S3 Glacier Instant Retrieval | $0.004 / GB-mo + $0.03/GB retrieval | AWS Price List API |
| S3 Glacier Flexible Retrieval | $0.0036 / GB-mo | AWS Price List API |
| Minimum object size that transitions by default | 128 KB (since Sept 2024) | AWS S3 User Guide |
| Minimum storage duration | 30 d (IA) / 90 d (Glacier IR, Flexible) / 180 d (Deep Archive) | AWS S3 User Guide |
| Lifecycle transition request, into Glacier Flexible | $0.03 / 1,000 requests | AWS Price List API |
What is an S3 lifecycle policy?
An S3 lifecycle policy is a bucket-level ruleset: it transitions objects to a cheaper class, or expires them, once they hit an age you set. You attach JSON or XML; S3 runs it asynchronously, so there's no cron job to babysit.
The User Guide lists five actions: Transition, Expiration, NoncurrentVersionTransition, NoncurrentVersionExpiration, AbortIncompleteMultipartUpload, and a Filter that takes Prefix, Tag, ObjectSizeGreaterThan, ObjectSizeLessThan, or And to combine them.
Two things actually cost you money. One: transitions only go down the ladder. The User Guide is blunt that the Deep Archive transition 'can go only one way.' Two: billing starts the instant the rule is satisfied, not when the data finishes moving. You're paying the destination rate from day one, even if AWS is still copying in the background.
Where does the 40% actually come from?
It comes down to the price gap between classes, times how much of your data is cold. That's the whole formula. For a 100 TB bucket in us-east-1 (AWS bills in 1 TB = 1,024 GB, so that's 102,400 GB), baseline, everything in S3 Standard:
51,200 GB × $0.023 = $1,177.60 (first 50 TB band)
51,200 GB × $0.022 = $1,126.40 (next 450 TB band)
------------------------------------------------
TOTAL $2,304.00 / month
Split that 100 TB three ways: hot in Standard, warm in Standard-IA, cold in Glacier IR:
| Scenario | Hot / Warm / Cold | Monthly storage | Saving |
|---|---|---|---|
| Conservative | 50% / 30% / 20% | $1,643.52 | −28.7% |
| Typical | 30% / 30% / 40% | $1,254.40 | −45.6% |
| Aggressive | 20% / 20% / 60% | $972.80 | −57.8% |
That 45.6% assumes roughly two-thirds of your bytes are over 30 days old, which is the usual shape for logs, backups, and ML snapshots. But it's the storage line only. Requests and retrievals bill on top, and they're exactly where the saving disappears.
Which storage class should each tier of data land in?
Pick the class by how often you actually read the data, then check the minimum duration. The two numbers that drive cost are per-GB storage and per-GB retrieval. Here's the full ladder from the same offer file:
| Storage class | $/GB-mo | Retrieval | Min duration | Min billable size | Availability SLA |
|---|---|---|---|---|---|
| S3 Standard | $0.023 | — | none | none | 99.99% |
| S3 Standard-IA | $0.0125 | $0.01/GB | 30 days | 128 KB | 99.9% |
| S3 One Zone-IA | $0.010 | $0.01/GB | 30 days | 128 KB | 99.5% |
| S3 Glacier Instant Retrieval | $0.004 | $0.03/GB | 90 days | 128 KB | 99.9% |
| S3 Glacier Flexible Retrieval | $0.0036 | $0.01/GB standard, $0.00/GB bulk | 90 days | n/a* | 99.99% after restore |
| S3 Glacier Deep Archive | see note | $0.02/GB standard, $0.0025/GB bulk | 180 days | n/a* | 99.99% after restore |
* Glacier Flexible and Deep Archive add 40 KB of overhead per object, of which 8 KB is billed at S3 Standard rates and 32 KB at the destination Glacier rate.
Note on Deep Archive: the us-east-1 S3 offer file exposes Deep Archive's transition, retrieval, and checksum SKUs but no per-GB timed-storage SKU. I won't quote a storage rate I can't pull from a first-party file, so get it from the pricing console before you budget on it. The closest verifiable neighbour in the same file is Intelligent-Tiering's Deep Archive Access tier at $0.00099/GB-mo.
One Zone-IA is 20% cheaper and lives in a single AZ at 99.5% availability. Fine for regenerable stuff: thumbnails, transcodes, a second backup copy. Never the only copy you've got.
How do you write and apply the rule?
With aws s3api put-bucket-lifecycle-configuration. This is the official AWS CLI example, copied verbatim from the command reference:
aws s3api put-bucket-lifecycle-configuration --bucket amzn-s3-demo-bucket --lifecycle-configuration file://lifecycle.json
{
"Rules": [
{
"ID": "Move rotated logs to Glacier",
"Prefix": "rotated/",
"Status": "Enabled",
"Transitions": [
{
"Date": "2015-11-10T00:00:00.000Z",
"StorageClass": "GLACIER"
}
]
},
{
"Status": "Enabled",
"Prefix": "",
"NoncurrentVersionTransitions": [
{
"NoncurrentDays": 2,
"StorageClass": "GLACIER"
}
],
"ID": "Move old versions to Glacier"
}
]
}
[Both blocks sourced verbatim from docs.aws.amazon.com/cli/latest/reference/s3api/put-bucket-lifecycle-configuration.html — NOT EXECUTED IN CI.]
A workable rule, using only documented LifecycleRule fields: it tiers logs down, skips objects too small to move, expires old versions, and reclaims abandoned multipart uploads:
{
"Rules": [
{
"ID": "logs-tier-down",
"Status": "Enabled",
"Filter": {
"And": {
"Prefix": "logs/",
"ObjectSizeGreaterThan": 131072
}
},
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 120, "StorageClass": "GLACIER_IR" }
],
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
}
]
}
ObjectSizeGreaterThan: 131072 is just 128 KB written out, and the next section explains why that number matters. The Glacier IR hop sits at day 120, not 90, because Standard-IA has a 30-day minimum and the User Guide says the second transition 'must occur after at least' the first one's minimum has passed. Tighten that gap and AWS rejects the rule outright.
Why do small objects break lifecycle economics?
You pay per transition request, not per byte. The first lifecycle bill that ever surprised me was a 200 KB-heavy bucket where transition cost ate the whole storage saving, which is the trap most people hit before they check object size. A 200 KB object costs the same request as a 200 MB one. AWS made that the default in September 2024: 'the default behavior prevents objects smaller than 128 KB from being transitioned to any storage class.'
Payback (transition fee divided by the monthly saving) at list rates:
| Object size | → Standard-IA | → Glacier IR | → Glacier Flexible |
|---|---|---|---|
| 128 KB | 7.8 months | 8.6 months | 12.7 months |
| 256 KB | 3.9 months | 4.3 months | 6.3 months |
| 1 MB | 1.0 month | 1.1 months | 1.6 months |
| 10 MB | ~0.1 month | ~0.1 month | ~0.2 months |
Scale that to a whole bucket. Same 100 TB, one transition each, at $0.01/1,000 into Standard-IA and $0.03/1,000 into Glacier Flexible:
| Average object size | Object count | One-time bill → Standard-IA | → Glacier Flexible |
|---|---|---|---|
| 64 MB | 1,638,400 | $16.38 | $49.15 |
| 2 MB | 52,428,800 | $524.29 | $1,572.86 |
| 200 KB | 536,870,912 | $5,368.71 | $16,106.13 |
Same 100 TB, but a 328x swing in transition cost driven purely by object size. AWS says it straight: 'for smaller objects, the transition costs can outweigh the storage savings.' Check your average object size in S3 Storage Lens first, and put ObjectSizeGreaterThan on every transition.
What does the policy cost when the data comes back?
Retrieval fees are the second place the win leaks out. Standard-IA saves $0.0105/GB-mo versus Standard but charges $0.01/GB to read. Those two numbers give you a hard break-even:
| Class | Monthly saving vs Standard | Retrieval | Break-even |
|---|---|---|---|
| Standard-IA | $0.0105/GB | $0.01/GB | 1.05 full reads/month |
| One Zone-IA | $0.0130/GB | $0.01/GB | 1.30 full reads/month |
| Glacier Instant Retrieval | $0.0190/GB | $0.03/GB | 0.63 full reads/month |
Read a Glacier IR object more than about twice a quarter and you'd have been cheaper in Standard. The '$0.004/GB' headline hides how tight that budget really is.
Early deletion is the third trap. Delete or overwrite before the minimum and AWS bills the rest pro-rated: $0.0125/GB-mo for Standard-IA, $0.004 for Glacier IR, $0.0036 for Glacier. Point a 30-day retention rule at a 90-day-minimum class and you pay for 90 days of Glacier every month, indefinitely.
Intelligent-Tiering or hand-written rules?
Intelligent-Tiering adds a monitoring fee of $0.0025 per 1,000 objects per month and moves data between tiers for you, with no retrieval fee on the Frequent, Infrequent, or Archive Instant tiers. Hand-written rules are free to run but assume you already know your access pattern.
What decides it is object count, not total data:
| Object count | Intelligent-Tiering monitoring |
|---|---|
| 1,000,000 | $2.50/month |
| 50,000,000 | $125.00/month |
| 536,870,912 | $1,342.18/month |
I use Intelligent-Tiering for buckets with unpredictable reads and big objects, and explicit rules where the access pattern is already obvious. Logs and backups are the obvious case: nobody reads last quarter's access logs spontaneously, so a monitoring fee to find that out is pure waste. The other direction: half a billion thumbnails is $1,342/month in monitoring, and most of those objects sit under the 128 KB threshold anyway.
Does lifecycle work the same on self-hosted S3?
Not quite. On AWS a transition shuffles bytes between AWS-run classes. On self-hosted S3 there's no Glacier, so a 'lifecycle rule' means expire, clean up old versions, or move to a remote tier you set up yourself.
MinIO implements this through mc ilm. From the official command reference:
mc ilm rule add --expire-days 90 --noncurrent-expire-days 30 myaistor/mydata
mc ilm rule add --transition-days 30 --transition-tier "COLDTIER" myaistor/mydata
[Verbatim from min.io/docs — the myaistor alias is MinIO's own example alias. NOT EXECUTED IN CI.]
RustFS is straight about this one. Its README Feature & Status table shows Lifecycle Management as 🚧 Under Testing, next to Distributed Mode and RustFS KMS. Versioning, Bucket Replication, Event Notifications, Bitrot Protection, and Single Node Mode are ✅ available; there's no Object Lock row and no tiering row. If tiered lifecycle is a hard requirement today, RustFS isn't the answer yet.
What RustFS does give you is the structural argument: your own disks mean no per-GB retrieval fee and no per-request transition fee, so the break-even math above stops applying. The catch is you buy and run the hardware. If you're already running RustFS for other reasons, expire-and-cleanup lifecycle is free and worth turning on. If you'd stand it up purely to dodge egress, the hardware usually costs more than the bandwidth you save, so run the numbers first.
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:latest
[Verbatim from github.com/rustfs/rustfs README — NOT EXECUTED IN CI.]
S3 API on :9000, Console on :9001. The README lists default credentials rustfsadmin / rustfsadmin, and docs.rustfs.com explicitly tells you not to keep them: "Do not use the well-known rustfsadmin value for either credential."
The five mistakes that eat the savings
-
Transitioning objects under 128 KB. Payback is 8–13 months. Filter them out with
ObjectSizeGreaterThan. - Chaining transitions inside a minimum duration. Standard-IA → Glacier IR at day 60 is invalid; the IA minimum is 30 days and Glacier IR's own 90-day clock only starts on arrival.
-
Forgetting
NoncurrentVersionExpiration. On a versioned bucket, every overwrite leaves a billable version. Storage grows forever while your object count looks flat. -
Skipping
AbortIncompleteMultipartUpload. Failed uploads leave parts that are invisible inListObjectsbut fully billable. - Enabling Intelligent-Tiering on hundreds of millions of small objects. Objects under 128 KB are never monitored for tiering, but you can still be paying monitoring fees at scale for the rest.
FAQ
How much can S3 lifecycle policies actually save?
On AWS us-east-1 list prices, a 100 TB bucket costs $2,304.00/month entirely in S3 Standard. Moving 30% to Standard-IA and 40% to Glacier Instant Retrieval brings it to $1,254.40/month — a 45.6% cut. A conservative 50/30/20 split still saves 28.7%. Savings scale linearly with the fraction of cold data, and apply to the storage line only.
What is the minimum object size for an S3 lifecycle transition?
128 KB. Since September 2024, AWS's default behavior prevents objects smaller than 128 KB from transitioning to any storage class. Configurations created before September 2024 keep the old behavior until you edit them. You can override with the x-amz-transition-default-minimum-object-size header on PutBucketLifecycleConfiguration, but you usually should not: a 128 KB object takes 7.8 months to pay back a single Standard-IA transition.
Does S3 charge for lifecycle transitions?
Yes, per request. In us-east-1: $0.01 per 1,000 transitions into Standard-IA, One Zone-IA, or Intelligent-Tiering; $0.02 per 1,000 into Glacier Instant Retrieval; $0.03 per 1,000 into Glacier Flexible Retrieval; $0.05 per 1,000 into Glacier Deep Archive. For 536 million small objects, that single move to Glacier Flexible is $16,106.
When is Standard-IA more expensive than S3 Standard?
When you read the object more than 1.05 times per month. Standard-IA saves $0.0105/GB-month on storage but charges $0.01/GB on retrieval, so a little over one full read per month erases the discount. Glacier Instant Retrieval breaks even at 0.63 reads/month because its $0.03/GB retrieval fee is three times higher.
Do self-hosted S3 servers support lifecycle policies?
Partially, and support varies. MinIO implements lifecycle through mc ilm rule add with expiration and remote-tier transition. RustFS lists Lifecycle Management as 🚧 Under Testing in its README Feature & Status table, so it is not production-ready today. The upside of self-hosting is that per-GB retrieval fees and per-request transition fees do not exist, which removes the break-even math entirely.
Sources
All figures checked 2026-08-10.
| Source | Used for |
|---|---|
AWS Price List Bulk API — pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonS3/current/region_index.json, us-east-1 offer file, publicationDate 2026-08-07
|
All per-GB storage, retrieval, request, and transition rates |
| Transitioning objects — general considerations | 128 KB default, minimum durations, 40 KB Glacier overhead, one-way Deep Archive, early-billing rule |
| Understanding and managing Amazon S3 storage classes | Minimum billable size, availability SLAs, AZ counts |
| S3 Lifecycle configuration elements | Action and Filter element names |
| aws s3api put-bucket-lifecycle-configuration | Verbatim CLI command and JSON example |
| mc ilm rule add | Verbatim MinIO ILM commands |
| rustfs/rustfs README | Verbatim docker run, default credentials, Feature & Status table |
| docs.rustfs.com — Docker | API port 9000, Console port 9001, credential warning |
Before you trust any of this on your own bucket, pull object count and average size from S3 Storage Lens and drop them into the payback table to see whether a rule actually pays off. In my experience most don't, or only barely.
If retrieval and transition fees, not the storage rate, are what's blowing up your bill, self-hosted S3-compatible storage deletes both line items. RustFS is Apache 2.0, S3-compatible, and runs single-node today: github.com/rustfs/rustfs.
Top comments (0)