Unattached EBS volumes: How to Find Them and What They Cost

Unattached EBS volumes are volumes left behind when instances were terminated, billed at full price for storage nobody reads. They are one of the most reliable sources of pure waste in an AWS account, because nothing in the console flags them and nothing fails when they exist — the bill simply stays higher than it needs to be.

This page covers what they cost, the command that lists yours, and — importantly — what to check before deleting anything.

What they are and why they linger

An EBS volume is billed for its provisioned size from creation to deletion, whether or not it is attached to anything and whether or not a single byte is read. A volume in the available state is doing nothing and costing exactly what it cost when it was in use.

They accumulate because DeleteOnTermination defaults to true for the root volume and false for additional volumes attached at launch. Terminate an instance with a separate data volume and the root disappears while the data volume quietly stays.

What they cost

At roughly $0.08 per GB-month for gp3, a forgotten 500 GB volume is about $40 a month, $480 a year. Provisioned IOPS volumes are worse: io2 charges for storage and for provisioned IOPS, so an idle io2 volume with 10,000 IOPS provisioned can exceed $700 a month while serving nothing.

The pattern is rarely one large volume. It is fifty small ones from three years of terminated instances.

Finding yours

# Every unattached volume, largest first, with age and cost estimate.
aws ec2 describe-volumes --filters Name=status,Values=available \
  --query 'sort_by(Volumes,&Size)[*].{Id:VolumeId,GB:Size,Type:VolumeType,AZ:AvailabilityZone,Created:CreateTime,Name:Tags[?Key==`Name`]|[0].Value}' \
  --output table

# Total gigabytes sitting idle in this region.
aws ec2 describe-volumes --filters Name=status,Values=available \
  --query 'sum(Volumes[].Size)' --output text

# Across every region you use.
for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
  gb=$(aws ec2 describe-volumes --region "$r" --filters Name=status,Values=available \
        --query 'sum(Volumes[].Size)' --output text 2>/dev/null)
  [ "$gb" != "None" ] && [ -n "$gb" ] && echo "$r: $gb GB unattached"
done

Before you delete

Snapshot before deleting. A volume in available state may be the only copy of something. A snapshot costs a fraction of the volume and can be kept indefinitely:

aws ec2 create-snapshot --volume-id vol-0123456789abcdef0 \
  --description "pre-deletion snapshot $(date -u +%F)" \
  --tag-specifications 'ResourceType=snapshot,Tags=[{Key=Reason,Value=cleanup}]'

Check the age too. A volume detached this morning is probably part of work in progress; one detached two years ago is not. And read the Name tag — volumes named after a database or a licence server deserve a conversation before deletion.

Stopping them coming back

Set DeleteOnTermination=true on data volumes in launch templates where the data is genuinely disposable, and use a lifecycle policy or a tag convention where it is not.

Run the query above on a schedule — a weekly Lambda that tags anything unattached for more than 30 days with cleanup-candidate and posts the list is a twenty-line function that pays for itself immediately.


Prices quoted are us-east-1 list rates, last checked on 2026-08-22, and are shown with the arithmetic so you can substitute the rates for your own region. Always confirm against the AWS pricing page before acting on a number.