← Back to Blog

EBS Snapshot Sprawl: The Waste Cost Explorer Can't Show You

Rick Wise5 min read
AWSEBSFinOpsCost OptimizationWaste Detection
EBS Snapshot Sprawl: The Waste Cost Explorer Can't Show You

Last week's short covered the 101 on old EBS snapshots: the 90-day threshold, the $0.05/GB-month rate, aws ec2 delete-snapshot. If you saw it, you already know snapshots are cheap-per-unit and expensive-in-aggregate. What that 42 seconds couldn't fit in is the actual reason snapshot sprawl is so hard to find in the first place — and it isn't the price. It's that Cost Explorer structurally cannot show you who's responsible for it.

Cost Explorer shows you a bill, not a culprit

Group your AWS costs by usage type and EBS snapshots show up as one line: EBS:SnapshotUsage, rolled up per account and region. That's it. Not per-snapshot, not per-volume, not per-AMI. If that line is $340/month, Cost Explorer will tell you the total and nothing about which of your 200 snapshots — or which of your 40 AMIs pinning them — put it there.

Part of why this is so opaque is how the billing actually works. EBS snapshots are incremental: the first snapshot of a volume captures every block, but every snapshot after that only stores blocks that changed since the previous one. Delete an "old" snapshot in the middle of a chain and AWS doesn't just drop its unique blocks — it can merge the still-referenced blocks from that snapshot into the next one to keep the chain valid. The result is a genuinely well-designed storage model that happens to make per-snapshot cost attribution close to meaningless from the outside. You can't look at snapshot #14 in a chain of 20 and know what deleting it actually frees, and Cost Explorer doesn't even try — it just gives you the account-wide sum and moves on.

So the number you see going up every month is real. The reason is invisible from the billing console. You have to go look at the resources directly.

deregistered ≠ deleted — the AMI-pinning trap

Here's the gap that costs teams the most and gets found the least: deregistering an AMI does not delete the snapshot backing it.

When you run CreateImage against an EC2 instance, AWS creates an AMI and, silently, one or more EBS snapshots to back it — you'll find the linkage in the snapshot's own description field, something like Created by CreateImage(i-0abc123def456789) for ami-0fedcba987654321. Deregister that AMI later (cleaning up an old release, retiring a pipeline, whatever the reason) and AWS removes the AMI. The snapshot stays. Forever. Nothing in the console flags it, nothing in Cost Explorer changes shape, and nothing tells you the thing that snapshot was created for no longer exists.

This is exactly the pattern our AMI_ORPHANED_SNAPSHOT detector checks for. It's not a guess — it's a direct read of the relationship AWS itself records: the detector regex-matches each snapshot's description against the CreateImage(...) for (ami-...) pattern, pulls the referenced AMI ID, and checks it against the account's currently-registered AMIs. If the AMI isn't there anymore, the snapshot is flagged — at higher priority than a generic "old snapshot" check, because an orphaned-AMI snapshot has a certain reason to be dead, not just an age-based guess.

That priority ordering matters. Our storage detector dedups three overlapping checks against the same snapshot inventory: AMI-orphan first, then volume-orphan (the source volume was deleted), then plain age (over the 90-day default threshold, at the $0.05/GB-month rate from last week's short). A snapshot only gets counted once, under whichever explanation is strongest — an AMI-orphaned snapshot isn't also reported as merely "old," because "old" undersells why it's actually safe to delete.

Lifecycle policies are the actual fix — for snapshots you haven't made yet

None of the above is a criticism of EBS snapshots as a backup mechanism. They're cheap, they're incremental, and they're the right tool. The problem is entirely operational: nothing deletes them automatically unless you tell it to.

AWS Data Lifecycle Manager (DLM) exists for exactly this — attach a policy to a tag or resource type and it will create snapshots on a schedule and expire them on a schedule, so "backup taken 400 days ago for a server that's been gone for 399 of them" stops being possible going forward. If you're not running DLM policies today, that's the highest-leverage 20-minute fix here, full stop.

But DLM only prevents new sprawl. It does nothing for the snapshots already sitting in your account from AMIs someone deregistered two years ago, or backups nobody automated before DLM was set up. That backlog needs to be found once, by hand or by a scan, before a policy can keep it clean going forward. That's the gap our detector is built for — not a replacement for lifecycle policies, the thing that finds what predates them.

Finding yours

If you want to see this in your own account instead of grepping AMI descriptions by hand, a free scan checks this along with 190+ other waste patterns across 40+ AWS services — read-only, five minutes, nothing gets deleted without you clicking it. If you'd rather watch the 90-day/$0.05-per-GB basics first, we posted a 30-second walkthrough of that detector last week.

Or check the AMI-pinning trap yourself right now:

# Snapshots whose description names a CreateImage/AMI pairing
aws ec2 describe-snapshots --owner-ids self \
  --query "Snapshots[?contains(Description, 'CreateImage')].[SnapshotId,Description]" \
  --output table

# Currently-registered AMI IDs
aws ec2 describe-images --owners self \
  --query "Images[].ImageId" --output text

Cross-reference the AMI ID in each snapshot's description against the second list. Anything missing has been quietly billing you since the day someone cleaned up an AMI and assumed the cleanup was done.

Stop wasting money on AWS

CloudWise monitors 45 AWS services and finds waste automatically. Free forever.

Start Free Scan →