Why unused resources are a chronic problem
Cloud infrastructure grows fast and shrinks slowly. A developer spins up a test environment, finishes the experiment, terminates the EC2 instance — but forgets the EBS volume, the Elastic IP, and the Application Load Balancer that were created alongside it. AWS silently continues to charge for all three. Multiply that across a team of twenty engineers over twelve months and you have a meaningful chunk of your annual AWS bill funding nothing.
AWS Cost Anomaly Detection can flag sudden spikes, but it is poor at surfacing steady, low-level waste that accumulates gradually. The fastest way to audit that kind of waste is the AWS CLI with a well-crafted --query filter. Here are five commands you should run on every account you manage.
One-liner 1: Unattached EBS volumes $0.08–$0.10 / GB-month
An EBS volume with status available is not attached to any instance. It sits there, billing you at the full gp3 or gp2 rate, indefinitely.
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[*].[VolumeId,Size,CreateTime]' \
--output table
What you get: a table of every detached volume in the current region, its size in GiB, and the date it was created. A 500 GiB gp3 volume sitting detached for a year costs roughly $480 for absolutely nothing. If CreateTime is months old, delete it — after confirming the data is either backed up or worthless.
Pro tip: add --region us-east-1 (or loop over all regions with aws ec2 describe-regions --query 'Regions[].RegionName' --output text) because orphaned volumes follow engineers across every region they ever worked in.
One-liner 2: Unused Elastic IPs ~$3.65 / IP-month
Elastic IPs are free only while associated with a running instance. The moment you stop or terminate the instance without releasing the IP, AWS charges you roughly $0.005 per hour — about $3.65 per month per idle address.
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==null].[PublicIp,AllocationId]' \
--output table
What you get: every Elastic IP in the region that has no AssociationId, meaning it is not currently associated with any network interface. Release any you do not plan to use within the next 24 hours. You can always allocate a new one later.
One-liner 3: Old snapshots $0.05 / GB-month
EBS snapshots are incremental, but they still accumulate cost. A snapshot policy that retains 365 daily backups of a 1 TB database volume will cost you $1,800+ per year just in snapshot storage.
aws ec2 describe-snapshots \
--owner-ids self \
--query 'Snapshots[?StartTime<=`2025-01-01`].[SnapshotId,VolumeSize,StartTime]' \
--output table
What you get: all snapshots you own that were created before 1 January 2025 — anything over 15 months old. Adjust the date cutoff to match your retention policy. Snapshots with no surviving source volume (the original volume was deleted) are almost always safe to remove. Cross-reference with your disaster recovery SLA before deleting anything tagged compliance or legal-hold.
Note on syntax: the backtick-quoted date string in --query is JMESPath literal syntax. It works as-is in bash; on Windows PowerShell you may need to escape the backticks.
One-liner 4: Load balancers with zero healthy targets ~$16–$22 / ALB-month
An Application Load Balancer costs around $0.008 per LCU-hour plus a fixed hourly charge, adding up to roughly $16–22 per month at minimum. A load balancer with no registered targets is the most expensive no-op in AWS.
aws elbv2 describe-load-balancers \
--query 'LoadBalancers[*].[LoadBalancerName,LoadBalancerArn,State.Code]' \
--output table
Then for any load balancer you want to inspect further, check its target groups:
aws elbv2 describe-target-groups \
--load-balancer-arn <YOUR_LB_ARN> \
--query 'TargetGroups[*].[TargetGroupName,TargetType]' \
--output table
aws elbv2 describe-target-health \
--target-group-arn <YOUR_TG_ARN> \
--query 'TargetHealthDescriptions[*].[Target.Id,TargetHealth.State]' \
--output table
What you get: a clear picture of which load balancers have no healthy registered targets. An ALB with zero targets serving zero traffic is a candidate for deletion. Before you do, verify it has no DNS records pointing to it (check Route 53 and your external DNS) and no pending deployment that expects it to exist.
One-liner 5: Stopped EC2 instances with attached EBS storage cost continues
Stopped EC2 instances do not charge for compute — but their attached EBS volumes continue billing at full price. A stopped r6i.8xlarge with 2 TB of gp3 attached is costing you $160+/month in storage even though no one is using it.
aws ec2 describe-instances \
--filters Name=instance-state-name,Values=stopped \
--query 'Reservations[*].Instances[*].[InstanceId,InstanceType,LaunchTime,Tags[?Key==`Name`].Value|[0]]' \
--output table
What you get: every stopped instance in the region, its type, launch date, and Name tag. If an instance has been stopped for more than 30 days, odds are it will never restart. Create a final snapshot of the root volume if needed, then terminate the instance and delete the volumes. This recovers both the storage cost and cleans up your account.
Putting it all together
Running these five commands manually each month works, but the real leverage comes from automation. A simple shell script that loops over all regions, runs each query, and dumps results to a file gives you a monthly waste report in under a minute:
#!/bin/bash
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,CreateTime]' \
--output table
aws ec2 describe-addresses \
--region "$region" \
--query 'Addresses[?AssociationId==null].[PublicIp,AllocationId]' \
--output table
done
Schedule this with a Lambda function on a monthly EventBridge rule and route the output to an SNS email topic. Your team gets a free, automated FinOps report with zero third-party tooling required.
Cert tie-in: SOA-C02, CLF-C02, and SAA-C03
Cost optimization is a first-class domain in multiple AWS certifications. If you are studying for any of these, these one-liners map directly to exam content:
- AWS SysOps Administrator (SOA-C02) — Domain 6 covers Cost and Performance Optimization explicitly. Expect scenario questions about identifying idle resources, right-sizing instances, and using Compute Optimizer. The CLI patterns above are exactly the kind of operational tool the exam expects you to know.
- AWS Cloud Practitioner (CLF-C02) — Covers the AWS pricing model, the shared responsibility model, and the fact that stopped instances still incur EBS costs. A classic CLF trick question is: “Which cost continues when an EC2 instance is in the stopped state?” — answer: EBS storage.
- AWS Solutions Architect Associate (SAA-C03) — Domain 4 (Cost-Optimized Architectures) asks you to design systems that minimize waste. Understanding why unattached volumes and idle EIPs exist — and how to prevent them through lifecycle policies and Infrastructure-as-Code teardown automation — is fair game.
Key takeaways
- AWS charges for existence, not usage. EBS volumes, Elastic IPs, and load balancers bill continuously regardless of whether they are doing anything.
- The AWS CLI
--queryflag uses JMESPath and is powerful enough to replace most console clicking for cost audits. - Stopped EC2 instances are a trap: no compute charge, but full storage charge continues.
- Snapshot costs compound silently. Review snapshots older than your retention policy every quarter.
- These five commands take under two minutes to run and can uncover hundreds of dollars per month in recoverable waste.