STORAGE COSTS

The storage lifecycle leak: how old versions become the bill (S3, B2, R2)

By Buy Me Compute · Published November 4, 2026 · Last updated November 4, 2026

Object storage rarely announces its waste. It accumulates one backup, one overwritten file, and one “temporary” upload at a time—until the account is paying every month to preserve versions nobody would choose to keep. The fix is often configuration, not migration: set a deliberate lifecycle before storage turns into a permanent subscription to your own history.

Why versioning inflates your storage bill

Teams compare object-storage providers by the headline price per gigabyte, then lose the plot in the defaults. A bucket can hold the current object, many prior versions, incomplete multipart uploads, delete markers, logs, backups of backups, and media that has not been opened in years—all in the same storage class, all retained forever because no rule says otherwise.

That is the lifecycle leak. It has three common forms:

  • Versioning without an expiry: every overwrite creates another billable copy, and “delete” may only hide the current version.
  • Hot storage for cold objects: archives, old photos, and historical backups stay in the fastest, most expensive tier long after access becomes rare.
  • No owner for retention: engineering keeps data “just in case,” legal requirements are guessed rather than documented, and finance sees only the growing total.

The public example: $1,200 to $26 a month (Pieter Levels, B2), labelled not ours

The numbers in this section come from Pieter Levels’ public cost-cutting account. We use them because they show the leak pattern clearly—not because Buy Me Compute produced them.

Public exampleBeforeFixAfter
Backblaze B2 server backups$1,200/moReplaced effectively infinite version retention with a 30-day lifecycle$26/mo
Cloudflare R2 photo storage$1,200/moMoved objects to Infrequent Access after 90 days$800/mo

The B2 result is dramatic because the retained history was enormous relative to the live backup set. The R2 result is smaller because the data still exists; it was moved to a cheaper access pattern rather than expired. Neither number predicts your bill. They do prove why the first audit question should not be “Which storage vendor is cheaper?” It should be “What are we retaining, in which class, and why?”

How to find the leak (S3 Storage Lens, B2 file versions, R2 metrics)

Start read-only, and price each category separately so the leak is visible before anything is deleted:

  • Amazon S3: use S3 Storage Lens and the bucket metrics to split current versus noncurrent bytes, object age, storage-class distribution, and incomplete multipart uploads — then check which buckets have no lifecycle rule at all.
  • Backblaze B2: list file versions per bucket and compare stored bytes against the live backup set; a large gap between the two is retained history you did not know you were keeping.
  • Cloudflare R2: check bucket metrics for object age and access patterns; media that has not been read in 90+ days is a transition candidate, not a deletion candidate.

Also inventory legal holds and documented retention requirements before proposing any expiry. Nothing should be expired just because it is old — the audit makes retention a decision with a price attached, instead of an accident with a monthly charge.

Amazon S3 lifecycle rule for noncurrent versions (JSON)

This rule expires noncurrent versions 30 days after they are superseded, keeps the 3 newest noncurrent versions, aborts incomplete multipart uploads after 7 days, and cleans up expired delete markers:

{"Rules":[{"ID":"expire-old-versions","Status":"Enabled","Filter":{"Prefix":""},
 "NoncurrentVersionExpiration":{"NoncurrentDays":30,"NewerNoncurrentVersions":3},
 "AbortIncompleteMultipartUpload":{"DaysAfterInitiation":7},
 "Expiration":{"ExpiredObjectDeleteMarker":true}}]}

AWS requires a Filter when NewerNoncurrentVersions is set; an empty prefix applies the rule to the whole bucket. Adjust the day count and the kept-version count to your documented recovery window — 30 days is the public example’s value, not a universal recommendation.

Backblaze B2 lifecycle rules (daysFromHidingToDeleting)

B2 lifecycle rules are set per bucket and are built around hiding: when a file is overwritten or deleted, the previous version becomes a hidden file, and daysFromHidingToDeleting controls how long hidden versions survive before they are actually deleted. A rule aligned with a 30-day recovery window looks like this:

[{"fileNamePrefix":"","daysFromHidingToDeleting":30,"daysFromUploadingToHiding":null}]

Align backup retention with the recovery window you actually need, and check whether multiple backup jobs are preserving the same history. A backup you would never restore after 30 days should not silently become a ten-year archive.

Cloudflare R2 lifecycle rules (Infrequent Access, abort multipart)

For R2 the fix is usually transition, not deletion. Separate active media from cold originals: set a lifecycle rule that moves objects to Infrequent Access once they age out of normal use (the public example used 90 days; your access data should set the threshold), apply expiry only where the business record truly ends, and abort incomplete multipart uploads so abandoned uploads stop occupying billed space. Your access pattern — not a headline rate — should decide what stays hot.

Minimum-duration traps (S3 IA 30 days, Glacier FR 90, Deep Archive 180)

Lifecycle rules are not free of fine print. The colder S3 tiers carry minimum storage durations: 30 days for S3 Infrequent Access, 90 days for S3 Glacier Flexible Retrieval, and 180 days for S3 Glacier Deep Archive. Deleting an object — or transitioning it onward — before its tier’s minimum still charges for the remainder of the minimum. A lifecycle policy that churns objects through tiers faster than the minimums can cost more than the leak it was meant to fix, so transitions should be timed past the minimum of the tier the object is leaving.

A five-question self-check

  • Can you state, for each major bucket, how many days of prior versions you retain?
  • Do deleted objects actually expire, or does versioning preserve them?
  • What percentage of stored bytes has not been read in 90, 180, or 365 days?
  • Are incomplete multipart uploads cleaned up automatically?
  • Could you tie last month’s storage growth to a product event—or is it just “backups”?

If those answers are missing, the cheapest storage migration may be no migration at all. It may be a lifecycle policy, a retention decision, and a verified invoice.

FAQ

Does S3 versioning cost extra?

Versioning itself is not a separate line item — the cost is the storage. Every noncurrent version of an object is stored and billed like a live object, alongside delete markers and incomplete multipart uploads. With no lifecycle rule, those old copies bill forever, which is how a bucket quietly becomes a permanent subscription to your own history.

How do I delete old S3 versions automatically?

Add a lifecycle rule with NoncurrentVersionExpiration: set NoncurrentDays to your real retention window and the rule expires prior versions automatically. Pair it with ExpiredObjectDeleteMarker to clean up delete markers and AbortIncompleteMultipartUpload so abandoned uploads stop billing. The JSON in this post expires versions after 30 days while keeping the 3 newest noncurrent versions.

How do I keep only N versions of an S3 object?

Use NewerNoncurrentVersions in the same lifecycle rule: it caps how many newer noncurrent versions are retained while the age-based expiration applies to the rest. AWS requires a Filter on the rule when NewerNoncurrentVersions is set — an empty prefix applies the rule to the whole bucket. The example rule in this post keeps 3.

Why didn’t deleting files shrink my Backblaze B2 bill?

Because with versioning, a delete usually hides the current file rather than erasing the data. Earlier versions stay stored — and billed — until a lifecycle rule expires them, governed by daysFromHidingToDeleting. In Pieter Levels’ public example, B2 backups retained effectively forever fell from $1,200 to $26 a month once a 30-day lifecycle replaced infinite retention.

Do lifecycle rules have minimum-duration charges?

Yes, on the colder tiers. S3 Infrequent Access carries a 30-day minimum storage duration, Glacier Flexible Retrieval 90 days, and S3 Glacier Deep Archive 180 days. Transitioning or deleting before the minimum still charges for the remainder, so lifecycle timings should be set with the minimums in mind — moving data too eagerly can cost more than leaving it.

Want the leak found before anything is deleted?

Buy Me Compute’s Stack Cost Audit begins read-only: one bill, the bucket inventory, and a priced map of versions, cold data, and missing lifecycle rules. We pilot the proposed lifecycle on one low-risk bucket, roll the approved retention and storage-class rules out in waves, and verify against the next real invoice by bucket and storage class. Only after you approve the retention decisions does anything change.

Email [email protected]

Post your own savings example

Found a storage bill, renewal shock, lifecycle leak, or migration that paid? Email the service, old bill, fix, and new bill to [email protected]. We are collecting patterns that may become future posts. Please do not send credentials or private invoices.

Related migrations

Sources and basis

Pieter Levels’ public November 2026 cost-cutting post, as captured in Buy Me Compute’s stack-pain research: Backblaze B2 at $1,200/month before a 30-day version lifecycle and $26/month after; Cloudflare R2 at $1,200/month before a 90-day Infrequent Access transition and $800/month after. These are his reported figures and are explicitly not Buy Me Compute client results. Lifecycle rule fields and minimum storage durations follow the providers’ published documentation patterns (Amazon S3 lifecycle configuration, Backblaze B2 lifecycle rules).