According to Flexera's 2025 State of the Cloud report, enterprises now waste an estimated $47 billion per year on unused or underutilized cloud resources. That's not a rounding error — it's nearly a third of all cloud spend globally. And the problem is getting worse, not better, as cloud environments grow more complex and teams move faster without governance guardrails.

AWS idle resources are the single biggest contributor to this waste. Idle resources are services you're paying for that deliver zero business value: stopped EC2 instances with running EBS volumes, unattached Elastic IPs, forgotten NAT Gateways, ancient EBS snapshots from instances long since terminated, and load balancers pointing at nothing. In any AWS environment older than 6 months, these exist — often in quantities that would shock even experienced engineers.

This guide gives you the exact CLI commands to find them, the math to quantify the waste, and a step-by-step cleanup process to eliminate it.

$47B
Global cloud waste in 2025 (Flexera)
32%
Of all cloud spend is wasted on average
68%
Of orgs report cloud waste as a top concern

Why Idle Resources Accumulate

Understanding why waste happens is important for preventing its recurrence. The root causes are almost always organizational, not technical:

The Flexera finding that shocked us: 53% of cloud resources in surveyed organizations had zero activity in the past 30 days. Not underutilized — zero activity. These aren't overprovisioned resources; they're truly idle, doing nothing, billing every hour.

Category 1: Stopped EC2 Instances and Their Volumes

A stopped EC2 instance doesn't charge you for compute — but it absolutely charges you for the attached EBS volumes. A stopped m5.4xlarge with two 500 GB gp3 EBS volumes costs you $0 for compute but $80/month for storage. Multiplied across a fleet of stopped instances, this adds up fast.

Find all stopped instances across your account:

aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=stopped" \
  --query 'Reservations[*].Instances[*].{
    ID:InstanceId,
    Type:InstanceType,
    Name:Tags[?Key==`Name`]|[0].Value,
    Stopped:StateTransitionReason,
    VolumeCount:BlockDeviceMappings|length(@)
  }' \
  --output table

----------------------------------------------------------
| DescribeInstances                                      |
+------------------+--------+----------------+-----------+
| ID               | Type   | Name           | Stopped   |
+------------------+--------+----------------+-----------+
| i-0abc123def456  | m5.2xl | prod-migration | User stop |
| i-0def456abc789  | t3.med | test-server-01 | User stop |
| i-0ghi789jkl012  | r5.xl  | perf-test-db   | User stop |
+------------------+--------+----------------+-----------+

For each stopped instance, check its EBS volumes and calculate the monthly cost:

aws ec2 describe-volumes \
  --filters "Name=attachment.instance-id,Values=i-0abc123def456" \
  --query 'Volumes[*].{
    VolumeId:VolumeId,
    Size:Size,
    Type:VolumeType,
    MonthlyCost:Size
  }' \
  --output table

# gp3: $0.08/GB/month | gp2: $0.10/GB/month | io1: $0.125/GB/month

The action: For each stopped instance, determine if it's still needed. If not, take a final AMI snapshot, terminate the instance, and delete (or snapshot+delete) its volumes. If the instance might be needed again, at minimum stop EBS billing by creating a snapshot and deleting the volume — you can recreate the volume from the snapshot when needed.

Category 2: Unattached EBS Volumes

These are the worst kind of idle resource because they have no parent instance to hint at their purpose. An unattached EBS volume is in "available" state — not attached to any instance — and billing you 24/7.

aws ec2 describe-volumes \
  --filters "Name=status,Values=available" \
  --query 'Volumes[*].{
    VolumeId:VolumeId,
    Size:Size,
    Type:VolumeType,
    Created:CreateTime,
    IOPS:Iops
  }' \
  --output table

-------------------------------------------------------------------
| DescribeVolumes                                                  |
+----------------------+------+------+---------------------------+-+
| VolumeId             | Size | Type | Created                   | |
+----------------------+------+------+---------------------------+-+
| vol-0abc123def456789 | 500  | gp2  | 2024-03-15T09:22:11.000Z  | |
| vol-0def456abc789012 | 200  | gp3  | 2024-07-03T14:55:33.000Z  | |
| vol-0ghi789jkl012345 | 1000 | io1  | 2023-11-28T08:10:22.000Z  | |
| vol-0jkl012mno345678 | 100  | gp2  | 2024-01-19T16:44:07.000Z  | |
+----------------------+------+------+---------------------------+-+

# Estimated waste: 500*$0.10 + 200*$0.08 + 1000*$0.125 + 100*$0.10
# = $50 + $16 + $125 + $10 = $201/month from 4 volumes

In large AWS accounts, it's common to find 50–200 unattached volumes representing $500–$5,000/month in waste. Tag each volume with its last known instance before deleting — that context helps you determine whether it's safe to remove.

Enable "Delete on termination" going forward: The default EC2 behavior is to not delete root volumes on termination. Change this for all future instances by setting DeleteOnTermination=true in the block device mapping. This prevents the accumulation problem at the source. For existing instances, you can update this setting via the EC2 console or AWS CLI without stopping the instance.

Category 3: Ancient EBS Snapshots

EBS snapshots are cheap at $0.05/GB/month — until you have thousands of them. Automated backup tools (AWS Backup, third-party tools, custom scripts) create snapshots daily or weekly and frequently have no retention policy. A database with 500 GB of data being snapshotted daily for 2 years accumulates 730 snapshots totaling roughly 36 TB of snapshot storage — at $0.05/GB/month, that's $1,800/month in snapshot costs alone.

# Find snapshots older than 90 days that you own
aws ec2 describe-snapshots \
  --owner-ids self \
  --query 'Snapshots[?StartTime<=`2025-10-12`].{
    ID:SnapshotId,
    Size:VolumeSize,
    Created:StartTime,
    Desc:Description
  }' \
  --output table | head -50

# Count and total size of all your snapshots
aws ec2 describe-snapshots --owner-ids self \
  --query 'Snapshots[*].VolumeSize' \
  --output text | tr '\t' '\n' | \
  awk '{sum+=$1; count++} END {
    printf "Snapshots: %d\nTotal GB: %d\nEst. cost: $%.2f/month\n",
    count, sum, sum*0.05
  }'

The fix: Implement AWS Data Lifecycle Manager (DLM) policies that automatically delete snapshots older than your retention requirement. Most teams need 7–30 days of daily snapshots for operational recovery, and 90 days for compliance. Beyond that, snapshots should either be deleted or moved to EBS Snapshot Archive (75% cheaper for rarely-accessed archives).

Category 4: Unused Elastic IP Addresses

Elastic IPs (EIPs) are allocated IPv4 addresses. AWS charges $0.005/hr ($3.60/month) for every EIP not associated with a running instance. This sounds trivial until you find 50 of them — $180/month for addresses attached to nothing.

aws ec2 describe-addresses \
  --query 'Addresses[?AssociationId==null].{
    AllocationId:AllocationId,
    PublicIP:PublicIp,
    Domain:Domain
  }' \
  --output table

# Output shows all EIPs with no current association:
------------------------------------------------
| DescribeAddresses                            |
+---------------------------+----------------+-+
| AllocationId              | PublicIP       | |
+---------------------------+----------------+-+
| eipalloc-0abc123def456789 | 54.234.12.87   | |
| eipalloc-0def456abc789012 | 18.144.55.200  | |
| eipalloc-0ghi789jkl012345 | 3.88.12.145    | |
+---------------------------+----------------+-+
# 3 idle EIPs = $10.80/month wasted

Release unused EIPs immediately. Before releasing, check if the IP is referenced in DNS records, firewall rules, or application configs — but if none of those are the case, release it. AWS will give you a different EIP when you need one.

Category 5: Forgotten NAT Gateways

NAT Gateways are charged at $0.045/hr ($32.40/month) per gateway, plus $0.045/GB of data processed. A NAT Gateway provisioned for a VPC that no longer has private subnet instances is pure waste — $32.40/month for a gateway routing zero bytes.

aws ec2 describe-nat-gateways \
  --filter "Name=state,Values=available" \
  --query 'NatGateways[*].{
    ID:NatGatewayId,
    VpcId:VpcId,
    SubnetId:SubnetId,
    Created:CreateTime
  }' \
  --output table

# For each NAT Gateway, check if any instances in its VPC
# are actually routing through it by checking CloudWatch metrics:
aws cloudwatch get-metric-statistics \
  --namespace AWS/NATGateway \
  --metric-name BytesOutToDestination \
  --dimensions Name=NatGatewayId,Value=nat-0abc123def456789 \
  --start-time 2026-01-14T00:00:00Z \
  --end-time 2026-01-21T00:00:00Z \
  --period 604800 \
  --statistics Sum \
  --output text
# If Sum is 0 or very low, the NAT Gateway is idle

Consolidate NAT Gateways with VPC endpoints: Before deleting NAT Gateways, evaluate whether VPC endpoints could replace the traffic they're handling. S3, DynamoDB, ECR, SQS, SNS, and most AWS services support VPC endpoints, which route traffic within the AWS network for free. Replacing NAT Gateway traffic to these services with VPC endpoints can eliminate NAT Gateway costs entirely for some workloads.

Category 6: Idle Load Balancers

Application Load Balancers (ALBs) cost $0.008/LCU/hr plus $0.0225/hr base — roughly $16–$20/month per ALB at minimum. Classic Load Balancers cost $0.025/hr (~$18/month base). An ALB with no healthy targets in any target group is idle — it's processing nothing, but you're still paying the hourly rate.

aws elbv2 describe-load-balancers \
  --query 'LoadBalancers[*].{
    Name:LoadBalancerName,
    ARN:LoadBalancerArn,
    Type:Type,
    State:State.Code
  }' \
  --output table

# Then check target health for each ALB:
aws elbv2 describe-target-groups \
  --load-balancer-arn arn:aws:elasticloadbalancing:... \
  --query 'TargetGroups[*].TargetGroupArn' \
  --output text | xargs -I{} aws elbv2 describe-target-health \
  --target-group-arn {}

Any ALB with zero healthy targets for more than 7 days should be evaluated for deletion. Check if it's still referenced in Route 53 records or application configs before removing.

Building a Systematic Waste Detection Process

Running these commands manually works for a one-time cleanup, but idle resources accumulate continuously. To maintain a clean environment, you need ongoing detection. Here are three approaches from simple to comprehensive:

Option 1: AWS Trusted Advisor (Built-In)

Available on Business ($100/month minimum) and Enterprise support plans, Trusted Advisor automatically flags idle EC2 instances (under 10% CPU for 4+ days), unused EIPs, underutilized EBS volumes, and idle load balancers. It's not comprehensive and misses many waste categories, but it's included in your support plan cost.

Option 2: Scheduled AWS CLI Scans

A weekly Lambda function running the queries above and posting results to Slack costs virtually nothing and catches most idle resource categories. The limitation is that it requires engineering time to build and maintain, and the output requires manual review.

Option 3: AI-Powered Continuous Monitoring

Tools like Cloud Hero AI's savings platform continuously analyze your AWS environment, identify waste across all categories, quantify the exact dollar cost, and (with permissions) can remediate issues automatically. This is the fastest path from finding waste to eliminating it without requiring engineering cycles.

Find Your Hidden AWS Waste in Minutes

Cloud Hero AI scans your entire AWS environment for idle resources, orphaned volumes, unused IPs, and forgotten gateways — then shows you exactly how much each is costing you, with one-click remediation options.

Start Free Waste Scan →

Frequently Asked Questions

Is it safe to delete old EBS snapshots?
Generally yes, with caveats. First, verify the snapshot isn't referenced by any AMI (you can't delete a snapshot that's the basis for a registered AMI). Second, check whether the snapshot is your only backup of production data — if so, keep it or archive it to EBS Snapshot Archive. Third, confirm no compliance requirements mandate keeping it. For application snapshots older than your defined retention window (typically 30–90 days), deletion is safe and recommended.
Why does AWS keep charging for stopped instances?
AWS only stops charging for compute (the EC2 instance hourly rate) when an instance is stopped. Attached EBS volumes, Elastic IPs, and other associated resources continue to bill regardless of instance state. To stop all billing, you must terminate the instance — not just stop it. Stopping is useful when you need to preserve the instance's configuration and data temporarily, but termination is the only way to eliminate all associated costs.
How often should I audit for idle resources?
At minimum, monthly. For fast-moving teams, weekly or continuous automated monitoring is better. Idle resources accumulate quickly — a two-week project can leave behind $200/month in orphaned volumes and IPs. The longer idle resources go undetected, the more they compound. Automated scanning tools make continuous monitoring trivial from a cost perspective.
Can I automate the cleanup of idle resources?
Yes, with appropriate guardrails. Releasing unused Elastic IPs, deleting expired snapshots (past retention policy), and removing empty target groups can be safely automated. Terminating EC2 instances or deleting EBS volumes should require human approval because the blast radius of a mistake is higher. Cloud Hero AI supports automated remediation for low-risk items and human-approved workflows for higher-risk cleanups.
What's the fastest way to find idle resources across multiple AWS accounts?
AWS Organizations with Resource Explorer or AWS Config aggregators can provide cross-account visibility into resources. However, native tooling requires significant setup and still requires manual analysis. Purpose-built tools like Cloud Hero AI are designed specifically for multi-account analysis and can scan an entire AWS Organization in minutes, aggregating idle resource findings with dollar costs across every account.