AWS Storage Cost Optimization
EBS Cost Optimization: Stop Paying for Storage You Don't Use
Unattached volumes, gp2 pricing, and over-provisioned IOPS can waste up to 50% of EBS spend. Most of these are 15-minute Terraform fixes.
The 3 EBS Money Pits
These three issues account for nearly all EBS overspend in startup accounts.
Unattached EBS volumes
HighWhen an EC2 instance is terminated, the attached EBS volume is often left behind - especially when `DeleteOnTermination` is set to false (which is the default for volumes added after launch). These volumes sit idle, billed at full price.
The fix
Run `aws ec2 describe-volumes --filters Name=status,Values=available` to list all unattached volumes. Review each before deleting - create a snapshot first if the data might be needed.
Terraform change
Set `delete_on_termination = true` in `aws_instance.root_block_device` for all new instances. This prevents future orphans.
Typical saving: $50–500/month depending on volume count and size
gp2 volumes (migrate to gp3)
Mediumgp2 is the old EBS default. gp3 is 20% cheaper per GB, delivers 3,000 IOPS baseline for free (vs. gp2's burst credit system), and lets you configure IOPS and throughput independently. There is no reason to use gp2 in 2026.
The fix
Identify all gp2 volumes with `aws ec2 describe-volumes --filters Name=volume-type,Values=gp2`. Migrate to gp3 - zero downtime, no reboot required, performs the conversion online.
Terraform change
Change `volume_type = "gp2"` to `volume_type = "gp3"` in all `aws_ebs_volume` and `aws_instance.root_block_device` resources.
Typical saving: 20% reduction on all gp2 storage spend
Over-provisioned io1/io2 IOPS
Mediumio1 and io2 volumes charge separately for storage ($0.125/GB/month for io1) and provisioned IOPS ($0.065/IOPS-month for io1). If you provisioned 10,000 IOPS but CloudWatch shows peak IOPS of 1,200, you're paying for 88% unused IOPS.
The fix
Check `VolumeReadOps` and `VolumeWriteOps` CloudWatch metrics at p99 over 2 weeks. If actual IOPS is consistently below provisioned, reduce IOPS or migrate to gp3 (which provides 3,000 IOPS free and up to 16,000 IOPS at $0.005/IOPS - 13× cheaper than io1).
Terraform change
For gp3: `iops = 3000` (free), up to `iops = 16000` at $0.005/IOPS. For io2: `iops = <actual_peak * 1.5>`.
Typical saving: 30–70% reduction on high-IOPS volume costs
Snapshot Lifecycle Policies
EBS snapshots accumulate silently. Daily automated backups with no retention policy result in months or years of snapshots billing at $0.05/GB/month.
Quick audit
# List all snapshots owned by your account
aws ec2 describe-snapshots \
--owner-ids self \
--query 'Snapshots[*].{
ID:SnapshotId,
Size:VolumeSize,
Date:StartTime
}' \
--output table | sort -k3DLM lifecycle policy (Terraform)
resource "aws_dlm_lifecycle_policy" "ebs_backups" {
description = "Daily EBS backups, 7-day retention"
execution_role_arn = aws_iam_role.dlm.arn
policy_details {
resource_types = ["VOLUME"]
schedule {
name = "7-day retention"
retain_rule { count = 7 }
create_rule {
interval = 24
interval_unit = "HOURS"
times = ["03:00"]
}
}
target_tags = {
Environment = "production"
}
}
}