AWS Builder Center

Stop Overpaying for S3: A Practical Guide to Storage Classes and Lifecycle Rules

I picked S3 cost optimization because it's broadly relevant, intermediate-friendly, and hands-on. The article covers: - Why using a single storage class is usually a mistake (hot vs. cold data) - A plain-language comparison table of the main storage classes - When to use Intelligent-Tiering vs. explicit lifecycle rules - Working examples in both AWS CLI and Terraform - Real-world gotchas (minimum durations, transition costs, retrieval fees) - How to measure impact with Storage Lens

System and process enhancer.

Stop Overpaying for S3: A Practical Guide to Storage Classes and Lifecycle Rules

Amazon S3 is often the first service people reach for, and the last one they think about optimizing. It just works, so the bill quietly grows. The thing is, most S3 cost problems aren't about how much you store, they're about storing everything in the wrong place.
In this article we'll walk through S3 storage classes, when each one makes sense, and how to put objects on autopilot with lifecycle rules. By the end you'll be able to cut storage costs on an existing bucket without touching a single line of application code.

Why a single storage class is almost always wrong

When you upload an object to S3 without specifying anything, it lands in S3 Standard. Standard is designed for frequently accessed data: low latency, high throughput, and a price to match. That's great for the assets your app reads every second, and wasteful for the 2-year-old logs nobody has opened since they were written.
The reality of most buckets is a long tail: a small fraction of objects are "hot" and accessed constantly, while the majority are "cold" and sit untouched. Paying Standard prices for cold data is the single most common S3 cost mistake.

The storage classes, in plain terms

Here's how to think about the main options without memorizing a pricing table:
Storage classBest forTrade-off
S3 StandardFrequently accessed dataHighest storage price, no retrieval fees
S3 Intelligent-TieringUnknown or changing access patternsSmall monitoring fee per object, moves data automatically
S3 Standard-IA (Infrequent Access)Data accessed a few times a yearCheaper storage, you pay per retrieval + 30-day minimum
S3 Glacier Instant RetrievalArchives you occasionally need right nowCheap storage, higher retrieval cost
S3 Glacier Flexible / Deep ArchiveLong-term archive, rare accessCheapest storage, retrieval takes minutes to hours
A quick rule of thumb:
  • You don't know the access pattern? Use Intelligent-Tiering and let AWS move objects for you.
  • You know the pattern? Use lifecycle rules to transition objects explicitly.
Intelligent-Tiering is the "set it and forget it" choice. It monitors each object and shifts it between tiers based on real access, with no retrieval fees. The only cost is a tiny per-object monitoring charge, which is negligible for larger objects but can add up if you store millions of tiny files.

Lifecycle rules: putting objects on autopilot

A lifecycle rule is a policy attached to a bucket that automatically transitions objects to cheaper classes, or deletes them, as they age. This is where the real, hands-off savings come from.
A typical lifecycle for log or backup data looks like this:
  1. Day 0 to 30: keep in Standard (recent data, might still be queried).
  2. Day 30: transition to Standard-IA.
  3. Day 90: transition to Glacier Flexible Retrieval.
  4. Day 365: delete (or move to Deep Archive if you have a compliance retention requirement).

Doing it with the AWS CLI

Create a file called lifecycle.json:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"Rules": [
{
"ID": "archive-and-expire-logs",
"Status": "Enabled",
"Filter": {
"Prefix": "logs/"
},
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER"
}
],
"Expiration": {
"Days": 365
}
}
]
}
Apply it to your bucket:
1
2
3
aws s3api put-bucket-lifecycle-configuration \
--bucket my-example-bucket \
--lifecycle-configuration file://lifecycle.json
Verify it took effect:
1
2
aws s3api get-bucket-lifecycle-configuration \
--bucket my-example-bucket
Note the Filter with a Prefix: this rule only applies to objects under logs/. You can run different lifecycle strategies for different prefixes in the same bucket, which is handy when one bucket holds both hot assets and cold archives.

Doing it with Terraform

If you manage infrastructure as code, here's the equivalent in Terraform:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
bucket = aws_s3_bucket.my_bucket.id

rule {
id = "archive-and-expire-logs"
status = "Enabled"

filter {
prefix = "logs/"
}

transition {
days = 30
storage_class = "STANDARD_IA"
}

transition {
days = 90
storage_class = "GLACIER"
}

expiration {
days = 365
}
}
}
Keeping lifecycle rules in version control means your cost strategy is reviewable, repeatable, and documented alongside the rest of your infrastructure.

A few gotchas worth knowing

A couple of details trip people up, so flag these before you roll out rules at scale:
  • Minimum storage durations. Standard-IA and Glacier classes have minimum billing periods (30 days for IA, 90 days for Glacier). If you transition an object and delete it shortly after, you still pay for the minimum. Don't transition data that churns quickly.
  • Transition costs. Each transition is a request that costs a small amount. Transitioning millions of tiny objects can cost more than the storage you save. For many small files, Intelligent-Tiering or just packing them into archives is better.
  • Retrieval is not free. IA and Glacier trade cheaper storage for retrieval fees. If you'll read the data often, you haven't saved anything, you've shifted cost to the read path.
  • Rules act on object age, not upload date to the rule. When you apply a rule, it immediately evaluates existing objects by their age. A rule transitioning at day 30 will act on objects already older than 30 days right away.

Measuring the impact

Before and after you apply rules, use S3 Storage Lens (free dashboard in the S3 console) to see how your data is distributed across storage classes and prefixes. It's the fastest way to spot a prefix sitting entirely in Standard that should have aged out months ago.
For a quick command-line snapshot of what's in a bucket:
1
aws s3 ls s3://my-example-bucket --recursive --summarize --human-readable

Wrapping up

S3 cost optimization isn't about a clever one-time cleanup, it's about setting policies that keep working as new data arrives. The pattern is simple:
  1. Use Storage Lens to understand your access patterns.
  2. For predictable data, write lifecycle rules to transition and expire objects.
  3. For unpredictable data, let Intelligent-Tiering do the work.
  4. Watch out for minimum durations and retrieval fees.
Set this up once per bucket and your storage costs will largely take care of themselves, which is exactly how infrastructure should behave.

Have a bucket you've been meaning to clean up? Start with Storage Lens, pick one noisy prefix, and write your first lifecycle rule today.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article