AWS Idle Resource Audit Checklist

A step-by-step interactive audit to find and eliminate idle AWS resources that are silently draining your cloud budget.

☁️ 40 audit checks πŸ”₯ Find waste fast πŸ’° Estimate savings
40
Total Checks
0
Reviewed
0
Issues Found
$0
Est. Monthly Savings
Audit Progress 0%
Start the audit below ↓
πŸ’Έ

Estimated monthly waste found so far

Based on issues flagged in your audit. Actual savings depend on your instance sizes and usage patterns.

/month estimated
πŸ–₯️ EC2 Instances 0 / 10
βœ“
Stopped instances sitting idle for 30+ days
Stopped EC2 still incurs EBS volume, Elastic IP, and ENI charges. A stopped t3.medium costs ~$30–60/month just in attached storage.
How to check β–Ύ
HIGH $30–120/mo

AWS CLI Command

aws ec2 describe-instances \ --filters "Name=instance-state-name,Values=stopped" \ --query 'Reservations[].Instances[].{ID:InstanceId,Type:InstanceType,Stopped:StateTransitionReason}' \ --output table

Fix Options

  • Terminate if confirmed unused
  • Snapshot EBS then terminate
  • Move to Savings Plans if intermittently needed
  • Use ServerScheduler to auto-stop on a schedule instead
βœ“
Dev/staging instances running 24/7
Dev and staging environments rarely need to run nights and weekends. A t3.large running 24/7 vs 8h/day Mon–Fri is ~70% waste.
How to check β–Ύ
HIGH $100–400/mo

AWS CLI Command

aws ec2 describe-instances \ --filters "Name=tag:Environment,Values=dev,staging,qa,test" \ --query 'Reservations[].Instances[].{ID:InstanceId,State:State.Name,Type:InstanceType}' \ --output table

Fix Options

  • Schedule stop at 6pm, start at 8am Mon–Fri
  • Add weekend shutdown
  • ServerScheduler: visual grid scheduler, one-click setup for all envs
βœ“
Instances consistently <10% CPU over 30 days
Low CPU utilisation is the clearest signal of overprovisioning or idle compute. Most AWS accounts have 30–40% of instances in this state.
How to check β–Ύ
HIGH $50–200/mo

AWS CLI Command

aws cloudwatch get-metric-statistics \ --namespace AWS/EC2 \ --metric-name CPUUtilization \ --period 2592000 \ --statistics Average \ --dimensions Name=InstanceId,Value=i-xxxx \ --start-time 2024-01-01T00:00:00Z \ --end-time 2024-01-31T00:00:00Z

Fix Options

  • Downsize to next smaller instance type
  • Consolidate multiple under-used instances
  • Use AWS Compute Optimizer recommendations
  • Enable auto-scaling and remove static over-allocation
βœ“
Old generation instance types (m3, c3, r3, t1, t2)
Previous-gen instances cost 20–40% more than equivalent current-gen (m5, c5, r5, t3) for the same compute. No reason to stay on old gen.
How to check β–Ύ
MED $20–80/mo

AWS CLI Command

aws ec2 describe-instances \ --query 'Reservations[].Instances[?starts_with(InstanceType,`m3`) || starts_with(InstanceType,`c3`) || starts_with(InstanceType,`t2`)].{ID:InstanceId,Type:InstanceType}' \ --output table

Fix Options

  • Change instance type in the console (requires stop/start)
  • t2 β†’ t3 is almost always a drop-in replacement
  • m3/c3 β†’ m5/c5 requires testing but is straightforward
βœ“
Instances not covered by Savings Plans or Reserved Instances
On-demand pricing for stable workloads is typically 30–40% higher than 1-year Savings Plans. Any instance running 8+ hours/day, 5+ days/week should be evaluated.
How to check β–Ύ
MED $30–100/mo

Where to Check

AWS Cost Explorer β†’ Savings Plans β†’ Coverage Report. Filter by service EC2. Any coverage <80% represents uncovered on-demand spend.

Fix Options

  • Compute Savings Plan: most flexible, covers EC2/Fargate/Lambda
  • EC2 Instance Savings Plan: deeper discount for specific families
  • Start with 1-year no-upfront to minimise commitment risk
βœ“
Unattached Elastic IPs
AWS charges $3.60/month per Elastic IP not associated with a running instance. Forgotten test IPs add up quickly.
How to check β–Ύ
LOW $3–15/mo

AWS CLI Command

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

Fix

Release any unattached Elastic IPs you don't need. Note: releasing is permanent β€” if you need to reserve the IP, associate it with a running instance first.

βœ“
Unused Elastic Load Balancers with no targets
ALBs cost ~$16/month minimum even with zero traffic. Classic/NLB have similar fixed costs. Orphaned LBs from decommissioned services are common.
How to check β–Ύ
MED $16–50/mo

AWS CLI Command

aws elbv2 describe-load-balancers --output table # Then for each LB: aws elbv2 describe-target-groups \ --load-balancer-arn \ --query 'TargetGroups[].TargetGroupArn'

Fix

Delete LBs with no target groups or only empty target groups. Check CloudWatch metrics for RequestCount = 0 over the last 14 days before deleting.

βœ“
Over-sized Auto Scaling groups (min capacity too high)
ASGs with minimum capacity set above actual load maintain idle instances 24/7. Many are set-and-forgotten from peak provisioning.
How to check β–Ύ
MED $30–100/mo

Where to Check

EC2 β†’ Auto Scaling Groups. Compare MinSize vs actual in-service count vs CloudWatch CPU metrics. If actual rarely scales above MinSize, MinSize is too high.

Fix

  • Reduce MinSize to 1 (or 0 for dev/staging with scheduled scaling)
  • Add scheduled scale-in for overnight/weekend periods
  • Enable target tracking based on CPU/request count
βœ“
EC2 instances in wrong region (closer region available)
Compute costs vary 15–30% across regions. If your team or users are in Europe and instances are in us-east-1, you may be paying more and adding latency.
How to check β–Ύ
LOW $10–40/mo

Where to Check

AWS Pricing page β†’ compare instance pricing across regions. Use Infracost or CloudPrice to diff costs. eu-west-1 is often cheaper than us-east-1 for equivalent types.

Fix

Snapshot β†’ copy AMI to target region β†’ launch from AMI. Also check data transfer costs before migrating β€” regional egress can offset compute savings.

βœ“
No instance scheduler for batch/cron workloads
Batch jobs, CI runners, and scheduled reports often run on full-time instances but only need compute for minutes or hours per day.
How to check β–Ύ
MED $40–150/mo

Common Patterns

  • Jenkins/GitLab runner: online 24/7, builds run 2–4h/day
  • Report generator: scheduled nightly, runs 30 min
  • Data pipeline: weekday batches, idle all weekend

Fix Options

  • Schedule start/stop around the job window (+ 30min buffer)
  • Move to Fargate/Lambda for truly event-driven jobs
  • ServerScheduler: schedule precise on/off times visually
πŸ—„οΈ RDS Databases 0 / 8
βœ“
Dev/staging RDS running 24/7 (no stop schedule)
RDS instances are often 2–4Γ— the cost of EC2 for equivalent compute. A db.t3.medium at $0.068/hr = $49/mo. Stop it nights and weekends: $14/mo.
How to check β–Ύ
HIGH $35–200/mo

AWS CLI Command

aws rds describe-db-instances \ --query 'DBInstances[?TagList[?Key==`Environment` && (Value==`dev` || Value==`staging`)]].{ID:DBInstanceIdentifier,Class:DBInstanceClass,Status:DBInstanceStatus}' \ --output table

Fix Options

  • Manual stop in console (auto-restarts after 7 days β€” need automation)
  • ServerScheduler handles RDS stop/start on schedule natively
  • Aurora Serverless v2: scales to 0 when idle (good for intermittent dev DBs)
βœ“
Multi-AZ enabled for non-production databases
Multi-AZ doubles the instance cost (you're paying for a standby replica). Dev/staging rarely needs multi-AZ β€” it's production HA, not dev convenience.
How to check β–Ύ
HIGH $50–250/mo

AWS CLI Command

aws rds describe-db-instances \ --query 'DBInstances[?MultiAZ==`true`].{ID:DBInstanceIdentifier,MultiAZ:MultiAZ,Class:DBInstanceClass}' \ --output table

Fix

Modify the instance β†’ disable Multi-AZ. Requires a brief maintenance window. Only disable for environments where downtime during maintenance is acceptable (dev/staging).

βœ“
Overprovisioned storage (gp2 vs gp3, or huge unused space)
gp2 storage costs $0.115/GB/mo. gp3 costs $0.08/GB/mo (30% cheaper, with free baseline 3000 IOPS). And if you've allocated 500GB but use 50GB, that's waste.
How to check β–Ύ
MED $10–80/mo

AWS CLI Command

aws rds describe-db-instances \ --query 'DBInstances[].{ID:DBInstanceIdentifier,Storage:AllocatedStorage,Type:StorageType}' \ --output table

Fix

  • Migrate gp2 β†’ gp3: same performance, 30% cheaper, no downtime
  • RDS doesn't support storage reduction β€” prevents over-allocation next time
βœ“
Old manual snapshots never cleaned up
Manual RDS snapshots are charged at $0.095/GB/month and never auto-expire. A forgotten 100GB snapshot from a year ago costs ~$114 in storage alone.
How to check β–Ύ
MED $10–50/mo

AWS CLI Command

aws rds describe-db-snapshots \ --snapshot-type manual \ --query 'DBSnapshots[].{ID:DBSnapshotIdentifier,Created:SnapshotCreateTime,Size:AllocatedStorage}' \ --output table

Fix

Delete snapshots older than your retention policy. Set a lifecycle policy going forward. AWS Backup can automate expiry and compliance.

βœ“
Read replicas provisioned but receiving no traffic
A read replica costs the same as the primary. If your application doesn't actively route reads to it, it's an expensive hot-standby.
How to check β–Ύ
HIGH $40–200/mo

Where to Check

CloudWatch β†’ RDS β†’ DatabaseConnections metric for your replica. If it's consistently near 0 while the primary is active, the replica is receiving no queries.

Fix

  • Delete the replica if it was created "just in case"
  • If kept for failover: verify promotion readiness instead of keeping always-on
  • Route reads explicitly if you want to justify the cost
βœ“
Backup retention window set too long for dev databases
Automated backup storage is charged at $0.095/GB/month Γ— daily retention days. 35-day retention on a dev DB with lots of writes racks up quietly.
How to check β–Ύ
LOW $5–20/mo

AWS CLI Command

aws rds describe-db-instances \ --query 'DBInstances[].{ID:DBInstanceIdentifier,BackupRetention:BackupRetentionPeriod}' \ --output table

Fix

Reduce backup retention for dev/staging to 1–3 days. For production, 7 days is usually sufficient unless compliance requires more.

βœ“
RDS instances not covered by Reserved Instances
RDS Reserved Instances save 30–60% over on-demand for stable production databases. Any DB running 24/7 for production should be evaluated.
How to check β–Ύ
MED $25–120/mo

Where to Check

RDS β†’ Reserved Instances β†’ check coverage. Cost Explorer β†’ Reserved Instance Coverage β†’ filter to RDS service.

Fix

Purchase 1-year partial upfront RI for your primary production DB class. Multi-AZ RI discounts apply to the primary only; standby is charged on-demand.

βœ“
Aurora clusters with 0 connections for 7+ days
Aurora clusters have fixed cluster-level costs plus instance costs. A paused/forgotten Aurora cluster with no activity can still cost $50–200/month.
How to check β–Ύ
MED $30–150/mo

AWS CLI Command

aws rds describe-db-clusters \ --query 'DBClusters[].{ID:DBClusterIdentifier,Status:Status,Engine:Engine}' \ --output table

Fix

  • Delete unused Aurora clusters (snapshot first)
  • For intermittent dev use, switch to Aurora Serverless v2 (scales to 0)
πŸ’Ύ EBS Volumes & Storage 0 / 7
βœ“
Unattached EBS volumes ("available" state)
Volumes in "available" state aren't attached to any instance but still incur full storage charges. Often left behind when instances are terminated.
How to check β–Ύ
HIGH $5–60/mo

AWS CLI Command

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

Fix

Snapshot then delete unrecognised volumes. If you need the data, keep the snapshot (much cheaper at $0.05/GB/mo). Delete the volume.

βœ“
gp2 volumes (should be gp3 β€” 20% cheaper, same or better performance)
AWS launched gp3 in 2020. gp2 costs $0.10/GB/mo; gp3 costs $0.08/GB/mo and includes 3000 IOPS free. There is no reason to keep gp2.
How to check β–Ύ
MED $5–30/mo

AWS CLI Command

aws ec2 describe-volumes \ --filters "Name=volume-type,Values=gp2" \ --query 'Volumes[].{ID:VolumeId,Size:Size,IOPS:Iops}' \ --output table

Fix

Modify volume type from gp2 β†’ gp3 in the console or via CLI. No downtime required. Takes a few minutes. AWS does this live on the running volume.

βœ“
Old EBS snapshots without a lifecycle policy
EBS snapshots cost $0.05/GB/month and accumulate indefinitely without lifecycle policies. A 500GB disk snapshotted monthly for a year = $300 in snapshots.
How to check β–Ύ
MED $10–50/mo

AWS CLI Command

aws ec2 describe-snapshots --owner-ids self \ --query 'Snapshots[?StartTime<=`2024-01-01`].{ID:SnapshotId,Size:VolumeSize,Date:StartTime}' \ --output table

Fix

  • Create a Data Lifecycle Manager (DLM) policy to auto-expire old snapshots
  • Delete snapshots older than your defined retention period
βœ“
io1/io2 provisioned IOPS much higher than actual usage
io1/io2 charges per provisioned IOPS ($0.065/IOPS/mo for io1). If you've provisioned 10,000 IOPS but peak at 2,000, you're paying for 8,000 unused IOPS = $520/mo.
How to check β–Ύ
HIGH $50–600/mo

Where to Check

CloudWatch β†’ EBS β†’ VolumeReadOps + VolumeWriteOps. Compare peak actual IOPS over 30 days to provisioned IOPS. If peak < 50% of provisioned, reduce.

Fix

Reduce provisioned IOPS to 120–130% of your peak actual usage. Or migrate to gp3 if IOPS are under 16,000 β€” gp3 includes 3,000 IOPS free and can add up to 16,000.

βœ“
S3 buckets using wrong storage class for access patterns
S3 Standard costs $0.023/GB/mo. S3-IA costs $0.0125/GB/mo. Glacier Instant costs $0.004/GB/mo. Data untouched for 30+ days in Standard is likely misclassified.
How to check β–Ύ
LOW $5–30/mo

Where to Check

S3 β†’ Metrics β†’ Storage Lens. Enable S3 Intelligent-Tiering for buckets with mixed access patterns. AWS moves objects automatically between tiers based on actual access.

Fix

Create S3 Lifecycle rules to transition objects to S3-IA after 30 days, Glacier Instant after 90 days. Or use Intelligent-Tiering for uncertain access patterns.

βœ“
S3 versioning enabled with no expiry policy (versions accumulate)
S3 versioning with no lifecycle policy means deleted and overwritten objects accumulate as non-current versions. You may be storing 5–10Γ— the data you expect.
How to check β–Ύ
MED $5–40/mo

AWS CLI Command

aws s3api get-bucket-versioning --bucket my-bucket # List non-current versions: aws s3api list-object-versions --bucket my-bucket \ --query 'DeleteMarkers[].{Key:Key,VersionId:VersionId}'

Fix

Add lifecycle rule: expire non-current versions after 30 days. Permanently delete delete markers when all versions are gone. Run a one-time cleanup for historical accumulation.

βœ“
NAT Gateway data processing costs unchecked
NAT Gateways charge $0.045/GB of data processed. High-traffic private subnets can generate $50–500/month in NAT charges that go unnoticed.
How to check β–Ύ
MED $20–200/mo

Where to Check

CloudWatch β†’ NatGateway β†’ BytesOutToDestination. Cost Explorer β†’ filter by service "EC2-Other" β†’ usage type contains "NatGateway". High costs often from EC2β†’S3 routing through NAT.

Fix

  • Use S3 Gateway Endpoint (free) for EC2β†’S3 traffic to bypass NAT
  • Use Interface Endpoints for other AWS services in VPC
  • Cache ECR images locally if Docker pull is the source
⚑ Lambda & Serverless 0 / 5
βœ“
Lambda functions with memory allocated far above usage
Lambda is billed by GB-seconds. A function using 200MB allocated 1024MB is wasting 5Γ— memory-seconds. AWS Compute Optimizer detects this automatically.
How to check β–Ύ
LOW $5–30/mo

How to Check

AWS Compute Optimizer β†’ Lambda recommendations. Also check CloudWatch max memory used for each function. Target: allocated = max_used Γ— 1.3 (30% headroom).

Fix

Reduce memory to 130% of max observed. Use AWS Lambda Power Tuning (open-source tool) to find the cost-optimal memory size automatically for each function.

βœ“
Provisioned Concurrency never or rarely triggered
Provisioned Concurrency costs $0.015/GB-hour (always on). If the concurrency is rarely hit, you're paying for warm instances that idle. Check actual concurrency vs provisioned.
How to check β–Ύ
MED $10–80/mo

Where to Check

CloudWatch β†’ Lambda β†’ ProvisionedConcurrencyUtilization. If average < 60%, consider reducing provisioned concurrency or using auto-scaling for provisioned concurrency instead.

Fix

  • Reduce provisioned concurrency count
  • Use Application Auto Scaling to scale provisioned concurrency on a schedule
  • Consider Lambda SnapStart for Java functions (built-in cold start fix, no cost)
βœ“
CloudWatch Logs retention set to "Never expire"
Lambda writes logs to CloudWatch Logs at $0.76/GB ingestion + $0.031/GB/month storage. Without expiry, logs accumulate for years. 50GB/month β†’ $130/year just in storage.
How to check β–Ύ
LOW $5–40/mo

AWS CLI Command

aws logs describe-log-groups \ --query 'logGroups[?retentionInDays==null].{Name:logGroupName,Size:storedBytes}' \ --output table

Fix

Set retention to 30 days for dev, 90 days for prod (or per compliance). Use a one-line CLI loop to set retention on all log groups in one go.

βœ“
SQS queues with messages stuck (DLQ ignored, lambda retrying infinitely)
Failed messages retrying in a loop can trigger millions of Lambda invocations and SQS API calls per day. SQS charges $0.40 per million requests.
How to check β–Ύ
MED $5–30/mo

Where to Check

SQS β†’ Dead Letter Queue β†’ NumberOfMessagesSent metric. Lambda β†’ Throttles and Error metrics for associated functions. Any DLQ with growing message count needs investigation.

Fix

Purge the DLQ once the root cause is fixed. Set maxReceiveCount on the main queue to limit retries (3–5 is typical). Add DLQ alarms to catch this going forward.

βœ“
EventBridge / CloudWatch scheduled rules triggering unused functions
Orphaned scheduled rules that fire every minute on a function that returns early (or errors) silently cost invocation fees and log ingestion forever.
How to check β–Ύ
LOW $2–15/mo

AWS CLI Command

aws events list-rules \ --query 'Rules[?ScheduleExpression!=null && State==`ENABLED`].{Name:Name,Schedule:ScheduleExpression}' \ --output table

Fix

Disable or delete rules for functions that no longer exist or serve a purpose. Check the function's invocation count in CloudWatch to confirm it's actually being triggered.

🌐 Networking & Data Transfer 0 / 5
βœ“
High inter-AZ data transfer costs (cross-AZ traffic)
Cross-AZ data transfer costs $0.01/GB each way. Microservices in different AZs making constant cross-calls can rack up $100–500/month unnoticed.
How to check β–Ύ
HIGH $50–300/mo

Where to Check

Cost Explorer β†’ filter by usage type β†’ contains "DataTransfer-Regional". Group by Usage Type. Enable VPC Flow Logs + Athena queries to identify top talker pairs across AZs.

Fix

  • Pin services to a single AZ where cross-AZ HA isn't needed
  • Use AZ-aware load balancing to prefer same-AZ targets
  • Add same-AZ routing to service mesh configs (Istio, App Mesh)
βœ“
Unused VPC endpoints (data routing through NAT unnecessarily)
S3 and DynamoDB Gateway Endpoints are free. Without them, all S3/DynamoDB traffic from private subnets routes through NAT Gateway at $0.045/GB.
How to check β–Ύ
MED $10–100/mo

AWS CLI Command

aws ec2 describe-vpc-endpoints \ --query 'VpcEndpoints[].{Type:VpcEndpointType,Service:ServiceName,State:State}' \ --output table

Fix

Create S3 and DynamoDB Gateway Endpoints for every VPC with private subnets. Free. Takes 2 minutes. Add route table entries for private subnets to route S3/DynamoDB through the endpoint.

βœ“
CloudFront distributions with no traffic (old frontends/CDN setups)
Inactive CloudFront distributions still have a minimum monthly cost (HTTP requests charge). Old distributions from deprecated frontends or test sites accumulate.
How to check β–Ύ
LOW $1–15/mo

Where to Check

CloudFront β†’ Distributions list β†’ check each for Requests metric in CloudWatch. Any distribution with near-zero requests for 30+ days should be reviewed for deletion.

Fix

Disable (not delete) first to confirm nothing breaks. Then delete after 24–48 hours. Keep the SSL certificate if the domain is still in use elsewhere.

βœ“
Unused VPCs, subnets, or route tables from old projects
While VPCs themselves are free, they often have associated NAT Gateways, Internet Gateways, or ENIs that incur costs. Old project infrastructure is commonly forgotten.
How to check β–Ύ
MED $10–70/mo

AWS CLI Command

aws ec2 describe-vpcs \ --query 'Vpcs[].{ID:VpcId,CIDR:CidrBlock,IsDefault:IsDefault,Tags:Tags}' \ --output table # Then check for NAT Gateways in each: aws ec2 describe-nat-gateways --filter "Name=state,Values=available"

Fix

Delete orphaned NAT Gateways first (large cost). Then release Elastic IPs associated with them. Then clean up subnets, route tables, IGWs, and finally the VPC itself.

βœ“
No AWS Cost Anomaly Detection alerts configured
Without anomaly detection, you may not notice a new idle resource or runaway process until the monthly bill arrives. Setup is free, takes 5 minutes.
How to check β–Ύ
LOW Prevents future waste

Where to Set Up

AWS Cost Explorer β†’ Cost Anomaly Detection β†’ Create Monitor. Set alert threshold (e.g. $20 unexpected spend). Add SNS notification to email or Slack.

Fix

Create monitors per service (EC2, RDS, etc.) with individual thresholds. This is the most impactful prevention tool β€” it catches new idle resources within hours, not at month-end.

πŸ”΄ ElastiCache & Other 0 / 5
βœ“
Dev ElastiCache clusters running 24/7 (no stop schedule)
ElastiCache doesn't support stop/start natively. A cache.t3.medium costs ~$50/month. For dev environments, deleting and recreating (or using a schedule-managed approach) saves ~70%.
How to check β–Ύ
HIGH $35–100/mo

AWS CLI Command

aws elasticache describe-cache-clusters \ --query 'CacheClusters[].{ID:CacheClusterId,Status:CacheClusterStatus,Type:CacheNodeType}' \ --output table

Fix Options

  • ServerScheduler can automate ElastiCache cluster deletion + recreation on a schedule
  • For dev: delete cluster at end of day, recreate in the morning (cache is ephemeral anyway)
  • Move to Valkey (ElastiCache open-source compatible, often cheaper at smaller sizes)
βœ“
ElastiCache cluster size over-provisioned (low memory usage)
Redis/Memcached clusters with <30% memory usage are over-provisioned. A cache.r6g.xlarge at 30% usage would save $80/month on a cache.r6g.large.
How to check β–Ύ
MED $20–100/mo

Where to Check

CloudWatch β†’ ElastiCache β†’ DatabaseMemoryUsagePercentage (Redis) or FreeableMemory. If memory usage is consistently <50%, consider downgrading one node size.

Fix

Modify cluster node type (requires brief failover for cluster mode). Scale down one size at a time, monitor for evictions (CurrEvictions metric) after change.

βœ“
Unused CloudWatch dashboards and metric alarms
CloudWatch charges $3/dashboard/month and $0.10/alarm/month. Forgotten dashboards from old projects and orphaned alarms (pointing at deleted resources) accumulate quietly.
How to check β–Ύ
LOW $5–30/mo

AWS CLI Command

# List all alarms (check for INSUFFICIENT_DATA = orphaned) aws cloudwatch describe-alarms \ --state-value INSUFFICIENT_DATA \ --query 'MetricAlarms[].{Name:AlarmName,Reason:StateReason}' \ --output table

Fix

Delete INSUFFICIENT_DATA alarms pointing at non-existent resources. Audit dashboards for last-opened date. Delete unused dashboards (AWS doesn't show last-opened β€” sort by name for old project names).

βœ“
No budget alerts configured (flying blind on spend)
AWS Budgets alerts are free for up to 2 budgets. Without them, spend surprises arrive as shock bills. Basic monthly budget + 80%/100% alerts take 3 minutes to set up.
How to check β–Ύ
LOW Prevents overruns

Where to Set Up

Billing β†’ Budgets β†’ Create Budget β†’ Monthly Cost Budget. Set two thresholds: 80% actual + 100% forecast. Email or SNS alert. Free for first 2 budgets per account.

Pro Tip

Also set a service-level budget for EC2 and RDS separately β€” these are usually the biggest line items and worth monitoring independently. $3/month each after the free tier.

βœ“
AWS Trusted Advisor recommendations ignored (High Utilisation / Idle Resources)
Trusted Advisor's Cost Optimisation checks (Business/Enterprise support tiers) flag idle and underutilised resources automatically. Business plan = $100/month minimum but commonly saves 5–15Γ— that.
How to check β–Ύ
MED $50–500/mo found

Where to Check

AWS Console β†’ Trusted Advisor β†’ Cost Optimization category. On Basic plan: only 6 checks available. On Business+: full 115 checks including idle EC2, unassociated EIPs, underutilised RDS.

Free Alternative

AWS Compute Optimizer (free) covers EC2, Lambda, ECS, EBS. Cost Explorer Rightsizing Recommendations covers EC2 with no Trusted Advisor required.

Stop paying for idle AWS resources

The quickest fix for dev/staging compute and database waste is automated scheduling. ServerScheduler shuts down EC2, RDS, and ElastiCache on a visual weekly grid β€” no code, no EventBridge config, no Lambda to maintain.

Try ServerScheduler Free β†’
No credit card required Β· Works with EC2, RDS, and ElastiCache
SS
ServerScheduler Staff Β· 5 min read

Most AWS accounts accumulate idle resources over time. Instances that were spun up for a project and forgotten, EBS volumes left behind when an instance was terminated, Elastic IP addresses that aren't attached to anything β€” each of these is a line on your bill with nothing to show for it. The checklist above helps you systematically find them.

Checklist being completed representing an AWS audit
A structured audit approach is more reliable than ad-hoc reviews for finding all the idle resources in an AWS account.

What Counts as an Idle Resource

An idle resource is one that is incurring cost but delivering no value. That can mean different things depending on the resource type. An EC2 instance with under 5% CPU utilisation over 14 days is idle in a performance sense β€” you're paying for capacity that isn't being used. An unattached EBS volume is idle in an existence sense β€” it exists but isn't connected to anything. An Elastic IP not associated with a running instance is idle simply because AWS charges for unattached reserved addresses.

The distinction matters because the remediation is different. A low-utilisation instance might be right-sized, scheduled, or terminated. An unattached EBS volume can usually be snapshotted and deleted. Understanding which category a resource falls into helps you prioritise correctly and avoid accidentally deleting something that's idle for a legitimate reason (a cold standby, a disaster recovery volume, a dev environment that's only used occasionally).

Tag before you delete

Before deleting any idle resource, check its tags and ask the team who owns it. A resource with no tags and no apparent owner is usually safe to terminate β€” but establish that before acting. Implementing a tagging policy prevents this problem in new resources.

Auditing Compute Resources

EC2 instances are the first place to look. In CloudWatch, filter for instances with average CPU below 10% over a 14-day period. Cross-reference with network traffic β€” an instance with low CPU and near-zero network traffic is almost certainly not serving any real workload. Also look for stopped instances that have been stopped for more than 30 days β€” they're not incurring compute charges but they do have attached EBS volumes that are costing money.

For RDS, look at database connection counts. A database with zero connections for more than a week is idle. Also check for RDS instances that are still running in dev or staging environments β€” these are candidates for scheduling to stop overnight rather than running around the clock. The RDS scheduled start/stop guide explains how to implement this without data loss.

Auditing Storage Resources

EBS volumes are charged based on provisioned capacity, not actual usage. A 500 GB gp3 volume attached to a stopped instance costs the same as one attached to a running instance processing heavy workloads. Start by identifying all unattached volumes β€” these are volumes with no instance attached β€” and decide whether they contain data that's still needed. ServerScheduler's EBS volume auditing surfaces these automatically.

Also audit your EBS snapshot inventory. Snapshots of deleted volumes, old AMI snapshots from deprecated deployments, and snapshots created by automated backup policies that run more frequently than needed all accumulate cost over time. AWS does not automatically delete snapshots when their source volume is deleted β€” you need to manage this explicitly.

Resource TypeIdle SignalTypical Monthly CostAction
Unattached EBS volumeNo instance attached$0.08–$0.10/GBSnapshot and delete
Old EBS snapshots> 90 days, source deleted$0.05/GBDelete old snapshots
Stopped EC2 + EBSStopped > 30 days$varies (storage only)Terminate or restart
Elastic IP (unattached)Not associated$0.005/hourRelease immediately
NAT Gateway (no traffic)Near-zero bytes processed$0.045/hourDelete if unused

Auditing Networking Resources

Elastic IP addresses that aren't associated with a running instance are charged at $0.005 per hour β€” about $3.60 per month each. It's a small cost individually but accounts often accumulate dozens of them over time. Finding and releasing unattached Elastic IPs is a quick win. In the EC2 console, navigate to Elastic IPs and filter for addresses with no associated instance.

Load balancers β€” both Application Load Balancers and Network Load Balancers β€” are charged per hour regardless of traffic. An ALB processing no traffic (zero healthy targets) is still costing around $16 per month. Audit your load balancers for target groups with no healthy instances and consider whether the load balancer itself can be deleted.

Turning Audit Findings Into Action

An audit is only valuable if its findings lead to action. For each idle resource identified, assign an owner, a recommended action, and a timeline. The most common blocker is uncertainty β€” nobody knows if a resource is safe to terminate. Establish a process: tag the resource with a "pending-review" tag, give the owner two weeks to respond, and terminate if there's no response.

For non-production compute resources, the quickest path from audit finding to action is scheduling rather than termination. If a dev environment is running around the clock but only used during working hours, scheduling it to stop overnight captures 60% of the cost without the risk of terminating something a developer still needs. Use the schedule generator to create the appropriate schedule and the no-code automation guide to understand your implementation options.