How to Find and Delete Unused AWS Resources (And Stop Paying for Them)
Every AWS account collects waste. Someone spins up an instance for a test, a volume gets detached during a migration, a snapshot is taken “just in case.” None of it gets cleaned up. Months later you’re paying for resources nobody remembers creating.
The good news: most unused AWS resources are easy to find once you know where to look. This guide walks through the biggest offenders, how to confirm they’re really unused, and how to delete them without breaking anything.
Why unused resources pile up
AWS makes it trivial to create things and easy to forget them. There’s no built-in nagging when a volume sits unattached for a month or a NAT gateway routes zero traffic. Costs accrue silently, spread across regions and accounts, until the bill jumps and someone asks why.
The pattern is almost always the same: resources outlive the work that created them. A founder or engineer wears the ops hat part-time, cleanup never makes the sprint, and the meter keeps running.
The biggest sources of AWS waste
Idle EC2 instances
Idle instances are the classic example. A t3.large left running 24/7 costs roughly $60/month even at near-zero CPU. Multiply that across a few forgotten dev boxes and a staging environment nobody turned off, and it adds up fast.
How to find them:
- In CloudWatch, look at average CPU utilization over the last 14 days. Anything consistently under 5% is a strong candidate.
- Cross-check network in/out. An instance with no CPU and no network traffic is almost certainly doing nothing.
- Don’t forget instances that are stopped but still attached to provisioned EBS. You stop paying for compute, but the storage keeps billing.
Before you delete: confirm it’s not a scheduled batch worker or a warm standby. Tag owners and give them 48 hours to object.
Unattached EBS volumes
When you terminate an instance, its root volume usually goes with it, but data volumes set to not delete on termination stick around. They show up in the console as available instead of in-use, and they bill the full provisioned rate whether attached or not.
A 500 GB gp3 volume sitting unattached costs about $40/month for storage you aren’t using.
To find them, filter EBS volumes by state available. Anything that’s been available for more than a week is a candidate. Snapshot it first if you’re nervous, then delete:
aws ec2 describe-volumes --filters Name=status,Values=available
aws ec2 delete-volume --volume-id vol-0a1b2c3d4e
Old and orphaned snapshots
Snapshots are cheap individually, which is exactly why they accumulate. Automated backup jobs without a retention policy can leave thousands of snapshots, and orphaned snapshots (those whose source volume no longer exists) serve no purpose at all.
Look for snapshots older than your actual recovery window (most teams never restore anything older than 30–90 days) and snapshots tied to volumes that have been deleted. A retention policy on your backup tooling prevents this from recurring.
Forgotten S3 buckets
S3 waste is sneakier because storage looks cheap per gigabyte. The problems are scale and storage class:
- Buckets full of old logs, build artifacts, or database dumps nobody reads.
- Objects sitting in Standard storage that should be in Infrequent Access or Glacier.
- Incomplete multipart uploads that never finished but still bill for the parts already uploaded.
Enable S3 Storage Lens to see usage by bucket, add lifecycle rules to transition or expire old objects automatically, and add a rule to abort incomplete multipart uploads after 7 days.
Unused elastic IPs, load balancers, and NAT gateways
These quietly bill for existing, not for being used:
- An Elastic IP not attached to a running instance costs about $3.60/month, small but pure waste.
- An idle Application Load Balancer runs ~$16/month plus capacity units even with no targets.
- A NAT gateway with no traffic still costs ~$32/month just to exist.
Find them by checking for elastic IPs with no association, load balancers with zero healthy targets, and NAT gateways with near-zero processed bytes.
How to delete safely
Finding waste is the easy part. Deleting it without causing an incident takes a little discipline:
- Tag before you touch. Add a
review: delete-candidatetag and a date. - Snapshot anything with data you can’t perfectly account for.
- Give owners a window. A short Slack message and 48 hours catches the “wait, that’s actually used” cases.
- Delete in batches and watch your dashboards. If nothing breaks in a week, move to the next batch.
- Prevent recurrence with lifecycle rules, snapshot retention, and auto-stop schedules for non-production.
Doing this continuously instead of once
The manual approach works, but it’s a point-in-time cleanup. Waste regrows the moment you stop looking. Walking CloudWatch, EBS, S3, and snapshots by hand across every region and account is hours of work you won’t repeat often enough.
This is exactly the kind of sweep a read-only scanner automates. Graymole connects with a read-only role (no agents, no write access) and checks every region for idle instances, unattached volumes, orphaned snapshots, forgotten buckets, and dozens of other waste patterns in a single pass. Every finding comes with an estimated monthly and annual saving, a confidence score, and a copy-paste fix command, so you can sort by dollar impact and act on the biggest wins first.
You can run the same scan on a schedule to catch new waste as it appears, then track savings over time. The first scan is free, so see what’s hiding in your account before the next bill lands.