If you're at a seed or Series A company, nobody owns the AWS bill until it gets big enough to scare someone. By then the waste has been sitting there for months. It's rarely one big mistake. It's a pile of small things: a volume nobody deleted, a load balancer from a demo, logs kept forever.
This is the afternoon audit I run first on a startup's AWS account. Every check is a read-only CLI command, and each one comes with what it costs if you find something. Prices below are us-east-1 list prices. Check your own region before quoting numbers to anyone.
Before you start
Use a role with read-only access (the ReadOnlyAccess managed policy covers everything below; if the Cost Explorer call returns AccessDenied, add ce:GetCostAndUsage). Most resources are regional, so run each check in every region you use:
for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
echo "== $r"
# paste any check below here, with --region "$r"
done
0. Where the money actually goes
Start with last month's bill by service so you know which checks matter most:
aws ce get-cost-and-usage \
--time-period Start=2026-09-01,End=2026-10-01 \
--granularity MONTHLY --metrics UnblendedCost \
--group-by Type=DIMENSION,Key=SERVICE \
--query 'ResultsByTime[0].Groups[].[Keys[0],Metrics.UnblendedCost.Amount]' \
--output table
Cost Explorer API calls cost $0.01 each, so run this once, not in a loop.
1. Volumes attached to nothing
An EBS volume in the available state isn't attached to any instance, but you still pay for every gigabyte.
aws ec2 describe-volumes --filters Name=status,Values=available \
--query 'Volumes[].[VolumeId,Size,VolumeType,CreateTime]' --output table
At $0.08 per GB-month for gp3, 500 GB of forgotten volumes is $40 a month. Snapshot anything you're unsure about, then delete the volume.
2. gp2 volumes that should be gp3
gp3 costs $0.08 per GB-month against $0.10 for gp2, which is 20% cheaper, and its baseline performance is at least as good for most workloads. The change happens online, without detaching anything.
aws ec2 describe-volumes --filters Name=volume-type,Values=gp2 \
--query 'Volumes[].[VolumeId,Size,Iops]' --output table
# per volume, after checking its IOPS needs:
aws ec2 modify-volume --volume-id vol-0123456789abcdef0 --volume-type gp3
2,000 GB moved from gp2 to gp3 saves 2,000 × 0.02 = $40 a month. Before you convert a large gp2 volume, look at its IOPS: gp2 gets 3 IOPS per GB, and gp3 starts at a flat 3,000.
3. Snapshots nobody will restore
aws ec2 describe-snapshots --owner-ids self \
--query "Snapshots[?StartTime<='2026-04-01'].[SnapshotId,VolumeSize,StartTime,Description]" \
--output table
Standard snapshot storage is $0.05 per GB-month. VolumeSize overstates the real cost because snapshots are incremental, so read the "EBS:SnapshotUsage" line in Cost Explorer for the true figure. For snapshots you must keep but won't touch, the archive tier is $0.0125 per GB-month, with a 90-day minimum and a fee to restore.
4. Public IPv4 addresses
Since February 2024 every public IPv4 address costs $0.005 an hour, whether it's in use or idle. That's 0.005 × 730 = $3.65 a month each. Start with Elastic IPs that aren't associated with anything:
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==null].[PublicIp,AllocationId]' --output table
Then list every network interface with a public IP. You'll often find instances in public subnets that don't need one:
aws ec2 describe-network-interfaces \
--query 'NetworkInterfaces[?Association.PublicIp].[NetworkInterfaceId,Association.PublicIp,Description]' \
--output table
5. Load balancers with no traffic
An Application Load Balancer costs $0.0225 an hour before any traffic, so 0.0225 × 730 = $16.43 a month just for existing. Find the ones that served no requests in the last 14 days:
for arn in $(aws elbv2 describe-load-balancers --query 'LoadBalancers[?Type==`application`].LoadBalancerArn' --output text); do
name=$(echo "$arn" | cut -d/ -f2-4)
sum=$(aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB \
--metric-name RequestCount --dimensions Name=LoadBalancer,Value="$name" \
--start-time "$(date -u -d '14 days ago' +%FT%TZ)" --end-time "$(date -u +%FT%TZ)" \
--period 1209600 --statistics Sum --query 'Datapoints[0].Sum' --output text)
echo "$name $sum"
done
None or 0 means nothing has hit it in two weeks. On macOS, use date -u -v-14d instead of -d '14 days ago'.
6. Log groups that keep everything forever
New CloudWatch log groups default to "never expire". Storage is about $0.03 per GB-month, but ingestion at $0.50 per GB is usually the bigger number. This lists groups with no retention and their stored size; for ingestion, check each large group's IncomingBytes metric:
aws logs describe-log-groups \
--query 'logGroups[?!retentionInDays].[logGroupName,storedBytes]' --output table
# set a retention period per group:
aws logs put-retention-policy --log-group-name /aws/lambda/my-fn --retention-in-days 30
If one group is ingesting tens of GB a day, the fix is less logging, not shorter retention.
7. NAT gateways doing more work than they should
A NAT gateway costs $0.045 an hour (0.045 × 730 = $32.85 a month) plus $0.045 for every GB it processes. The usual culprit is S3 traffic (ECR image layers included, since they're pulled from S3) that should go through a free gateway endpoint. I wrote up that fix separately: why your NAT gateway bill keeps growing.
aws ec2 describe-nat-gateways --query 'NatGateways[].[NatGatewayId,VpcId,State]' --output table
What a typical first pass adds up to
Here's an illustrative example of findings, not a specific account:
| Finding | Monthly saving (USD) |
|---|---|
| 500 GB of unattached gp3 volumes | 40.00 |
| 2,000 GB of gp2 moved to gp3 | 40.00 |
| 8 idle public IPv4 addresses | 29.20 |
| 2 load balancers with no traffic | 32.85 |
| 3 TB of old standard snapshots | 150.00 |
| Total | 292.05 USD a month |
That's about 292.05 × 12 = 3,505 dollars a year from an afternoon of reading. None of this needs a reserved instance or a Savings Plan. Those are bigger levers, but they lock you in, so do this cleanup first and commit to the smaller bill afterwards.
Make it stick
- Tag everything with an
ownerand apurpose. Untagged resources are the ones nobody deletes. - Turn on AWS Cost Anomaly Detection. It's free, and it emails you when a service jumps.
- Put a monthly calendar reminder on someone's name to run this list again.
If you want a rough number before you start, our cloud cost calculator estimates how much of a bill is usually recoverable. For a full review across AWS, GCP and Azure, that's the cloud cost optimization work I do at Axionry.
Prices: AWS EBS, VPC, Elastic Load Balancing and CloudWatch pricing pages (us-east-1), checked October 2026. I used an AI assistant while drafting this.


Top comments (0)