How to Find Unattached EBS Volumes in AWS (and Safely Delete Them)
Unattached EBS volumes are the purest form of cloud waste. Nothing is reading them, nothing is writing to them, no application would notice if they vanished, and they bill every hour anyway.
They accumulate quietly. An engineer terminates an instance and the data volume survives because DeleteOnTermination was false. Someone detaches a volume to migrate data and never comes back to it. An Auto Scaling launch half-fails. None of these leave an alert behind, and none of them show up as a line item you would recognise on a bill, because EBS charges land in one aggregate storage figure.
Why they cost more than people expect
EBS bills on provisioned capacity, not on use. You pay for the size you asked for, whether the volume holds a production database or nothing at all.
That means a 500 GB gp3 volume costs the same detached as it did attached. At roughly $0.08 per GB-month for gp3 in us-east-1, that single forgotten volume is about $40 a month, or close to $500 a year, for storage nobody has read since the instance was terminated. Older gp2 volumes cost about 25 percent more per GB for the same capacity.
Two extras catch people out:
- Provisioned IOPS keep billing. On io1 and io2 volumes, IOPS are a separate charge from capacity. A detached io2 volume with high provisioned IOPS can cost several times what its gigabytes suggest.
- Snapshots outlive the volume. Teams often snapshot before deleting, then forget the snapshot. Snapshot storage is cheaper per GB, but an unmanaged pile of them becomes its own line item.
Finding them in the console
The EC2 console shows this in one place. Open EC2 → Elastic Block Store → Volumes and filter on State = available. Anything listed is attached to nothing.
Add the Created column and sort by it. Volumes created months ago are the safest deletion candidates, and they are usually the ones nobody remembers.
The catch is that this view is per-region. Waste hides in the regions you do not think about: the one where someone tested a service in 2024, the one a Terraform default put resources in. Check every region you have ever used, not the two you look at daily.
Finding them from the CLI
The CLI is the practical option once you have more than one region or account. This lists available volumes in a single region with the fields you need to make a decision:
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[].{ID:VolumeId,GiB:Size,Type:VolumeType,AZ:AvailabilityZone,Created:CreateTime}' \
--output table
To sweep every region, loop over the region list:
for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
echo "== $region"
aws ec2 describe-volumes --region "$region" \
--filters Name=status,Values=available \
--query 'Volumes[].[VolumeId,Size,VolumeType,CreateTime]' \
--output text
done
Run that once per account. If you have an AWS Organization, repeat it under an assumed role in each member account, which is where the total usually turns out to be larger than anyone guessed.
Deciding what is actually safe to delete
“Available” means nothing is attached right now. It does not mean the data is worthless. A volume detached yesterday during a migration looks identical to one abandoned two years ago.
Work through the candidates in this order:
- Sort by creation date. Anything older than a few months with no tags is almost always genuine waste.
- Read the tags. An owner, a stack name, or a
Namereferencing a long-dead service tells you most of what you need. - Snapshot anything you cannot fully account for. A snapshot costs a fraction of the volume and buys you a reversible decision.
- Give owners a window. A short message listing the volumes and a 48 hour deadline catches the “wait, I need that” cases before they become an incident.
- Delete in batches, oldest first, and leave a week between batches.
One related check while you are in here: volumes attached to stopped instances do not show as available, and they bill exactly the same. A stopped EC2 instance costs nothing for compute and keeps charging for every attached gigabyte. Those are worth a separate pass.
Stopping them coming back
Cleanup is a one-off. Prevention is what keeps the number near zero:
- Set
DeleteOnTerminationon data volumes where the data genuinely is disposable. It defaults to true for root volumes and false for additional volumes, which is exactly why extra volumes are the ones that pile up. - Require an owner tag on volume creation, enforced through an SCP or your IaC review, so a future you knows who to ask.
- Add an AWS Config rule. The managed
ec2-volume-inuse-checkrule flags volumes that are not in use, and can optionally flag ones withoutDeleteOnTerminationset. - Put a retention rule on snapshots so the snapshots you take during cleanup do not become the next thing to clean up.
Trusted Advisor has a related check, but the underutilised-volume check sits behind Business or Enterprise support, and it reports low utilisation rather than the detached state specifically. Config plus a scheduled sweep is the cheaper path for most teams.
Doing the sweep continuously
Everything above works, and it works exactly once. Waste regrows the week after you finish, because the behaviours that create it are normal engineering behaviours, not mistakes anyone will stop making.
The honest version of this job is a scheduled read-only sweep across every region and every account, with a dollar figure attached so you can sort by what actually matters. That is what Nuvozy does: it connects with a read-only role, no agents and no write access, checks every region for unattached volumes, orphaned snapshots, idle instances, and dozens of other waste patterns, and prices each finding using live AWS pricing so the cleanup list is already sorted by impact.
The first scan is free, which is usually enough to find out whether this is a $40 problem or a $4,000 one.