A SaaS company came to us last quarter with a familiar complaint: their AWS bill had grown from $12,000/month to $31,000/month over 18 months. Revenue had grown 2x in that period. Their infrastructure costs had grown 2.6x. The math was pointing the wrong direction.
Within four days of auditing their account, we found $11,200/month in recoverable waste — resources they were paying for and either not using or wildly over-provisioning. We've run this exercise dozens of times across companies ranging from early-stage startups to Series C companies. The same categories of waste appear, almost every time. Here's where the money hides.
1. The NAT gateway tax ($2,000–5,000/month)
NAT gateways charge $0.045 per gigabyte of data processed — both in and out. That's on top of the hourly instance cost. For companies running microservices in private subnets that call external APIs, S3, or other AWS services, this becomes a significant and invisible cost driver.
The SaaS company above was routing all S3 traffic through their NAT gateway. S3 has a VPC Gateway Endpoint — free, no data processing charges, faster latency — that routes traffic directly within the AWS network. Adding the endpoint took 20 minutes. Monthly savings: $2,800.
Similarly, inter-AZ data transfer is $0.01/GB each direction. If your application sends data between availability zones in a hot path — say, a cache miss that results in a call to a database replica in a different AZ — that adds up fast. The fix is either AZ-aware routing or consolidating traffic to a single AZ for latency-sensitive components that don't need multi-AZ redundancy.
2. Oversized RDS instances ($1,500–4,000/month)
RDS instances are provisioned for peak load. Peak load happens infrequently. The rest of the time, a db.r6g.2xlarge ($0.48/hour, $345/month) is sitting at 12% CPU and 30% memory utilization, waiting.
The usual story: the instance was sized up during a traffic spike six months ago, it handled the load, and nobody downsized it when the spike passed. We find this pattern in roughly 70% of the accounts we audit. The fix is straightforward — CloudWatch metrics for average CPU and memory over 30 days, compare against the next instance size down, test in staging, schedule a brief maintenance window, resize.
For databases with genuinely spiky load profiles, Aurora Serverless v2 is worth evaluating. It scales ACUs (Aurora Capacity Units) up and down in 0.5 ACU increments, so you pay for actual utilization rather than reserved capacity. Not suitable for every workload — the scaling has a floor that makes it more expensive than a small fixed instance at constant low load — but for bursty read-heavy workloads, it's compelling.
3. Zombie EBS volumes and snapshots ($500–2,000/month)
When an EC2 instance is terminated, its EBS volumes are not automatically deleted unless the instance was launched with DeleteOnTermination=true enabled. Over months of normal engineering activity — scaling groups, instance refreshes, experiments that were never cleaned up — unattached volumes accumulate. We've audited accounts with $800/month in EBS volumes attached to nothing.
Snapshots are worse. Automated backup policies create daily snapshots with a 7-day retention, then the retention policy gets changed or the backup job stops, but the old snapshots remain. We regularly find multi-year snapshot histories that nobody deleted because nobody knew they were there. At $0.05/GB/month, a 500GB database with 3 years of daily snapshots — even with deduplication — can cost $800–1,200/month in snapshot storage.
The audit: aws ec2 describe-volumes --filters Name=status,Values=available lists all unattached volumes. For snapshots, AWS Cost Explorer shows snapshot costs by resource, or you can use Trusted Advisor. A one-time cleanup of old snapshots typically saves $300–800/month and takes an afternoon.
4. Idle Elastic Load Balancers ($200–800/month)
ALBs and NLBs cost $0.008/hour plus $0.008 per LCU-hour — roughly $6–$30/month per load balancer at low traffic. The problem isn't the unit cost; it's the accumulation. Engineering teams spin up load balancers for new environments, services, or experiments, and they rarely get cleaned up when the project ends. We've found accounts with 12–15 load balancers, half of which had been serving zero requests for weeks.
Check: CloudWatch metrics for RequestCount per ALB over 30 days. Zero requests for 30 days is a strong signal it can be terminated.
5. EC2 Reserved Instance and Savings Plan mismatches ($2,000–6,000/month)
This is the most complex and often the most valuable category. Reserved Instances and Savings Plans require upfront commitment to instance type, region, and (for standard RIs) availability zone. When engineering teams change their instance strategy — moving from m5 to m7g, adding Graviton instances, changing regions — the old reservations continue charging even if no matching instances are running.
We audited one account where 14 Reserved Instances were running, 4 of them matched to instances that had been terminated. The RI payments were $2,100/month going to waste. The fix was selling the mismatched RIs on the AWS Marketplace (you typically recover 70–80% of remaining value) and aligning new commitments to the actual instance portfolio.
The other common RI issue: over-commitment. Teams buy 3-year RIs for a "baseline" load, then add Savings Plans on top, and end up with overlapping coverage. The AWS Cost Management console shows coverage and utilization, but it's easy to misread. Pull the data into a spreadsheet and verify the math manually before purchasing new commitments.
6. CloudWatch Logs retention at $0.03/GB/month ($300–1,200/month)
By default, CloudWatch Log Groups have infinite retention. Application logs, VPC flow logs, Lambda logs, RDS logs — they all accumulate indefinitely unless someone sets a retention policy. For a busy microservices application, this can reach hundreds of GB per month.
The fix is a one-time script that sets retention policies across all log groups. We usually set 30 days for application logs (queryable in CloudWatch Insights) and 90 days for audit logs (required for SOC 2 and PCI). The cleanup of existing over-retained logs happens over the next few months as old entries age out. Savings: $300–800/month for medium-scale applications.
What a cost audit actually looks like
A thorough AWS cost audit takes 2–3 days. We pull 3 months of Cost Explorer data, look at the top 20 cost drivers by service, then drill into each one with resource-level detail. For the categories above, we generate a prioritized list: savings potential, implementation effort, and risk level (some changes require maintenance windows; most don't).
The SaaS company we mentioned at the start: $11,200/month in recoverable savings, of which we implemented $8,400/month within the first two weeks. The remaining $2,800/month required a database resize that needed a 15-minute maintenance window — scheduled and done the following Sunday morning. Their bill went from $31,000 to $22,600 in 30 days. The audit cost them nothing.
"We knew we had some waste, but we thought it was maybe $1,000/month. Finding $11,000 in 4 days was a shock. The NAT gateway thing alone I would never have found on my own."
One thing that doesn't work: trusting AWS Cost Explorer alone
Cost Explorer is useful for trend analysis and top-line visibility, but it doesn't surface resource-level waste automatically. It will tell you "EC2 costs went up 20% this month" but not "you have 4 RIs that aren't matching anything." The Trusted Advisor checks help, but they're not exhaustive — particularly for cross-service issues like the NAT-to-S3 traffic pattern.
The gap is that cost analysis requires infrastructure knowledge. You need to understand how your application works to know whether a high data transfer cost is unavoidable (it's your architecture) or wasteful (it's a misconfiguration). That's why automated cost optimization tools catch the easy wins but consistently miss the $3,000/month item that requires someone who understands your stack.