The Hidden Cost of Unattached EBS Volumes (and How to Clean Them Up Safely)
Terminating an EC2 instance doesn't always delete its EBS volumes. A manual walkthrough for finding the ones quietly billing you, and the one thing to check before deleting any of them.
By default, an EC2 instance's root volume is set to delete on termination — but any additional volume you attached later, and any root volume where that flag was explicitly turned off (common on instances launched from a custom AMI or provisioned through older Terraform modules), survives the instance that used to own it. It keeps billing per-GB-month at standard EBS rates, attached to nothing, indefinitely, until someone notices.
Step 1 — list every unattached volume
aws ec2 describe-volumes \
--filters "Name=status,Values=available" \
--query "Volumes[].{Id:VolumeId,Size:Size,Type:VolumeType,Created:CreateTime}" \
--output tablestatus=available is EC2's own term for "not attached to anything" — it's not a judgment call, every volume in this list is unattached right now, full stop. The only question is whether it should be.
Step 2 — figure out how long it's actually been sitting there
CreateTime tells you when the volume was created, not necessarily when it was detached — a volume can be years old but only recently orphaned. What you actually want is the detach time, which lives in CloudTrail, not the EC2 API directly:
aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=ResourceName,AttributeValue=vol-0579c64e11011821c \ --query "Events[?EventName=='DetachVolume'].EventTime"
No CloudTrail history for it at all, on a reasonably new account, often means the volume was created standalone and never attached in the first place — worth double-checking it isn't a snapshot restore someone was about to use.
Step 3 — snapshot before you delete, always
This is the one step worth never skipping, even when you're confident: a snapshot costs a fraction of what the volume itself costs per month, and it turns "permanently deleted the wrong volume" into "restored it from snapshot in five minutes."
aws ec2 create-snapshot \ --volume-id vol-0579c64e11011821c \ --description "pre-delete safety snapshot $(date +%F)"
Step 4 — delete the volume
aws ec2 delete-volume --volume-id vol-0579c64e11011821c --region us-east-1
While you're in there: gp2 vs gp3
If a volume you're keeping is still on the older gp2 volume type, switching to gp3 is a same-size, same-durability, roughly 20% cheaper change with no downtime and no data migration — it's a type modification, not a new volume:
aws ec2 modify-volume --volume-id vol-xxxxxxxx --volume-type gp3
The honest caveat
A volume with no CloudTrail detach event and no snapshot lineage is sometimes a volume someone created intentionally and hasn't attached yet — a DR restore staged ahead of a migration, for example. "Unattached" is a fact, not automatically a verdict. The snapshot-first step above exists specifically so that being wrong costs you nothing.
CostLens finds these automatically, snapshot step included.
Connect your AWS account read-only and every unattached volume shows up with its age, its dollar figure, and a remediation command that snapshots it before deleting - no separate step to remember.
Connect your AWS account