AWS cost & waste audit
Summary
The scan ran 12 read-only checks and found 20 items worth reviewing.
The largest single opportunity is Unattached EBS volume (vol-61b83803386704d04,
about $208.10/month). 1 fix is low-risk and can be done this week. Nothing was changed in the account: every fix below is a command for you to review and run.
Top opportunities
| Priority | What | Resource | Region | Saving / month |
|---|---|---|---|---|
| high | Unattached EBS volume 100 GiB io1, 3,000 provisioned IOPS (perf-test), not attached to any instance |
vol-61b83803386704d04 | ap-south-1 | $208.10 |
| high | Unattached EBS volume 1000 GiB gp2 (forgotten-ml-dataset), not attached to any instance |
vol-d5ad5396436612ce5 | us-east-1 | $100.00 |
| high | Instance looks idle t2.large (legacy-reporting): average CPU 2.7%, peak 7.5% over 13 days |
i-1222cd8a252d8edc1 | ap-south-1 | $72.42 |
| high | Database has had no connections db.t3.medium postgres 100 GiB: 0 connections in 13 days |
analytics-copy | ap-south-1 | $63.96 |
| medium | Unattached EBS volume 500 GiB gp3 (migration-temp), not attached to any instance |
vol-8f632793cccc4eedc | ap-south-1 | $45.60 |
| medium | NAT gateway barely used moved 0.51 GB (in + out) in 14 days (VPC vpc-0527c2ef9d79f9cd3) |
nat-95a718f3d35a4e2d8 | ap-south-1 | $40.88 |
| medium | Unattached EBS volume 200 GiB gp2 (old-jenkins-data), not attached to any instance |
vol-021fe5f4debe2e734 | ap-south-1 | $22.80 |
| medium | Load balancer has no targets application load balancer with 1 target group(s) and 0 registered targets |
old-promo-alb | ap-south-1 | $16.43 |
| medium | Instance stopped for a long time t3.large (old-build-agent) stopped for 75 days, 1 volume(s) attached |
i-8891acb15ab0eabd4 | ap-south-1 | $13.68 |
| medium | Old snapshot not used by any AMI 410 days old, from a 200 GiB volume. pre-migration backup 2025 |
snap-623da09cda0b9887a | ap-south-1 | $10.00 |
Findings by check
EBS-001 · Unattached EBS volumes (4)
Volumes in the 'available' state are billed every month but serve nothing - often left behind by deleted instances.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
vol-61b83803386704d04 | 100 GiB io1, 3,000 provisioned IOPS (perf-test), not attached to any instance | Confirm with the owner, snapshot it (cheap insurance), then delete the volume. The command waits for the snapshot to finish (up to 10 minutes); if the wait times out, delete the volume once the snapshot shows 'completed'. Delete the snapshot too after a month or two if nobody needs it.Command (risk: medium)SNAP=$(aws ec2 create-snapshot --region ap-south-1 --volume-id vol-61b83803386704d04 --description 'billdoctor: backup before delete' --query SnapshotId --output text) && aws ec2 wait snapshot-completed --region ap-south-1 --snapshot-ids "$SNAP" && aws ec2 delete-volume --region ap-south-1 --volume-id vol-61b83803386704d04 |
ap-south-1 | $208.10 | $208.10 |
vol-d5ad5396436612ce5 | 1000 GiB gp2 (forgotten-ml-dataset), not attached to any instance | Confirm with the owner, snapshot it (cheap insurance), then delete the volume. The command waits for the snapshot to finish (up to 10 minutes); if the wait times out, delete the volume once the snapshot shows 'completed'. Delete the snapshot too after a month or two if nobody needs it.Command (risk: medium)SNAP=$(aws ec2 create-snapshot --region us-east-1 --volume-id vol-d5ad5396436612ce5 --description 'billdoctor: backup before delete' --query SnapshotId --output text) && aws ec2 wait snapshot-completed --region us-east-1 --snapshot-ids "$SNAP" && aws ec2 delete-volume --region us-east-1 --volume-id vol-d5ad5396436612ce5 |
us-east-1 | $100.00 | $100.00 |
vol-8f632793cccc4eedc | 500 GiB gp3 (migration-temp), not attached to any instance | Confirm with the owner, snapshot it (cheap insurance), then delete the volume. The command waits for the snapshot to finish (up to 10 minutes); if the wait times out, delete the volume once the snapshot shows 'completed'. Delete the snapshot too after a month or two if nobody needs it.Command (risk: medium)SNAP=$(aws ec2 create-snapshot --region ap-south-1 --volume-id vol-8f632793cccc4eedc --description 'billdoctor: backup before delete' --query SnapshotId --output text) && aws ec2 wait snapshot-completed --region ap-south-1 --snapshot-ids "$SNAP" && aws ec2 delete-volume --region ap-south-1 --volume-id vol-8f632793cccc4eedc |
ap-south-1 | $45.60 | $45.60 |
vol-021fe5f4debe2e734 | 200 GiB gp2 (old-jenkins-data), not attached to any instance | Confirm with the owner, snapshot it (cheap insurance), then delete the volume. The command waits for the snapshot to finish (up to 10 minutes); if the wait times out, delete the volume once the snapshot shows 'completed'. Delete the snapshot too after a month or two if nobody needs it.Command (risk: medium)SNAP=$(aws ec2 create-snapshot --region ap-south-1 --volume-id vol-021fe5f4debe2e734 --description 'billdoctor: backup before delete' --query SnapshotId --output text) && aws ec2 wait snapshot-completed --region ap-south-1 --snapshot-ids "$SNAP" && aws ec2 delete-volume --region ap-south-1 --volume-id vol-021fe5f4debe2e734 |
ap-south-1 | $22.80 | $22.80 |
EBS-002 · gp2 volumes that should be gp3 (1)
gp3 is about 20% cheaper than gp2 per GiB and has a 3,000 IOPS baseline regardless of size. The change is online.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
vol-7128ec531f6695580 | 100 GiB gp2 attached to i-1222cd8a252d8edc1 | Modify the volume to gp3 (no downtime).Command (risk: low)aws ec2 modify-volume --region ap-south-1 --volume-id vol-7128ec531f6695580 --volume-type gp3 |
ap-south-1 | $11.40 | $2.28 |
EBS-003 · Old EBS snapshots (2)
Snapshots older than the retention you need, and not used by an AMI, keep costing storage forever.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
snap-623da09cda0b9887a | 410 days old, from a 200 GiB volume. pre-migration backup 2025 | Keep only what your retention policy needs (e.g. 30-90 days) - set up Amazon Data Lifecycle Manager instead of manual snapshots. Saving shown is an upper bound (snapshots are incremental).Command (risk: medium)aws ec2 delete-snapshot --region ap-south-1 --snapshot-id snap-623da09cda0b9887a |
ap-south-1 | $10.00 | $10.00 |
snap-d93c9fe1018b66a97 | 220 days old, from a 200 GiB volume. manual backup before upgrade | Keep only what your retention policy needs (e.g. 30-90 days) - set up Amazon Data Lifecycle Manager instead of manual snapshots. Saving shown is an upper bound (snapshots are incremental).Command (risk: medium)aws ec2 delete-snapshot --region ap-south-1 --snapshot-id snap-d93c9fe1018b66a97 |
ap-south-1 | $10.00 | $10.00 |
EC2-001 · Unassociated Elastic IPs (2)
Since February 2024 every public IPv4 address costs $0.005/hour; an Elastic IP attached to nothing is pure waste.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
eipalloc-0076a7c9852db02a4 | 127.184.176.136 is not attached to anything | Release it unless an allow-list somewhere depends on this exact IP.Command (risk: medium)aws ec2 release-address --region ap-south-1 --allocation-id eipalloc-0076a7c9852db02a4 |
ap-south-1 | $3.65 | $3.65 |
eipalloc-689a9541a3f86cde3 | 127.43.70.107 is not attached to anything | Release it unless an allow-list somewhere depends on this exact IP.Command (risk: medium)aws ec2 release-address --region us-east-1 --allocation-id eipalloc-689a9541a3f86cde3 |
us-east-1 | $3.65 | $3.65 |
EC2-002 · Long-stopped instances still paying for disks (1)
A stopped instance costs nothing for compute, but its EBS volumes are billed in full.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
i-8891acb15ab0eabd4 | t3.large (old-build-agent) stopped for 75 days, 1 volume(s) attached | If it is not coming back: create an AMI (keeps a restorable copy), then terminate the instance. Volumes with 'delete on termination' turned off survive the termination; delete them too (the AMI holds a copy) or they show up as unattached volumes next time.Command (risk: high)IMG=$(aws ec2 create-image --region ap-south-1 --instance-id i-8891acb15ab0eabd4 --name billdoctor-i-8891acb15ab0eabd4 --query ImageId --output text) && aws ec2 wait image-available --region ap-south-1 --image-ids "$IMG" && aws ec2 terminate-instances --region ap-south-1 --instance-ids i-8891acb15ab0eabd4 |
ap-south-1 | $13.68 | $13.68 |
EC2-003 · Idle running instances (1)
Instances averaging under 5% CPU (and never above 20%) for two weeks are usually forgotten or oversized.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
i-1222cd8a252d8edc1 | t2.large (legacy-reporting): average CPU 2.7%, peak 7.5% over 13 days | Confirm with the owner. Then stop it, schedule it (office hours only), or downsize. Saving shown assumes it can be stopped.Command (risk: high)aws ec2 stop-instances --region ap-south-1 --instance-ids i-1222cd8a252d8edc1 |
ap-south-1 | $72.42 | $72.42 |
EC2-004 · Previous-generation instance types (2)
Older families (t2, m4, c4, r4...) cost more for less performance than their current replacements.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
i-1222cd8a252d8edc1 | t2.large -> t3.large | Change the instance type to t3.large during a maintenance window (stop, change type, start). Newer types run on Nitro: very old AMIs need the ENA and NVMe drivers (current images have them). A stop/start changes the public IP unless it is an Elastic IP. Graviton (t4g/m7g) is cheaper still if the software runs on ARM.Command (risk: medium)aws ec2 stop-instances --region ap-south-1 --instance-ids i-1222cd8a252d8edc1 && aws ec2 wait instance-stopped --region ap-south-1 --instance-ids i-1222cd8a252d8edc1 && aws ec2 modify-instance-attribute --region ap-south-1 --instance-id i-1222cd8a252d8edc1 --instance-type Value=t3.large && aws ec2 start-instances --region ap-south-1 --instance-ids i-1222cd8a252d8edc1 |
ap-south-1 | $72.42 | $7.01 |
i-a6d48966bc3c7556b | m4.large -> m6i.large | Change the instance type to m6i.large during a maintenance window (stop, change type, start). Newer types run on Nitro: very old AMIs need the ENA and NVMe drivers (current images have them). A stop/start changes the public IP unless it is an Elastic IP. Graviton (t4g/m7g) is cheaper still if the software runs on ARM.Command (risk: medium)aws ec2 stop-instances --region ap-south-1 --instance-ids i-a6d48966bc3c7556b && aws ec2 wait instance-stopped --region ap-south-1 --instance-ids i-a6d48966bc3c7556b && aws ec2 modify-instance-attribute --region ap-south-1 --instance-id i-a6d48966bc3c7556b --instance-type Value=m6i.large && aws ec2 start-instances --region ap-south-1 --instance-ids i-a6d48966bc3c7556b |
ap-south-1 | $73.00 | $2.92 |
ELB-001 · Load balancers with no targets (1)
A load balancer is billed by the hour even when nothing is registered behind it.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
old-promo-alb | application load balancer with 1 target group(s) and 0 registered targets | Delete it if the service is gone. First check DNS records pointing at it, and its listener rules: a load balancer without targets can still serve redirects or fixed responses.Command (risk: medium)aws elbv2 delete-load-balancer --region ap-south-1 --load-balancer-arn arn:aws:elasticloadbalancing:ap-south-1:123456789012:loadbalancer/app/old-promo-alb/3eacd4314c9ad45d |
ap-south-1 | $16.43 | $16.43 |
LOGS-001 · Log groups that never expire (2)
The default retention is 'never expire', so log storage grows forever.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
/aws/lambda/old-report-fn | 12.00 GB stored, retention: never expire | Set a retention period that matches your needs (e.g. 30 days for app logs, longer for audit logs). Saving depends on how old the data is, so it is not estimated.Command (risk: low)aws logs put-retention-policy --region ap-south-1 --log-group-name '/aws/lambda/old-report-fn' --retention-in-days 30 |
ap-south-1 | $0.36 | - |
/ecs/web | 48.00 GB stored, retention: never expire | Set a retention period that matches your needs (e.g. 30 days for app logs, longer for audit logs). Saving depends on how old the data is, so it is not estimated.Command (risk: low)aws logs put-retention-policy --region ap-south-1 --log-group-name '/ecs/web' --retention-in-days 30 |
ap-south-1 | $1.44 | - |
NAT-001 · Idle NAT gateways (1)
A NAT gateway costs ~$33-41/month before any data. Many dev VPCs keep one for traffic that never comes.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
nat-95a718f3d35a4e2d8 | moved 0.51 GB (in + out) in 14 days (VPC vpc-0527c2ef9d79f9cd3) | Delete it if private subnets do not need the internet; use VPC endpoints for S3/DynamoDB/ECR, or public subnets with tight security groups for small dev setups.Command (risk: high)aws ec2 delete-nat-gateway --region ap-south-1 --nat-gateway-id nat-95a718f3d35a4e2d8 |
ap-south-1 | $40.88 | $40.88 |
RDS-001 · Idle RDS databases (1)
A database with zero connections for two weeks is usually a forgotten test or migration copy.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
analytics-copy | db.t3.medium postgres 100 GiB: 0 connections in 13 days | Take a final snapshot and delete it (stopping only lasts 7 days before AWS restarts it).Command (risk: high)aws rds delete-db-instance --region ap-south-1 --db-instance-identifier analytics-copy --final-db-snapshot-identifier analytics-copy-billdoctor-final |
ap-south-1 | $63.96 | $63.96 |
S3-001 · S3 buckets without lifecycle rules (2)
Without lifecycle rules, incomplete multipart uploads and old object versions are kept (and billed) forever.
| Resource | Detail | Recommendation | Region | Now / month | Saving |
|---|---|---|---|---|---|
acme-uploads-demo | incomplete multipart uploads are never cleaned up; versioning is on but old versions never expire | Add a lifecycle rule: abort incomplete multipart uploads after 7 days and expire noncurrent versions after 30-90 days.Command (risk: low)aws s3api put-bucket-lifecycle-configuration --region ap-south-1 --bucket acme-uploads-demo --lifecycle-configuration '{"Rules":[{"ID":"billdoctor-hygiene","Status":"Enabled","Filter":{},"AbortIncompleteMultipartUpload":{"DaysAfterInitiation":7},"NoncurrentVersionExpiration":{"NoncurrentDays":60}}]}' # WARNING: replaces existing rules - merge them first |
ap-south-1 | - | - |
acme-exports-demo | incomplete multipart uploads are never cleaned up | Add a lifecycle rule: abort incomplete multipart uploads after 7 days.Command (risk: low)aws s3api put-bucket-lifecycle-configuration --region ap-south-1 --bucket acme-exports-demo --lifecycle-configuration '{"Rules":[{"ID":"billdoctor-hygiene","Status":"Enabled","Filter":{},"AbortIncompleteMultipartUpload":{"DaysAfterInitiation":7}}]}' # WARNING: replaces existing rules - merge them first |
ap-south-1 | - | - |
Method and assumptions
- Only read-only API calls were made (describe/list/get and CloudWatch metrics over the last 14 days).
- Prices are approximate on-demand list prices (2026-10); discounts, Savings Plans and Reserved Instances are not applied.
- Savings are estimates. Confirm with the resource owner, and snapshot or back up before deleting anything.
- ap-south-1: no local price for ebs_iops_month/io1; used us-east-1 as an approximation
- ap-south-1: no local price for ebs_iops_month/gp3; used us-east-1 as an approximation
- ap-south-1: no local price for ec2_hour/m4.large; used us-east-1 as an approximation
- ap-south-1: no local price for ec2_hour/m6i.large; used us-east-1 as an approximation
- ap-south-1: no local price for alb_hour; used us-east-1 as an approximation
- ap-south-1: no local price for logs_gb_month; used us-east-1 as an approximation
- ap-south-1: no local price for rds_hour/db.t3.medium; used us-east-1 as an approximation