Object Storage Lifecycle Policies: Cut Cost, Keep Data
Audit an S3-compatible bucket, then write lifecycle rules that avoid transition fees, minimum-duration charges, and versioning traps that raise bills.
An object storage bill has four line items that behave nothing alike: storage per GB-month, request counts, retrieval fees, and data transfer out. Lifecycle policies move the first one. They can make the other three worse if you write them from intuition rather than from your actual object inventory.
The failure mode we see most often is a team that reads about Glacier Deep Archive at roughly a fortieth the price of S3 Standard, writes a blanket “transition everything after 30 days” rule, and watches the next invoice go up — because the bucket holds forty million thumbnails averaging 40 KB, and per-object transition requests cost more than the storage those objects were consuming.
Below is the order of operations that avoids that: measure, then compute break-even, then handle egress separately, then roll out with an undo path.
Read the bill before you write a rule
Total bucket size is the least useful number you have. Three others decide whether lifecycle rules will help:
- Object count and size distribution. Savings scale with bytes; transition costs scale with object count. A bucket of 500,000 objects averaging 20 MB behaves completely differently from 200 million objects averaging 50 KB, even at the same 10 TB.
- The age curve of reads, not writes. Objects that are never read after 30 days are archive candidates. Objects read once a quarter are usually cheaper left in a hot class, because retrieval fees dwarf the storage delta.
- What is in the bucket that you cannot see.
ListObjectsV2does not show incomplete multipart uploads or, by default, noncurrent versions. Both are billed.
On AWS, S3 Storage Lens gives you object-count and size-distribution metrics per prefix, and an S3 Inventory report (Parquet, delivered daily) gives you the raw manifest you can query in Athena or DuckDB. GCS has Storage Insights inventory reports. On Cloudflare R2 and Backblaze B2 you generally list into your own manifest and analyze it yourself.
Compute the break-even before you transition anything
Here are the S3 list prices that drive the math (us-east-1, at the time of writing — verify against the current pricing page, these change):
| Storage class | Per GB-month | Minimum duration | Retrieval fee |
|---|---|---|---|
| S3 Standard | $0.023 | none | none |
| S3 Standard-IA | $0.0125 | 30 days | $0.01/GB |
| S3 Glacier Instant Retrieval | $0.004 | 90 days | $0.03/GB |
| S3 Glacier Deep Archive | $0.00099 | 180 days | $0.0025–$0.02/GB |
Three charges sit outside that table and decide most of the outcome:
- Transition requests. Moving an object to Standard-IA or Glacier Instant Retrieval costs about $0.01 per 1,000 objects. Moving to Glacier Flexible Retrieval or Deep Archive costs about $0.05 per 1,000 — five times more, per object, regardless of size.
- Minimum billable object size. Standard-IA and Glacier Instant Retrieval bill every object as at least 128 KB. A 10 KB object in Standard-IA is billed as 128 KB, which makes it more expensive than it was in Standard.
- Per-object metadata overhead in the Glacier classes. Glacier Flexible Retrieval and Deep Archive add roughly 40 KB of billed overhead per object — about 32 KB at the archive rate plus 8 KB at the Standard rate for the name index.
Run the arithmetic on one object before you run it on a hundred million. Take a 128 KB object moving from Standard to Deep Archive. The transition costs $0.00005. The raw storage saving is about $0.022 per GB-month, so on 0.000125 GB that is roughly $0.0000027 per month — before you add the 40 KB overhead, which eats most of what is left. Payback lands somewhere past the decade mark. The same transition on a 500 MB object pays for itself in under a day.
The practical rule: put a size floor on every transition. S3 lifecycle filters support ObjectSizeGreaterThan, so gate archive transitions at 128 KB minimum and, for the Glacier classes, more comfortably at 1 MB. Small objects should be consolidated at write time — packed into archives, or moved into a database — not shuffled between storage classes.
Minimum duration is the second trap. An object transitioned to Standard-IA on day 30 and deleted on day 45 is still billed for 30 days in Standard-IA. If your data has a 60-day total lifespan, a transition at day 30 buys you almost nothing and adds a request charge. Expire it directly instead.
OpenCode
An open-source terminal coding agent. Useful for this kind of work because the audit is scripting, not clicking: point it at an inventory manifest, have it write the DuckDB queries for size and age histograms, then generate and diff the lifecycle JSON against what the bucket currently has applied.
Open source; bring your own model API key
Affiliate link · We earn a commission at no cost to you.
Egress is a separate bill, and lifecycle will not touch it
No storage class changes what you pay to move bytes to the internet. AWS internet egress starts around $0.09/GB in the first tier — an order of magnitude above the monthly cost of storing that same gigabyte in Standard. If transfer out dominates your bill, lifecycle rules are the wrong lever entirely. The levers that work:
- Cache hit ratio. Serving through CloudFront, Cloudflare, or Fastly turns repeated origin reads into cache hits. Measure hit ratio before optimizing anything else.
- Keep reads in-region and off the NAT gateway. Traffic from a private subnet to S3 through a NAT gateway pays roughly $0.045/GB in processing charges on top of everything else. An S3 gateway VPC endpoint removes that and costs nothing.
- Providers that price egress at zero. Cloudflare R2 charges about $0.015/GB-month with no egress fee. Backblaze B2 is about $0.006/GB-month with free egress up to three times your average monthly stored data. For a public download bucket, that difference can be larger than every lifecycle optimization combined.
Retrieval fees deserve the same scrutiny. Glacier Instant Retrieval storage is $0.004/GB-month, but reading that gigabyte back costs $0.03 — more than seven months of storage. Divide monthly bytes read by bytes stored for the prefix. If that ratio is above a few percent, archiving that prefix loses money.
Roll it out with an undo path
Lifecycle has no dry-run mode, so build one: query your inventory manifest for the exact object count and byte total each rule would match, and check the number against what you expect before applying anything.
Then keep expiration rules in separate lifecycle rules from transition rules. They fail differently, and you want to be able to disable deletion without unwinding your archiving. Ship expiration scoped to one prefix first, watch it for a full billing cycle, then widen the filter.
Two operational notes for the rollout: lifecycle evaluation is asynchronous and runs roughly once a day, so nothing happens the moment you apply a policy — do not assume the rule is broken at hour two. And expirations are silent. Wire a bucket notification on delete events, or track object count in Storage Lens, so a mis-scoped filter surfaces in a dashboard rather than in a restore request six weeks later.
For data with a legal or compliance retention requirement, lifecycle rules are the wrong protection layer — a policy edit removes them. Use Object Lock in compliance mode, which no credential in the account can override for the retention period.
FAQ
Does S3 Intelligent-Tiering make lifecycle rules unnecessary?
Do lifecycle policies still matter on R2 or B2 where egress is free?
How long before savings appear on the invoice?
Tools used in this review
Some links above are affiliate links. We may earn a commission if you sign up. See our disclosure for details.
Related reading
2026-09-02
OpenAI Deployment Layer: The Assistants API Precedent
OpenAI shipped the Assistants API in November 2023 and marked it for sunset 16 months later. That precedent is how to price the new deployment stack.
2026-08-21
Bing Webmaster API's 100-URL Cap: 289 URLs, Three Days
Quota is 100 a day against 1300 a month, GetQueryStats was still empty at the end, and two error shapes will kill a cron job.
2026-08-19
Astro middleware can't serve 410 under output: 'static'
We deleted 434 articles. Middleware runs at build time and 404s in production; two prerender:false routes fix it on Cloudflare Workers.
2026-08-17
Workers KV's 60-Second Consistency Window
It's a cache TTL you cannot lower, not a replication delay. It bites cached nulls, uniqueness checks, and multi-key updates. When to swap in a Durable Object.
2026-08-13
Cache Races Make IndexNow Miss Pages You Just Shipped
A deploy API returning 200 does not mean the new URLs are reachable at the edge. Here is the verification sequence that stops wasted submissions.
Get the best tools, weekly
One email every Friday. No spam, unsubscribe anytime.