Most teams set their S3 storage class once, at object creation, and never touch it again. Then eighteen months later someone opens the Cost Explorer, sees a storage line item that's grown 3x faster than actual data volume, and can't figure out why. Here's the actual framework — in the order it should happen, not the order most teams discover it.
Step 1: Sort your objects by access pattern, not by age.
Before touching lifecycle policies, split what you're storing into three buckets: hot (accessed regularly, latency-sensitive — active application data, current logs), warm (accessed occasionally, retrieval time doesn't matter much — monthly reports, older user uploads), and cold (rarely or never accessed again, but you're legally or operationally required to keep it — audit logs, compliance archives, old backups). This sort matters more than the storage-class pricing chart, because the actual cost driver isn't which class you pick — it's how badly that class matches the object's real access pattern.
Step 2: Match the class to the bucket — and watch the retrieval trap.
Hot data → S3 Standard. Don't get clever here. The moment you move actively-accessed data to a cheaper class to save on storage, you start paying retrieval fees every time it's touched — and those add up faster than the storage savings did.
Warm data → S3 Standard-IA or Intelligent-Tiering. This is where most teams actually leave money on the table — leaving data that hasn't been touched in 90 days sitting in Standard because nobody set a lifecycle rule, paying full price for storage nobody's reading.
Cold data → Glacier Flexible Retrieval or Glacier Deep Archive, depending on how fast you'd actually need it back. Deep Archive is dramatically cheaper per GB, but retrieval can take 12+ hours — fine for compliance archives, a real problem if "cold" data turns out to be something engineering needs mid-incident.
Step 3: Don't let Intelligent-Tiering fool you into skipping this entirely.
Intelligent-Tiering sounds like it solves the whole problem automatically — and for genuinely unpredictable access patterns, it's the right call. But it has its own monitoring fee per object, and for buckets with millions of small objects, that per-object fee can quietly outweigh what you'd have paid just picking the right class manually. If you already know an object's access pattern (you usually do), a correctly-set lifecycle rule beats Intelligent-Tiering's automation fee every time.
Step 4: Revisit lifecycle policies when access patterns change, not on a fixed schedule.
The actual failure mode isn't "we never set lifecycle policies" — it's "we set them once, for last year's access pattern, and never revisited them." A dataset that was warm during a migration project is cold six months later. A logging bucket that seemed safe to archive at 90 days turns out to get pulled during incident reviews at 120. Tie lifecycle policy review to actual usage data — S3 Storage Lens or a scheduled Athena query against access logs — not to a calendar reminder that nobody reads.
Running Azure or GCP instead? The same three-bucket logic applies — just a different name on the tin:
Access pattern | AWS S3 | Azure Blob | GCP Cloud Storage |
|---|---|---|---|
Hot | Standard | Hot | Standard |
Warm | Standard-IA | Cool | Nearline |
Cold | Glacier Flexible Retrieval | Cold / Archive | Coldline |
Deep archive | Glacier Deep Archive | Archive | Archive |
The actual takeaway: the storage class pricing table is the least important input in this decision. The access-pattern sort in Step 1 is what determines whether lifecycle policies save you money or quietly generate a retrieval-fee bill nobody notices until it's three months of history deep.
Have a bucket you're not sure about — hot, warm, or cold? Reply to this email with the access pattern and I'll help you think through the class match.
