S3 storage cost problems are rarely about the Standard storage class being expensive — it's usually one of three things quietly accumulating in the background: objects sitting in the wrong storage class for how they're actually accessed, incomplete multipart uploads nobody knows exist, and object versions piling up with no lifecycle rule to clean them out.
Storage class transitions aren't free to get wrong
Standard-IA and Glacier tiers cost less per GB to store, but both carry retrieval costs and minimum storage duration charges — Standard-IA has a 30-day minimum, Glacier's is longer. Moving objects into a colder tier only saves money if the access pattern actually matches: infrequently accessed for IA, rarely-to-never accessed for Glacier. A lifecycle rule that transitions objects to Glacier on a schedule, applied to data that's still being read regularly, can end up costing more in retrieval fees than it saved in storage.
Storage cost per GB falls at each stage; retrieval cost rises. The transition only pays off if the access pattern actually matches the schedule.
The fix isn't to avoid tiering — it's to base the transition schedule on actual access patterns rather than a default rule copied from somewhere else. S3 Storage Lens (included at no additional cost) and S3 Inventory reports both show which prefixes are actually being read, which is enough to set transition thresholds that match reality instead of guessing.
Incomplete multipart uploads are invisible until you look
Multipart uploads that fail partway through — a dropped connection, a canceled upload, an application bug — leave their already-uploaded parts sitting in the bucket, billed as normal storage, with nothing in the console making them obvious. Buckets that have been receiving multipart uploads for years, especially from application code that doesn't handle failures well, can be storing a meaningful amount of data that was never a real, retrievable object in the first place.
A lifecycle rule that aborts incomplete multipart uploads after a set number of days (a handful of days is enough for the vast majority of legitimate cases) closes this permanently, and it's one of the few lifecycle rules that has essentially no downside to enabling broadly.
Versioning without a cleanup rule
Versioning is the right call for buckets that need it — protection against accidental overwrites and deletes is worth the storage cost. But versioning with no lifecycle rule on noncurrent versions means every overwrite keeps the old copy forever. On a bucket with frequent overwrites, that adds up to storing far more historical data than anyone is actually relying on, silently, for as long as the bucket exists.
A lifecycle rule that expires noncurrent versions after a defined retention window — long enough to cover realistic recovery needs, not indefinite — keeps the protection versioning provides without the unbounded storage growth.
Where to look
- S3 Storage Lens for a free, account-wide view of storage class distribution and access patterns.
- Any bucket with versioning enabled — check whether a noncurrent-version expiration rule actually exists.
- Lifecycle rules for aborting incomplete multipart uploads — confirm they're set on every bucket that accepts uploads, not just the ones someone remembered.