Identifying Expensive AWS Resources
Knowing that EC2 is your most expensive service is useful. Knowing that three of your EC2 instances are running at 3% CPU utilisation and could be replaced by something a quarter of the size — that’s actionable. This page shows you how to get to that level of detail.
The orphaned resource problem
Cloud environments accumulate forgotten resources. A developer spins up a test EC2 instance and an EBS volume, does their testing, terminates the instance — but doesn’t think to delete the EBS volume. The volume silently charges $0.08/GB/month indefinitely.
The same pattern repeats with:
- Elastic IPs not attached to a running instance: $0.005/hour when idle
- Load balancers with no registered targets still running
- NAT Gateways in VPCs no longer hosting anything
- EC2 snapshots from instances that were terminated months ago
- Old Lambda versions with provisioned concurrency still allocated
- CloudWatch log groups with no retention policy storing years of logs
- RDS snapshots from databases that no longer exist
- Old AMIs (and their underlying EBS snapshots) from instance types you no longer use
A quarterly audit of a single AWS account belonging to a 10-person engineering team found $800/month in resources that could be safely deleted. None of them were intentional — they all accumulated through normal development activity over 18 months.
Resource tagging as the foundation
Before you can assign costs to specific teams, projects, or applications, your resources need to be tagged consistently. AWS Cost Explorer can group costs by tag, so a tag like Environment: staging or Team: payments turns a billing line item into actionable information.
Without tagging, you know EC2 cost $4,000 last month. With tagging, you know the payments team’s staging environment EC2 instances cost $1,200 and the platform team’s production instances cost $2,800.
See resource tags for how to design a tagging strategy. For cost purposes, the minimum useful tags are:
Environment(production, staging, dev)TeamorProjectOwner(the person or team responsible)
AWS Trusted Advisor cost checks
Trusted Advisor’s cost optimisation checks scan your account for resources that are likely wasting money. Key checks include:
Idle EC2 instances — instances with very low CPU utilisation and network I/O over the past two weeks. These are candidates for rightsizing or termination.
Unassociated Elastic IP addresses — Elastic IPs not attached to running instances, which cost $0.005/hour when idle.
Underutilised Amazon EBS volumes — volumes with low write activity over the past week, which may be safe to delete or downsize.
Idle load balancers — Application and Classic Load Balancers with little or no traffic.
Underutilised RDS DB instances — RDS instances with very low connection counts and CPU usage.
Savings Plans and Reserved Instance coverage — checks whether your On-Demand usage is covered by Savings Plans, and whether existing Reserved Instances are being utilised.
Full Trusted Advisor checks require a Business or Enterprise support plan. The free tier only exposes a subset of checks. If you’re on a Business plan, run a Trusted Advisor review monthly — the findings often identify savings that far exceed the support plan cost.
AWS CLI commands for finding orphaned resources
The following commands help you find resources that are accruing charges without serving a useful purpose.
Unattached EBS volumes:
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[*].{ID:VolumeId,Size:Size,Created:CreateTime}' \
--output tableElastic IPs not associated with any resource:
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==null].{IP:PublicIp,AllocationId:AllocationId}' \
--output tableLoad balancers with no registered targets:
aws elbv2 describe-load-balancers \
--query 'LoadBalancers[*].{Name:LoadBalancerName,State:State.Code,ARN:LoadBalancerArn}' \
--output tableThen for each load balancer ARN, check if it has any healthy targets:
aws elbv2 describe-target-health \
--target-group-arn arn:aws:elasticloadbalancing:...RDS snapshots older than 90 days:
aws rds describe-db-snapshots \
--query 'DBSnapshots[?SnapshotCreateTime<=`2025-12-20`].{ID:DBSnapshotIdentifier,Size:AllocatedStorage,Created:SnapshotCreateTime}' \
--output tableLambda functions with provisioned concurrency:
aws lambda list-provisioned-concurrency-configs \
--function-name YOUR_FUNCTION_NAMERun through each Lambda function in your account to find provisioned concurrency allocations that may no longer be needed.
The EBS snapshot accumulation problem
EBS snapshots are charged at $0.05/GB/month for the data stored in each snapshot. Snapshots are incremental, so they don’t each take up a full copy of the volume — but they accumulate.
The critical thing most people don’t know: terminating an EC2 instance does not delete its EBS snapshots. Snapshots are independent resources. If you’ve been taking daily backups of a 500GB volume for a year, you could have hundreds of snapshots totalling thousands of GB in storage, all silently charging.
To list all EBS snapshots owned by your account:
aws ec2 describe-snapshots \
--owner-ids self \
--query 'Snapshots[*].{ID:SnapshotId,Size:VolumeSize,Created:StartTime,Desc:Description}' \
--output tableFor snapshot lifecycle management, use Amazon Data Lifecycle Manager to automatically delete snapshots after a retention period. If you don’t have a retention policy, set one up — there is no reason to keep EBS snapshots indefinitely.
Using AWS Config rules to enforce hygiene
AWS Config can enforce resource hygiene automatically. Managed Config rules relevant to cost include:
ec2-volume-inuse-check— flags EBS volumes not attached to any instanceeip-attached— flags Elastic IPs not associated with any resourcerds-instance-public-access-check— flags publicly accessible RDS instances (also a security concern)required-tags— flags resources missing required tags
When a Config rule detects a non-compliant resource, it can trigger an SNS alert or invoke a Lambda to auto-remediate (e.g., delete the unattached volume after a grace period). This moves cost hygiene from a manual quarterly exercise to a continuous automated process.
AWS Cost and Usage Report for resource-level data
The AWS Cost and Usage Report (CUR) is the most granular cost data AWS provides. It delivers hourly or daily line-item data to an S3 bucket, including the specific resource ID (e.g., i-1234567890abcdef0 for an EC2 instance) that incurred each charge.
CUR data requires a tool to query — it’s delivered as compressed CSV files. Common approaches are loading it into Athena (AWS’s serverless query service) for SQL queries, or using a third-party tool like CloudHealth or Spot.io.
For most teams, Cost Explorer with good tagging covers 90% of what they need. CUR is for teams with complex cost allocation requirements or who need per-instance cost data that isn’t available in Cost Explorer.
Note: When you find unattached EBS volumes, verify before deleting. Some backup processes or DR setups intentionally create unattached volumes. Check the creation date, tags, and any recent snapshot activity before assuming a volume is safe to remove. If in doubt, take a final snapshot before deleting — it’s cheap insurance.
Common mistakes
- Not tagging resources at creation time — Retroactively tagging hundreds of resources is painful. Enforce tags at creation using tag policies in AWS Organizations or Service Control Policies.
- Assuming instance termination cleans everything up — Terminating an EC2 instance does not delete its snapshots, any manually created EBS volumes, or any Elastic IPs that were associated with it. Each of these must be cleaned up separately.
- Ignoring snapshot accumulation — EBS snapshot costs grow silently. A volume with daily snapshots and no retention policy accumulates 365 snapshots per year. Use Data Lifecycle Manager to enforce retention.
- Running Trusted Advisor reviews once and forgetting — New orphaned resources appear continuously as development work progresses. Schedule a monthly Trusted Advisor review as a recurring calendar item.
- Deleting resources without verifying ownership — Before deleting any resource, check its tags, creation date, and whether any other resource depends on it. Automated cleanup scripts should have a dry-run mode.
Summary
- Orphaned resources — unattached EBS volumes, idle Elastic IPs, old snapshots — silently accumulate cost
- Terminating an EC2 instance does not delete its EBS snapshots — they must be deleted separately
- Trusted Advisor cost checks identify idle and underutilised resources (full checks need Business support)
- AWS CLI commands can list unattached volumes, idle Elastic IPs, and old snapshots across your account
- AWS Config rules can automatically flag and remediate non-compliant resources
- Consistent tagging is required for Cost Explorer tag-based cost allocation
- The Cost and Usage Report provides per-resource-ID cost data for teams needing maximum granularity
Frequently asked questions
Can I see per-resource costs in Cost Explorer?
Cost Explorer shows costs by service by default. For per-resource detail, you need to enable resource-level data in the Cost and Usage Report (CUR). Alternatively, consistent resource tagging lets you group Cost Explorer by tag to approximate per-resource costs.
What is AWS Trusted Advisor?
Trusted Advisor is an AWS service that scans your account and provides recommendations across cost, performance, security, and fault tolerance. The cost checks include flagging idle EC2 instances, unattached EBS volumes, and underutilised Reserved Instances. Full Trusted Advisor checks require a Business or Enterprise support plan.
How do I find unattached EBS volumes?
Use the AWS CLI: `aws ec2 describe-volumes --filters Name=status,Values=available`. The "available" status means the volume is not attached to any instance. These volumes still incur storage charges.