AWS storage cost analysis: Find where money is being wasted

Storage costs in AWS accumulate quietly. Unlike compute, which is obviously on or off, storage persists indefinitely unless you explicitly act to remove or tier it. The S3 bucket created for a project two years ago still exists and still charges. The EBS snapshots from a decommissioned server are still there. The CloudTrail logs and VPC Flow Logs are in Standard storage when they could cost 80% less in Archive.

A storage cost analysis works through each storage service systematically: inventory what exists, identify what is misconfigured or abandoned, and apply the changes that reduce cost without losing data.

Step 1: Get the full picture with Cost Explorer

Before optimising anything, understand what you are currently spending and on which storage services. Navigate to AWS Cost Explorer > Service breakdown. Filter the time range to the last 3 months to establish a baseline.

Identify the top storage cost drivers. In a typical AWS environment: - S3 often appears in multiple line items: storage, requests, data transfer out, replication - EBS appears as volume storage and snapshot storage separately - RDS storage appears as provisioned storage and backup storage - CloudWatch Logs and other log services accumulate if not managed

For S3 specifically, enable S3 Storage Lens. Navigate to S3 > Storage Lens > Create dashboard. Storage Lens provides a detailed breakdown of storage by bucket, storage class, and access patterns. It shows which buckets contain the most data, which storage classes are most expensive, and which objects have not been accessed recently.

Storage Lens has a free version covering key metrics and a paid version with detailed object-level insights. The free version is sufficient for a cost analysis; the paid version is useful for ongoing governance.

Step 2: Audit S3 storage class distribution

S3 Standard costs approximately £0.018/GB per month. S3 Intelligent-Tiering costs £0.014/GB for frequent access and £0.0054/GB for infrequent access, with automatic movement between tiers based on access patterns. S3 Standard-IA costs £0.0098/GB. S3 Glacier Instant Retrieval costs £0.0032/GB. S3 Glacier Deep Archive costs £0.00077/GB.

Data that has not been accessed in 30 days sitting in S3 Standard is paying 5-20x more than it needs to.

Identify misclassified data:

# List the storage class breakdown across all buckets
aws s3api list-buckets --query 'Buckets[*].Name' --output text | \
  xargs -I{} aws s3api list-objects-v2 --bucket {} \
  --query 'sum(Contents[*].Size)' --output text

For a detailed object-level inventory, enable S3 Inventory on high-volume buckets. S3 Inventory produces a daily or weekly CSV or Parquet file listing every object with its storage class, size, last modified date, and optional last access date. Query the inventory with Athena to identify objects that have not been accessed and are in expensive storage classes.

The migration path: enable S3 Intelligent-Tiering for buckets with unpredictable access patterns. Intelligent-Tiering moves objects automatically based on 30-day (infrequent) and 90-day (archive) access thresholds with no retrieval fees on accessed data.

For data with predictable access patterns (log archives accessed only for compliance investigation, database backups accessed only during recovery), use explicit Lifecycle Rules to move data to Glacier Instant Retrieval after 30 days and Deep Archive after 90 days.

Step 3: Remove EBS snapshot waste

EBS snapshots are incremental backups of EBS volumes. They are charged per GB of data stored. Snapshots of deleted volumes continue to exist and accumulate charges indefinitely until explicitly deleted.

Identify orphaned snapshots (snapshots with no associated volume):

# Find snapshots not associated with any existing volume
aws ec2 describe-snapshots --owner-ids self \
  --query 'Snapshots[?!State==`available` || Description==`Created by CreateImage`].{ID:SnapshotId,Size:VolumeSize,Date:StartTime,VolumeId:VolumeId}'

Cross-reference the VolumeId in each snapshot against existing volumes. Snapshots whose source volume no longer exists are candidates for deletion. Before deleting, confirm the snapshot is not referenced by any AMI: aws ec2 describe-images --filters Name=block-device-mapping.snapshot-id,Values=snap-xxxx.

Establish a snapshot retention policy. For development environments, daily snapshots with 7-day retention is typical. For production, daily with 30-day retention plus weekly with 1-year retention. Implement the policy using Data Lifecycle Manager: navigate to EC2 > Elastic Block Store > Lifecycle Manager > Create lifecycle policy. DLM automatically creates and deletes snapshots per the defined schedule.

Step 4: Audit EBS volume types and sizing

EBS volume type and size both affect cost. gp2 volumes are charged at £0.076/GB per month; gp3 volumes at £0.062/GB per month with the same baseline performance. Migrating from gp2 to gp3 saves approximately 20% on EBS costs with no performance trade-off for most workloads.

Identify gp2 volumes:

aws ec2 describe-volumes \
  --filters Name=volume-type,Values=gp2 \
  --query 'Volumes[*].{ID:VolumeId,Size:Size,Instance:Attachments[0].InstanceId}'

Migrating gp2 to gp3 is non-disruptive: the volume modification happens live without detaching the volume. In the EC2 console, select the volume and choose Modify Volume > gp3. For volumes where you have also customised IOPS on gp2, verify the gp3 IOPS configuration before migrating (gp3 provides 3,000 IOPS baseline and 125 MB/s throughput by default).

Also check provisioned IOPS volumes (io1, io2). If the application's actual IOPS usage is well below the provisioned level, reduce the provisioned IOPS. CloudWatch provides VolumeReadOps and VolumeWriteOps metrics to confirm actual usage.

Step 5: Manage log storage costs

CloudWatch Logs, VPC Flow Logs, CloudTrail logs, and application logs accumulate indefinitely if not managed. CloudWatch Logs charges for data ingested and for data retained beyond the free 31-day threshold.

Set log group retention policies for all CloudWatch Logs groups:

# Set 90-day retention on all log groups without a retention policy
aws logs describe-log-groups --query 'logGroups[?!retentionInDays].logGroupName' --output text | \
  xargs -I{} aws logs put-retention-policy --log-group-name {} --retention-in-days 90

For long-term log retention required for compliance (PCI DSS, FCA), export logs to S3 and apply Glacier lifecycle rules rather than paying CloudWatch Logs rates for multi-year retention. CloudWatch Logs at £0.50/GB ingested and £0.023/GB stored is significantly more expensive than S3 Glacier Deep Archive at £0.00077/GB.

Where Critical Cloud comes in

Storage cost analysis is a one-time activity that delivers significant savings, but without ongoing governance, the waste accumulates again. New buckets are created, snapshots pile up, and lifecycle rules are not applied to new workloads. We manage AWS storage cost governance as an ongoing operational function, with Cost Explorer and Storage Lens monitoring integrated into the platform management we run for technology-led businesses. See how Critical Support works.