The AWS Costs Hiding in Plain Sight
Idle EC2 instances, forgotten EBS volumes, quiet load balancers and NAT gateways. Here’s where AWS waste hides, and how to clean it up safely.
Patrick · Founder of CloudBudgie
5 min read

Your AWS bill is almost always right. It just isn't very helpful.
It tells you what you used. It doesn't tell you what you still need. The test instance someone spun up for a quick experiment. The volume left behind when an old server was shut down. The staging environment nobody remembers owning. They're all on the bill, and none of them come labeled "waste."
Here's the catch: something that looks idle isn't automatically safe to delete. So grab your magnifying glass. Some of this waste is hiding in plain sight.
Your AWS bill can tell you what you used. It can't tell you what you no longer need.
EC2 instances that are running but doing little
We've all done it. You launch an instance to test something, move on to the next fire, and months later it's still running and still billing. Low activity is your first clue, especially in nonproduction accounts. But don't stop at average CPU.
Before you touch it, check network traffic, memory (if you collect it), scheduled jobs, tags, Auto Scaling group membership, and who owns it. That "idle" box might be a bastion host, a warm standby, or a service that only wakes up for the jobs that really matter.
My favorite first step: list the quiet instances, then ask each owner to call theirs required, resizable, schedulable, or removable. Each answer leads to a different change, and only one of them means delete.
EBS volumes nobody is using
Terminating an instance doesn't mean its storage goes with it. Root volumes created at launch are deleted on termination by default. Volumes attached after launch keep DeleteOnTermination set to false, so they survive. Detached volumes sit in the available state, and EBS bills for provisioned storage whether anything is attached or not.
Finding them takes one command:
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[].{ID:VolumeId,GiB:Size,Type:VolumeType,Created:CreateTime}' \
--output table
Before you delete one, check its tags and creation date, find out whether the data has an owner, and decide whether you want a snapshot first. Snapshots are incremental and billed for the data they store. A snapshot usually costs less than keeping the volume, but it isn't a free archive.
There's a second win hiding here: attached volumes provisioned far beyond what they use. Measure carefully, though. File-system usage inside the instance and the volume's provisioned size are two different numbers.
Load balancers serving almost nothing
Load balancers have a way of outliving the apps they were built for. An Application Load Balancer is billed for every hour it runs, plus capacity units for the traffic it handles. Idle still costs money.
Low request counts alone aren't a reason to remove one. Check listeners, target groups, registered and healthy targets, DNS records that point at it, and anything that only fires occasionally, like a monthly partner integration. Application, Network, and Classic Load Balancers report different metrics, so compare like with like.
A "dead" load balancer behind a forgotten DNS record can still matter to somebody. Treat removing it like any other infrastructure change: get it reviewed.
NAT gateways and quiet networking costs
NAT gateways are easy to miss because they're plumbing, not application servers. AWS charges for every hour a NAT gateway is available and for every gigabyte it processes. Traffic that crosses Availability Zones on its way to the gateway can add data transfer charges on top.
Look at your route tables and outbound traffic before deciding a gateway is unnecessary. If a lot of that traffic goes to Amazon S3 or DynamoDB, a gateway VPC endpoint carries no additional charge and keeps it off the NAT gateway. Interface endpoints for other services have their own hourly and per-GB charges, so do the math before swapping one cost for another.
The long tail of cloud waste
Then there's the stuff that's small on its own and adds up together:
- Public IPv4 addresses. AWS charges for public IPv4 addresses, including Elastic IPs that are allocated but not associated with anything.
- S3 versions and uploads. Every stored version of an object is billed, and parts from incomplete multipart uploads keep using storage until the upload is completed or aborted. A lifecycle rule can clean up both.
- Snapshots and images. Old EBS snapshots and container images in Amazon ECR pile up quietly without retention or lifecycle policies.
- Logs. CloudWatch Logs keeps log data forever unless you set a retention period on the log group.
Two quick checks you can run today:
# Elastic IPs that are not associated with anything
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==null].[PublicIp,AllocationId]' \
--output table
# Log groups with no retention period (kept forever)
aws logs describe-log-groups \
--query 'logGroups[?retentionInDays==null].logGroupName'
Every category needs its own definition of unused. A backup kept for compliance isn't waste. A rarely accessed object might be valuable. An old image might be your rollback plan. The goal isn't to delete as much as possible. It's to find the costs that no longer have a purpose, and make good calls about them.
From finding waste to fixing it
Finding waste is the easy part. Fixing it safely is where the real work is. A good recommendation answers five questions:
- What evidence supports it? Metrics, inventory, configuration, and the time window they cover.
- Who owns the resource? Somebody needs to confirm what it's for.
- What changes? The proposed action and its likely cost impact.
- What could break? Dependencies, availability, security, and recovery.
- How is it reviewed and reversed? A safe change process beats a dramatic savings estimate every time.
- 1
Detect
Inventory, metrics, and billing point at a resource.
- 2
Verify owner
Someone confirms what it is for, or that nobody needs it.
- 3
Assess impact
Dependencies, recovery needs, and the expected cost change.
- 4
Review change
A pull request or change request your team approves.
- 5
Validate
Check the workload and the bill after the change ships.
Finding waste is a measurement problem. Fixing it is a change-management problem.
Keep it fixed
A one-time cleanup feels great. Then next week someone launches a new test environment, and some of it gets forgotten too. Infrastructure changes every day, so cost reviews can't be a once-a-year project.
That's what CloudBudgie is built for. It finds idle and detached resources like unattached EBS volumes, idle load balancers, inactive NAT gateways, and unassociated Elastic IPs. It shows you the estimated savings and the evidence behind each one. Then you choose how to fix it: apply supported changes directly, or have BudgieBot open a Terraform or CloudFormation pull request for your team to review. You stay in control the whole way.

