AWS Cost Optimization Checklist: 50 Items Every Team Should Check
This checklist covers 50 specific AWS cost optimization checks you can run today. Every item includes an AWS CLI command, what to look for, and the typical savings. These checks are derived from real-world optimization patterns across hundreds of AWS accounts.
CostPatrol automates most of these checks. If you’d rather not run 50 CLI commands manually, a free scan covers the majority of these items in minutes.
EC2 (10 Items)
1. Find idle EC2 instances
Instances with CPU utilization consistently below 5% over 7 days are likely idle and should be terminated or stopped.
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-0abc123def456 \
--start-time $(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 86400 --statistics Maximum \
--query 'Datapoints[].Maximum'
Rule ID: EC2-O002 | Savings: 100% of instance cost
2. Identify previous-generation instance types
Instances running m4, c4, r4, t2, m3, c3, or r3 families are 20-40% more expensive per unit of compute than current-generation equivalents.
aws ec2 describe-instances \
--query 'Reservations[].Instances[?State.Name==`running`].{ID:InstanceId,Type:InstanceType}' \
--output table | grep -E 'm[34]\.|c[34]\.|r[34]\.|t2\.'
Rule ID: EC2-O001 | Savings: 10-30% per instance
3. Find stopped instances with attached EBS volumes
Stopped instances don’t incur compute charges, but their EBS volumes keep billing. A stopped m5.xlarge with 500 GB gp3 costs $40/month in storage alone.
aws ec2 describe-instances \
--filters Name=instance-state-name,Values=stopped \
--query 'Reservations[].Instances[].{ID:InstanceId,Volumes:BlockDeviceMappings[].Ebs.VolumeId}' \
--output table
Rule ID: OPT-022 | Savings: 100% of attached EBS cost
4. Check for on-demand instances without Savings Plans coverage
Instances running 500+ hours/month on-demand are candidates for Savings Plans (30-60% discount).
aws ce get-savings-plans-coverage \
--time-period Start=2026-02-01,End=2026-03-01 \
--granularity MONTHLY \
--query 'SavingsPlansCoverages[].{Coverage:Coverage.CoveragePercentage}'
Rule ID: EC2-004 | Savings: 30-60% on covered instances
5. Identify oversized EC2 instances
Instances with peak CPU below 30% and peak memory below 40% over 14 days can likely be downsized.
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-0abc123def456 \
--start-time $(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 604800 --statistics Maximum \
--query 'Datapoints[].Maximum'
Rule ID: OPT-023 | Savings: 30-60% by downsizing
6. Find instances not using Graviton (ARM)
Graviton instances (m7g, c7g, r7g) offer 20-40% better price-performance than x86 equivalents for most workloads.
aws ec2 describe-instances \
--filters Name=instance-state-name,Values=running \
--query 'Reservations[].Instances[?!contains(InstanceType, `g.`) && !contains(InstanceType, `gd.`)].{ID:InstanceId,Type:InstanceType}' \
--output table
Rule ID: EC2-O003 | Savings: 20-40% per instance
7. Check for instances with Detailed Monitoring enabled unnecessarily
Detailed Monitoring ($2.10/instance/month) provides 1-minute metrics. Most workloads are fine with 5-minute (free) metrics.
aws ec2 describe-instances \
--filters Name=monitoring-state,Values=enabled \
--query 'Reservations[].Instances[].{ID:InstanceId,Type:InstanceType}' \
--output table
Rule ID: OPT-021 | Savings: $2.10/instance/month
8. Find instances running in non-production during off-hours
Dev/staging instances running 24/7 when they’re only needed during business hours waste 65-70% of compute costs.
aws ec2 describe-instances \
--filters Name=instance-state-name,Values=running \
--query 'Reservations[].Instances[].{ID:InstanceId,Type:InstanceType,Tags:Tags[?Key==`Environment`].Value|[0]}' \
--output table | grep -iE 'dev|staging|test'
Rule ID: EC2-O004 | Savings: 65-70% with scheduling
9. Check for orphaned Elastic IPs
Unattached Elastic IPs cost $3.60/month each since the AWS public IPv4 pricing change.
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==null].{IP:PublicIp,AllocID:AllocationId}' \
--output table
Rule ID: EIP-O001 | Savings: $3.60/IP/month
10. Find EIPs on stopped instances
Elastic IPs attached to stopped instances also incur the idle charge.
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId!=null].{IP:PublicIp,InstanceId:InstanceId}' \
--output text | while read ip inst; do
state=$(aws ec2 describe-instances --instance-ids $inst \
--query 'Reservations[0].Instances[0].State.Name' --output text 2>/dev/null)
[ "$state" = "stopped" ] && echo "EIP $ip on stopped instance $inst"
done
Rule ID: EIP-O002 | Savings: $3.60/IP/month
RDS (8 Items)
11. Find idle RDS instances (zero connections)
RDS instances with zero database connections for 14+ days are idle and should be snapshotted and deleted.
aws cloudwatch get-metric-statistics \
--namespace AWS/RDS \
--metric-name DatabaseConnections \
--dimensions Name=DBInstanceIdentifier,Value=my-database \
--start-time $(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 86400 --statistics Maximum \
--query 'Datapoints[].Maximum'
Rule ID: RDS-O001 | Savings: 100% of instance cost
12. Check for Multi-AZ on non-production databases
Multi-AZ doubles your RDS cost. Non-production databases rarely need it.
aws rds describe-db-instances \
--query 'DBInstances[?MultiAZ==`true`].{ID:DBInstanceIdentifier,Class:DBInstanceClass,Tags:TagList[?Key==`Environment`].Value|[0]}' \
--output table
Rule ID: RDS-O004 | Savings: 50% of instance cost
13. Find RDS instances without Reserved Instance coverage
Production RDS instances running on-demand for 12+ months should have RI coverage.
aws rds describe-reserved-db-instances \
--query 'ReservedDBInstances[?State==`active`].{Class:DBInstanceClass,Count:DBInstanceCount,End:StartTime}' \
--output table
Savings: 30-40% with 1-year no-upfront RI
14. Identify oversized RDS instances
Check CPU and connection metrics to find databases that could run on a smaller instance class.
aws cloudwatch get-metric-statistics \
--namespace AWS/RDS \
--metric-name CPUUtilization \
--dimensions Name=DBInstanceIdentifier,Value=my-database \
--start-time $(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 86400 --statistics Maximum \
--query 'Datapoints[].Maximum'
Rule ID: RDS-O007 | Savings: 30-60% by downsizing
15. Find unused RDS read replicas
Read replicas with zero read traffic are pure waste.
aws rds describe-db-instances \
--query 'DBInstances[?ReadReplicaSourceDBInstanceIdentifier!=null].{Replica:DBInstanceIdentifier,Source:ReadReplicaSourceDBInstanceIdentifier,Class:DBInstanceClass}' \
--output table
Rule ID: OPT-024 | Savings: 100% of replica cost
16. Check RDS backup retention overage
RDS provides free backup storage equal to your provisioned database storage. Backup exceeding this amount is charged at $0.095/GB/month.
aws rds describe-db-instances \
--query 'DBInstances[].{ID:DBInstanceIdentifier,Storage:AllocatedStorage,BackupRetention:BackupRetentionPeriod}' \
--output table
Rule ID: RDS-O003 | Savings: 20-100% of backup overage
17. Find RDS instances on GP2 storage
GP3 provides 20% cost savings over GP2 with better baseline performance (3,000 IOPS vs 100 IOPS/GB).
aws rds describe-db-instances \
--query 'DBInstances[?StorageType==`gp2`].{ID:DBInstanceIdentifier,Size:AllocatedStorage,Type:StorageType}' \
--output table
Rule ID: RDS-O006 | Savings: 20% on storage costs
18. Detect RDS cluster sprawl
Multiple RDS clusters in the same account with similar engines often indicate forgotten test databases or duplicated environments.
aws rds describe-db-instances \
--query 'DBInstances[].{ID:DBInstanceIdentifier,Engine:Engine,Class:DBInstanceClass,Status:DBInstanceStatus}' \
--output table
Rule ID: RDS-O005 | Savings: 70-100% per unnecessary cluster
Lambda (6 Items)
19. Find unused Lambda functions (zero invocations)
Functions with zero invocations in 30 days should be deleted.
# Check invocations for a specific function
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Invocations \
--dimensions Name=FunctionName,Value=my-function \
--start-time $(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 2592000 --statistics Sum \
--query 'Datapoints[0].Sum'
Rule ID: LAM-O003 | Savings: 100% (small per function, significant at scale)
20. Identify over-provisioned Lambda memory
Functions using less than 50% of allocated memory are over-provisioned.
aws logs filter-log-events \
--log-group-name /aws/lambda/my-function \
--filter-pattern "REPORT" \
--start-time $(date -u -d '7 days ago' +%s)000 \
--query 'events[].message' --output text | \
grep -oP 'Memory Size: \K[0-9]+|Max Memory Used: \K[0-9]+'
Rule ID: LAM-O002 | Savings: 10-40% per function
21. Find x86 Lambda functions eligible for ARM64
ARM64 (Graviton2) provides 20% cost savings for compatible runtimes.
aws lambda list-functions \
--query 'Functions[?Architectures[0]!=`arm64` && (Runtime==`nodejs20.x` || Runtime==`python3.12` || Runtime==`python3.13`)].{Name:FunctionName,Runtime:Runtime}' \
--output table
Rule ID: LAM-O001 | Savings: 20% per function
22. Check for Lambda functions with excessive timeouts
Functions with 900-second timeouts that average under 10 seconds of execution are misconfigured.
aws lambda list-functions \
--query 'Functions[?Timeout>`300`].{Name:FunctionName,Timeout:Timeout}' \
--output table
Rule ID: LAM-O004 | Savings: Risk reduction (prevents runaway billing)
23. Detect Lambda cost spikes (duration anomalies)
Compare recent Lambda costs against the 14-day baseline to catch spikes.
aws ce get-cost-and-usage \
--time-period Start=2026-03-12,End=2026-03-13 \
--granularity DAILY \
--metrics UnblendedCost \
--filter '{"Dimensions":{"Key":"SERVICE","Values":["AWS Lambda"]}}' \
--query 'ResultsByTime[0].Total.UnblendedCost.Amount'
Rule ID: LAM-A001 | Trigger: 200% deviation from 14-day rolling average
24. Check for Lambda functions on deprecated runtimes
Functions on end-of-life runtimes (Node.js 16.x, Python 3.8) don’t get security patches and may have performance regressions.
aws lambda list-functions \
--query 'Functions[?Runtime==`nodejs16.x` || Runtime==`python3.8` || Runtime==`python3.9`].{Name:FunctionName,Runtime:Runtime}' \
--output table
Savings: Indirect (security risk reduction + potential performance improvement)
EBS (6 Items)
25. Find unattached EBS volumes
Unattached volumes in “available” state are pure waste.
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[].{ID:VolumeId,Size:Size,Type:VolumeType}' \
--output table
Rule ID: EBS-O002 | Savings: 100% of volume cost
26. Identify GP2 volumes for GP3 migration
GP3 is 20% cheaper than GP2 with better baseline performance.
aws ec2 describe-volumes \
--filters Name=volume-type,Values=gp2 \
--query 'Volumes[].{ID:VolumeId,Size:Size,IOPS:Iops}' \
--output table
Rule ID: EBS-O001 | Savings: 20% per volume
27. Check for over-provisioned GP3 IOPS/throughput
GP3 includes 3,000 IOPS and 125 MB/s free. Provisioned IOPS beyond this cost extra. Check if you’re actually using them.
aws cloudwatch get-metric-statistics \
--namespace AWS/EBS \
--metric-name VolumeReadOps \
--dimensions Name=VolumeId,Value=vol-0abc123 \
--start-time $(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 86400 --statistics Maximum
Rule ID: EBS-O005 | Savings: 10-70% of performance add-on cost
28. Find idle Fast Snapshot Restore (FSR)
FSR charges $0.75/AZ/hour for each snapshot-AZ combination. Unused FSR adds up fast.
aws ec2 describe-fast-snapshot-restores \
--query 'FastSnapshotRestores[?State==`enabled`].{Snapshot:SnapshotId,AZ:AvailabilityZone}' \
--output table
Rule ID: EBS-O004 | Savings: $0.75/AZ/hour ($540/AZ/month)
29. Check for stale EBS snapshots
Snapshots from deleted volumes or old backups that are no longer needed.
aws ec2 describe-snapshots --owner-ids self \
--query 'sort_by(Snapshots, &StartTime)[:20].{ID:SnapshotId,Size:VolumeSize,Date:StartTime,Desc:Description}' \
--output table
Rule ID: EBS-O006 | Savings: $0.05/GB/month
30. Find EBS snapshot archiving opportunities
Snapshots accessed less than once per 90 days can be moved to archive tier for 75% savings.
aws ec2 describe-snapshots --owner-ids self \
--query 'Snapshots[?StorageTier!=`archive`].{ID:SnapshotId,Size:VolumeSize,Created:StartTime}' \
--output table
Rule ID: EBS-O003 | Savings: 75% on snapshot storage
S3 (5 Items)
31. Find buckets without lifecycle policies
Large buckets without lifecycle rules accumulate data indefinitely.
for bucket in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
rules=$(aws s3api get-bucket-lifecycle-configuration --bucket $bucket 2>/dev/null | \
grep -c '"ID"' || echo 0)
[ "$rules" = "0" ] && echo "No lifecycle: $bucket"
done
Rule ID: S3-O001 | Savings: 40-70% with intelligent tiering
32. Check for incomplete multipart uploads
Failed multipart uploads leave orphaned parts that incur storage charges indefinitely.
aws s3api list-multipart-uploads --bucket my-bucket \
--query 'Uploads[].{Key:Key,Initiated:Initiated}' \
--output table
Rule ID: OPT-017 | Savings: Varies (can be substantial for large uploads)
33. Find S3 versioned buckets with noncurrent version bloat
Versioned buckets without noncurrent version expiration accumulate old versions forever.
aws s3api get-bucket-versioning --bucket my-bucket
# If enabled, check for noncurrent version lifecycle rules
aws s3api get-bucket-lifecycle-configuration --bucket my-bucket \
--query 'Rules[?NoncurrentVersionExpiration!=null]'
Rule ID: S3-O003 | Savings: 20-80% by expiring old versions
34. Check for Standard storage on infrequently accessed data
Buckets with low request rates on Standard storage should use S3 Intelligent-Tiering or S3-IA.
aws cloudwatch get-metric-statistics \
--namespace AWS/S3 \
--metric-name AllRequests \
--dimensions Name=BucketName,Value=my-bucket Name=FilterId,Value=EntireBucket \
--start-time $(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 2592000 --statistics Sum
Rule ID: S3-003 | Savings: 40-68% with Intelligent-Tiering
35. Find empty S3 buckets
Empty buckets incur no storage costs but add operational clutter. Clean them up.
for bucket in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
count=$(aws s3api list-objects-v2 --bucket $bucket --max-items 1 \
--query 'KeyCount' --output text 2>/dev/null)
[ "$count" = "0" ] && echo "Empty: $bucket"
done
Rule ID: S3-O004 | Savings: Minimal (operational hygiene)
DynamoDB (5 Items)
36. Find over-provisioned DynamoDB tables
Tables using less than 30% of provisioned RCU/WCU are over-provisioned.
aws dynamodb list-tables --query 'TableNames' --output text | tr '\t' '\n' | while read table; do
mode=$(aws dynamodb describe-table --table-name $table \
--query 'Table.BillingModeSummary.BillingMode' --output text 2>/dev/null)
[ "$mode" = "PROVISIONED" ] && echo "Provisioned: $table"
done
Rule ID: DDB-001 | Savings: 30-70% by right-sizing or switching to on-demand
37. Identify on-demand tables suitable for provisioned capacity
Tables with steady, predictable traffic patterns are cheaper on provisioned with auto-scaling.
aws dynamodb describe-table --table-name my-table \
--query 'Table.{Name:TableName,Mode:BillingModeSummary.BillingMode,Items:ItemCount,Size:TableSizeBytes}'
Rule ID: DDB-002 | Savings: 20-40% for steady workloads
38. Find unused DynamoDB tables
Tables with zero reads and writes for 14+ days are candidates for deletion.
aws cloudwatch get-metric-statistics \
--namespace AWS/DynamoDB \
--metric-name ConsumedReadCapacityUnits \
--dimensions Name=TableName,Value=my-table \
--start-time $(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 1209600 --statistics Sum \
--query 'Datapoints[0].Sum'
Rule ID: DDB-003 | Savings: 100% of table cost
39. Find unused Global Secondary Indexes
GSIs that are never queried consume write capacity and storage.
aws dynamodb describe-table --table-name my-table \
--query 'Table.GlobalSecondaryIndexes[].{Name:IndexName,Status:IndexStatus,Items:ItemCount}'
Rule ID: DDB-004 | Savings: 100% of GSI cost
40. Check for Standard-IA table class opportunities
Tables with storage costs exceeding 2x their read/write costs should use Standard-IA (60% cheaper storage, 25% more expensive reads/writes).
aws dynamodb describe-table --table-name my-table \
--query 'Table.{Name:TableName,Class:TableClassSummary.TableClass,SizeGB:TableSizeBytes}'
Rule ID: DDB-005 | Savings: 40-60% for storage-heavy tables
CloudWatch (5 Items)
41. Find log groups without retention policies
Log groups without retention accumulate data forever at $0.03/GB/month.
aws logs describe-log-groups \
--query 'logGroups[?retentionInDays==null].{Name:logGroupName,SizeBytes:storedBytes}' \
--output table
Rule ID: CWL-O001 | Savings: 50-80% of log storage costs
42. Identify the most expensive log groups
Find which log groups are consuming the most storage and ingestion.
aws logs describe-log-groups \
--query 'sort_by(logGroups, &storedBytes)[-10:].{Name:logGroupName,StoredGB:storedBytes}' \
--output table
Savings: Targeted retention policies on top consumers
43. Check for duplicate CloudTrail trails
Multiple CloudTrail trails in the same region deliver duplicate logs and charges.
aws cloudtrail describe-trails \
--query 'trailList[].{Name:Name,Region:HomeRegion,IsMultiRegion:IsMultiRegionTrail,IsOrgTrail:IsOrganizationTrail}' \
--output table
Rule ID: CT-O001 | Savings: 100% per duplicate trail
44. Find orphaned CloudWatch alarms
Alarms referencing deleted resources cost $0.10/alarm/month and create operational noise.
aws cloudwatch describe-alarms \
--state-value INSUFFICIENT_DATA \
--query 'MetricAlarms[].{Name:AlarmName,Metric:MetricName,Namespace:Namespace}' \
--output table
Rule ID: CW-O002 | Savings: $0.10/alarm/month + noise reduction
45. Check for Lambda dual-write logging
Lambda functions that send logs to both CloudWatch and an external APM (Datadog, New Relic) are double-paying for log ingestion.
aws logs describe-log-groups \
--log-group-name-prefix /aws/lambda/ \
--query 'logGroups[?subscriptionFilters!=null].{Name:logGroupName,Subscriptions:subscriptionFilters}' \
--output table 2>/dev/null || echo "Check subscription filters manually"
Rule ID: CWL-O002 | Savings: 100% of CW ingestion cost if external APM captures the same data
NAT Gateway (3 Items)
46. Identify NAT Gateway traffic eligible for VPC Endpoints
S3 and DynamoDB Gateway Endpoints are free and eliminate NAT Gateway data processing charges for those services.
# Check if S3 gateway endpoint exists
aws ec2 describe-vpc-endpoints \
--filters Name=service-name,Values=com.amazonaws.*.s3 Name=vpc-endpoint-type,Values=Gateway \
--query 'VpcEndpoints[].{VpcId:VpcId,State:State}' \
--output table
Rule ID: NAT-O001 | Savings: 50-90% of NAT data processing costs
47. Check NAT Gateway data processing costs
NAT Gateway charges $0.045/GB of data processed. High-traffic applications can rack up significant costs.
aws ce get-cost-and-usage \
--time-period Start=2026-02-01,End=2026-03-01 \
--granularity MONTHLY \
--metrics UnblendedCost \
--filter '{"Dimensions":{"Key":"SERVICE","Values":["Amazon Virtual Private Cloud"]}}' \
--query 'ResultsByTime[0].Total.UnblendedCost.Amount'
Savings: Varies based on traffic patterns
48. Find redundant NAT Gateways
Non-production VPCs with NAT Gateways in multiple AZs may only need one.
aws ec2 describe-nat-gateways \
--filter Name=state,Values=available \
--query 'NatGateways[].{ID:NatGatewayId,VpcId:VpcId,SubnetId:SubnetId,AZ:SubnetId}' \
--output table
Rule ID: NAT-O002 | Savings: ~50% by consolidating to single AZ (non-production only)
General (2 Items)
49. Check for AWS Config in unnecessary regions
AWS Config charges per configuration item recorded. If enabled in all 20+ regions but you only use 2-3, you’re paying for configuration recordings in unused regions.
for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
status=$(aws configservice describe-configuration-recorder-status \
--region $region --query 'ConfigurationRecordersStatus[0].recording' \
--output text 2>/dev/null)
[ "$status" = "True" ] && echo "Config active in: $region"
done
Rule ID: CFG-O001 | Savings: $100-$1,000+/month
50. Review overall Savings Plans utilization
Low Savings Plans utilization means you’re over-committed. High utilization with significant on-demand spend means you could buy more.
aws ce get-savings-plans-utilization \
--time-period Start=2026-02-01,End=2026-03-01 \
--query 'Total.{Utilization:UtilizationPercentage,Used:UsedCommitment,Unused:UnusedCommitment}'
Target: 90-95% utilization. Below 85% suggests over-commitment. 100% with on-demand spend suggests room for additional coverage.
How to Use This Checklist
Running all 50 checks manually takes hours. Here’s a practical approach:
Quick wins (30 minutes): Run items 9, 25, 26, 31, and 41 first. These are the easiest to check and often yield immediate savings (orphaned EIPs, unattached volumes, GP2-to-GP3, missing lifecycle policies, missing log retention).
Deep dive (2-3 hours): Work through EC2 (items 1-5), RDS (items 11-12), and DynamoDB (items 36-40) to find idle and oversized resources.
Recurring review (monthly): Re-run items 4, 19, 38, 46, and 50 monthly to catch new idle resources and verify commitment coverage.
Or automate it: CostPatrol runs the equivalent of this entire checklist in a single scan. It connects to your AWS account via a read-only CloudFormation role, evaluates 60+ detection rules across all services, and produces a prioritized report with estimated savings for each finding. The free scan covers most items on this list.
Expected Savings by Category
| Category | Typical Monthly Savings | Effort Level |
|---|---|---|
| EC2 idle/oversized | $200-$2,000 | Medium |
| EBS cleanup | $50-$500 | Low |
| RDS right-sizing | $100-$1,000 | Medium |
| Lambda optimization | $50-$500 | Low |
| S3 lifecycle policies | $100-$1,000 | Low |
| DynamoDB right-sizing | $50-$500 | Medium |
| CloudWatch log retention | $50-$300 | Low |
| NAT Gateway optimization | $100-$2,000 | Medium |
| Commitment coverage | $500-$5,000 | Low |
For a team spending $50,000/month on AWS, working through this checklist typically identifies $5,000-$15,000 in monthly savings (10-30%). The exact amount depends on how much optimization has already been done.