S3 Storage Classes and Lifecycle Rules: Real Costs
S3 storage classes and lifecycle rules explained with us-east-1 prices: minimum durations, the small-object trap, noncurrent versions and a worked example.
S3 is usually the first bill people try to cut and the one they most often make worse. The storage classes look like a simple price ladder: move data down, pay less. Then the transition requests arrive, or a minimum-duration charge, or an expedited Glacier restore that nobody priced first, and the “saving” turns out to be a cost.
The ladder is real, though. The trick is knowing what each rung charges besides storage, and which of your objects are the wrong shape to move. That’s what this guide covers, with a worked example at the end.
All prices are on-demand list prices for US East (N. Virginia), us-east-1, at the time of writing (October 2026), using the first 50 TB tier where tiers apply. Check the S3 pricing page for your region.
The storage classes, side by side
| Class | Storage per GB-month | Minimum duration | Minimum billable size | Retrieval fee |
|---|---|---|---|---|
| S3 Standard | $0.023 | none | none | none |
| S3 Intelligent-Tiering | $0.023 down to $0.00099, by tier | none | none (under 128 KB isn’t tiered) | none for automatic tiers |
| S3 Standard-IA | $0.0125 | 30 days | 128 KB | $0.01 per GB |
| S3 One Zone-IA | $0.01 | 30 days | 128 KB | $0.01 per GB |
| S3 Glacier Instant Retrieval | $0.004 | 90 days | 128 KB | $0.03 per GB |
| S3 Glacier Flexible Retrieval | $0.0036 | 90 days | 40 KB overhead per object | free (bulk) to $0.03 per GB (expedited) |
| S3 Glacier Deep Archive | $0.00099 | 180 days | 40 KB overhead per object | $0.0025 (bulk) or $0.02 (standard) per GB |
| S3 Express One Zone | $0.11 | none | none | $0.0006 per GB |
A few things the table hides.
Request prices climb as storage prices fall. A PUT costs $0.005 per 1,000 in Standard, $0.01 in Standard-IA, $0.02 in Glacier Instant Retrieval and $0.03 in Glacier Flexible Retrieval. GETs follow the same pattern: $0.0004 per 1,000 in Standard against $0.01 in Glacier Instant Retrieval. For data that’s read a lot, the cheap classes aren’t cheap.
Minimum duration means you pay for it anyway. Delete or move an object out of Standard-IA after ten days and you’re billed for the other twenty. Glacier Instant and Flexible bill 90 days, Deep Archive 180.
The archive classes add metadata. Every object in Glacier Flexible Retrieval or Deep Archive carries 40 KB of overhead: 32 KB billed at the archive rate and 8 KB at Standard rates. That’s nothing on a 2 GB backup and a disaster on a 4 KB JSON file.
One Zone means one zone. One Zone-IA and Express One Zone keep data in a single Availability Zone. Fine for data you can regenerate. Not fine for the only copy of anything.
Intelligent-Tiering, briefly
Intelligent-Tiering moves each object between tiers based on when it was last accessed: Frequent Access ($0.023), Infrequent Access after 30 days without access ($0.0125), and Archive Instant Access after 90 days ($0.004). You can also opt in to Archive Access ($0.0036) and Deep Archive Access ($0.00099) after a configurable 90 to 730 and 180 to 730 days, but objects in those two tiers need a restore before you can read them. Most teams shouldn’t turn them on unless every reader of the bucket can cope with that.
The cost is a monitoring fee of $0.0025 per 1,000 objects per month, charged only on objects of 128 KB or more. Smaller objects stay in Frequent Access and skip the fee. There are no retrieval fees for the automatic tiers and no charge when objects move between them.
Glacier retrieval speeds
Flexible Retrieval restores take 1 to 5 minutes (expedited, $0.03 per GB plus $0.01 per request), 3 to 5 hours (standard, $0.01 per GB) or 5 to 12 hours (bulk, free). Deep Archive has no expedited option: standard is within 12 hours and bulk within 48. If someone might need the data during an incident, Deep Archive isn’t the place for it.
Express One Zone is for speed
Express One Zone lives in directory buckets in a single AZ and gives single-digit millisecond access. Storage is nearly five times Standard’s price, though requests are much cheaper ($0.00113 per 1,000 PUTs and $0.00003 per 1,000 GETs, plus $0.0032 per GB uploaded and $0.0006 per GB retrieved). It makes sense for hot, request-heavy scratch data like ML training shards or job intermediates, paired with an expiration rule. As a place to keep things, it’s the wrong end of the ladder.
The small-object trap
Lifecycle transitions are billed per object: $0.01 per 1,000 into Standard-IA, One Zone-IA or Intelligent-Tiering, $0.02 into Glacier Instant Retrieval, $0.03 into Flexible Retrieval and $0.05 into Deep Archive. Per-object fees and per-GB savings don’t scale the same way, and that’s the whole trap.
Take ten million 20 KB objects, about 200 GB. In Standard they cost $4.60 a month. Push them to Deep Archive and:
- The transition requests cost $500, once.
- Each object is now billed as 52 KB at Deep Archive rates plus 8 KB at Standard rates, about $2.35 a month.
- You save about $2.25 a month, so the transition pays for itself in roughly eighteen years.
Standard-IA is worse in a different way. Each 20 KB object is billed as 128 KB, so the same 200 GB costs about $16 a month there, more than three times what it cost in Standard.
AWS now guards against this by default. Since September 2024, lifecycle rules skip objects smaller than 128 KB unless you add an object size filter that says otherwise. Rules written before then keep the old behaviour until you edit them. If you have lots of tiny objects you really want archived, bundle them first (tar, Parquet, whatever fits) and archive the bundle.
Lifecycle rules that pay for themselves
A lifecycle configuration is a list of rules, each with a filter (prefix, tags, object size) and actions. The ones worth having:
- Transitions for data whose access really does drop off with age.
- Expiration for data with a known lifetime. Deleting is the cheapest storage class.
- Noncurrent version expiration on every versioned bucket. Versioning without it means every overwrite keeps the old copy forever, at full price.
- Abort incomplete multipart uploads. Failed or abandoned multipart uploads leave parts that are billed as storage but never appear in a normal object listing. Seven days is a sensible default.
- Expired delete marker cleanup, so versioned buckets don’t fill with markers pointing at nothing.
One change worth knowing: since July 2026, S3 lets lifecycle rules move objects to Standard-IA or One Zone-IA from day 0 instead of waiting 30 days in Standard. The 30-day minimum storage charge in those classes still applies, so it only helps for data you know will sit there at least a month.
Here’s a rule set we’d start most log or export buckets with:
{
"Rules": [
{
"ID": "tier-and-expire",
"Status": "Enabled",
"Filter": { "And": { "Prefix": "exports/", "ObjectSizeGreaterThan": 131072 } },
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER_IR" }
],
"Expiration": { "Days": 400 }
},
{
"ID": "versions-and-mpu",
"Status": "Enabled",
"Filter": {},
"NoncurrentVersionExpiration": { "NoncurrentDays": 30, "NewerNoncurrentVersions": 3 },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
}
]
}
Check what’s already there before you touch anything, because the write call replaces the whole configuration rather than adding to it:
# Read-only: current rules, versioning state and stray multipart uploads
aws s3api get-bucket-lifecycle-configuration --bucket my-bucket
aws s3api get-bucket-versioning --bucket my-bucket
aws s3api list-multipart-uploads --bucket my-bucket --max-items 20
# CHANGES THE BUCKET: replaces every existing lifecycle rule with the file's contents
aws s3api put-bucket-lifecycle-configuration --bucket my-bucket \
--lifecycle-configuration file://lifecycle.json
Expiration rules delete data. Point them at a test prefix first, and read them twice.
Finding where the money is: Storage Lens and Storage Class Analysis
Don’t write rules from memory of what a bucket holds. Two built-in tools help.
S3 Storage Lens gives an organisation-wide dashboard. The free metrics cover storage by class, object counts, and cost-related signals like incomplete multipart uploads, with 14 days of history. Advanced metrics keep 15 months and add activity data, at $0.20 per million objects monitored per month. For most teams, the free dashboard sorted by bucket size is enough to find the three buckets that matter.
Storage Class Analysis watches access patterns on a bucket, prefix or tag filter, usually for 30 days or more, and tells you when objects stop being read. It costs $0.10 per million objects monitored per month. It only recommends Standard to Standard-IA, so treat its age curve as evidence and make the deeper archive call yourself.
To see what’s actually in a bucket by class:
# Read-only: bucket size by storage class from CloudWatch (a daily metric, so look back a few days)
# GNU date first, then the macOS form
START=$(date -u -d '3 days ago' +%Y-%m-%dT00:00:00Z 2>/dev/null || date -u -v-3d +%Y-%m-%dT00:00:00Z)
END=$(date -u +%Y-%m-%dT00:00:00Z)
aws cloudwatch get-metric-statistics --namespace AWS/S3 --metric-name BucketSizeBytes \
--dimensions Name=BucketName,Value=my-bucket Name=StorageType,Value=StandardStorage \
--start-time "$START" --end-time "$END" \
--period 86400 --statistics Average
Swap StandardStorage for StandardIAStorage, GlacierInstantRetrievalStorage and so on. Also check NumberOfObjects with StorageType=AllStorageTypes, because the average object size decides whether any of this is worth doing.
Worked example: a 40 TB export bucket
A bucket holds 40 TB (40,960 GB) of nightly exports averaging 4 MB, about 10.5 million objects. Analysts read the last month’s data often, the previous two months now and then, and anything older almost never. Today it’s all in Standard.
| Age | Size | Class after lifecycle | Monthly storage |
|---|---|---|---|
| 0 to 30 days | 3,072 GB | Standard | $70.66 |
| 30 to 90 days | 7,168 GB | Standard-IA | $89.60 |
| Over 90 days | 30,720 GB | Glacier Instant Retrieval | $122.88 |
| Total | 40,960 GB | $283.14 |
All in Standard, the same data costs $942.08 a month, so the rules save about $659 a month on storage.
What it costs to get there:
- One-off backlog transitions: about 1.8 million objects into Standard-IA ($18.35) and 7.9 million into Glacier Instant Retrieval ($157.29). Call it $176.
- Ongoing transitions: about 786,000 new objects a month, each moved twice, comes to roughly $24 a month.
- Retrievals: reading 1 TB a month from Standard-IA is about $10, and 500 GB from Glacier Instant Retrieval is $15.
Net, that’s still around $600 a month saved, and the backlog cost is paid back inside two weeks. At 4 MB per object, the archive overhead and per-object fees barely register. Run the same numbers with 40 KB objects and the answer flips.
Two alternatives worth pricing. If nobody reads the old data, Deep Archive drops the over-90-day line from $122.88 to about $32 including the metadata overhead, at the cost of 12-hour restores. If access patterns are unpredictable, Intelligent-Tiering gets you a similar storage bill for about $26 a month in monitoring fees and no retrieval charges, which is cheap insurance against a team that suddenly starts reprocessing last year’s data.
FAQ
What is the cheapest S3 storage class?
Glacier Deep Archive, at $0.00099 per GB-month in us-east-1. It has a 180-day minimum, 40 KB of overhead per object, and restores take up to 12 hours (48 for bulk), so it suits data you hope never to read.
Should I use S3 Intelligent-Tiering or Standard-IA?
Use Standard-IA when you know the access pattern and objects are well over 128 KB. Use Intelligent-Tiering when you don’t know, or when it changes. Its monitoring fee is usually smaller than one bad month of Standard-IA retrieval fees.
Why did my S3 bill go up after adding a lifecycle rule?
Usually transition request fees on lots of small objects, minimum-duration charges from moving or deleting data too early, or retrieval fees on data that was still being read. Check object counts and access before transitioning.
How do I delete incomplete multipart uploads in S3?
Add a lifecycle rule with AbortIncompleteMultipartUpload and a DaysAfterInitiation value, such as 7. It applies to existing uploads too. list-multipart-uploads shows what’s currently pending.
Do S3 lifecycle rules apply to old versions?
Only if you write them to. Use NoncurrentVersionExpiration or NoncurrentVersionTransitions; plain Expiration acts on current versions only.
Three buckets first
Open Storage Lens, sort by bucket size, and for the top three write down average object size, versioning state and whether an abort-multipart rule exists. Object size tells you which transitions are safe. The other two are usually fixed by the second rule in the JSON above, though its noncurrent-version expiry deletes old versions for good, so agree the numbers with whoever owns the bucket. If CloudWatch Logs is part of your storage bill as well, moving logs to S3 saves money in fewer places than you’d think.
