Object Storage Lifecycle Policies: Cutting Storage and Egress Cost Without Losing Data You Need
How to audit an S3-compatible bucket, write lifecycle rules that actually pay for themselves, and avoid the transition fees, minimum-duration charges, and versioning traps that make bills go up instead of down.
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-08-13
Cache Races in a Publish Pipeline: Why Your IndexNow Ping Misses the Pages You Just Shipped
A deploy API returning 200 does not mean your new URLs are reachable at the edge. Here is how the deploy-then-ping race silently burns IndexNow submissions, and the verification sequence that stops it.
2026-08-13
When There Is No API: Driving Chrome With the DevTools Protocol, and When Not To
CDP gives a scheduled agent a real browser when a site ships no API. Here is what it costs in memory and wall clock, the four failure modes that only surface on a cron, and the cases where you should not do it at all.
2026-08-12
Running a Small Kubernetes Cluster in 2026: k3s vs Talos vs a Managed Control Plane
k3s, Talos Linux, and a managed control plane solve different halves of the same problem. What each one actually costs you in money, upgrade work, and 2am debugging on a three-node cluster.
2026-08-12
Postgres Connection Pooling in 2026: PgBouncer vs Supavisor vs Driver-Side Pools
Postgres forks a process per connection, so pooling is not optional past a few dozen clients. How PgBouncer, Supavisor, and driver-side pools differ, and what transaction mode breaks.
2026-07-20
Blue-Green Deployments for Teams Without a Platform Engineer
Blue-green deployments do not require Kubernetes, a service mesh, or a dedicated platform team. Here is a working setup using nothing more than a reverse proxy, two ports, and a shell script.
Get the best tools, weekly
One email every Friday. No spam, unsubscribe anytime.