We Cut a SaaS Startup's AWS Bill by 62%. Here's Where the Money Was Going.

A Series A SaaS company brought us in after their AWS bill crossed $34,000 in a single month. Their engineering team had attributed it to growth — more users, more traffic, reasonable to pay more. We attributed it to six months of infrastructure decisions made without anyone watching the numbers.

Three weeks later, their monthly bill was $13,000. Here's exactly what we found and what we changed.

How we start every cost audit

Before touching anything, we spend a day in AWS Cost Explorer and Cost and Usage Reports. We want to see not just what the bill is, but how it changed month-over-month and which services drove each spike. The pattern matters: an EC2 cost that grew steadily with user count is a different problem than one that doubled in a month when usage stayed flat.

We also run the AWS Trusted Advisor cost recommendations and Compute Optimizer's rightsizing report. These surface the low-hanging fruit — oversized instances, idle load balancers, unattached volumes — without requiring us to know the architecture first.

For this client, five categories accounted for the waste. In order of impact:

EC2: Right-sized for a company three times their size

Their production API ran on 12 m5.2xlarge instances ($0.384/hour each, ~$3,300/month per instance). Average CPU utilization over the prior 30 days: 9%. Peak utilization: 31%, during a batch job that ran at midnight.

The original sizing decision was defensible — they'd over-provisioned intentionally during a Series A launch expecting a traffic spike that never materialized at that scale. Nobody revisited it afterward. The Compute Optimizer recommendation: m5.large, at $0.096/hour. We made the change in a rolling deployment, monitored for two weeks, and confirmed the smaller instances handled real traffic with headroom to spare.

We also replaced the fixed Auto Scaling Group with Karpenter, which provisions nodes based on actual pod scheduling pressure instead of a predetermined target. The cluster now scales from 4 to 14 nodes during peak and back down overnight — versus running 12 fixed nodes around the clock regardless of load.

  • Before: 12 × m5.2xlarge, 24/7 = $9,200/month
  • After: 4–8 × m5.large via Karpenter, average = $1,800/month
  • Saved: $7,400/month

Data transfer: the line item nobody reads

AWS charges for data leaving the cloud but not for data entering it. What most teams don't realize is that intra-region data transfer — between Availability Zones in the same region — also costs money: $0.01/GB in each direction. That sounds negligible until you have services making high-frequency calls across AZs.

This client's application tier and database tier were in different AZs with no affinity configuration. Every API request generated 4–6 database calls, each crossing the AZ boundary both ways. At their traffic volume, that was 8TB of cross-AZ transfer per month: $160/month just on the data transfer charge. The fix — adding pod and service topology hints to prefer same-AZ routing — took an afternoon.

The bigger transfer cost was CloudFront. They were serving 4TB of assets per month directly from S3 without CloudFront, at $0.09/GB. CloudFront's origin fetch from S3 is free, and its edge delivery is $0.0085/GB — about 10x cheaper for assets that can be cached at the edge.

  • Cross-AZ routing fix: saved $160/month
  • CloudFront for static assets: saved $320/month
  • Saved: $480/month

RDS: Multi-AZ in environments that don't need it

Multi-AZ RDS provides a synchronous standby replica in a separate Availability Zone for automatic failover. It doubles the instance cost. It's the right choice for production databases. It's unnecessary in staging and development environments where a few minutes of downtime for a restore is acceptable.

This client ran three RDS instances in staging, all Multi-AZ, all sized the same as production (db.r6g.xlarge at $0.48/hour). Staging had approximately 5 concurrent users during business hours. They'd copied the production Terraform module without changing the settings, and nobody had revisited it since.

We switched staging to db.t3.medium single-AZ instances. Performance is more than sufficient for testing. We kept Multi-AZ enabled in production.

  • Before: 3 × db.r6g.xlarge Multi-AZ staging = $2,100/month
  • After: 3 × db.t3.medium single-AZ staging = $195/month
  • Saved: $1,905/month

NAT Gateways: one per AZ, three per environment

NAT Gateways enable private subnet resources to reach the internet. They charge $0.045/hour per gateway plus $0.045/GB of data processed. This client had one NAT Gateway per AZ (three AZs), in each of three environments — production, staging, and dev — for a total of nine NAT Gateways.

The gateway hours alone added up to $900/month. The data processing charges added $600/month on top of that. And a significant portion of that traffic was going to S3 and DynamoDB — which have VPC endpoints that route traffic through AWS's internal network instead of through the NAT Gateway. VPC endpoints for S3 and DynamoDB are free.

We created S3 and DynamoDB VPC endpoints in each environment, which immediately reduced NAT Gateway data processing. We also consolidated dev to a single NAT Gateway (dev doesn't need HA). The staging environment kept two gateways but dropped to one for dev.

  • VPC endpoints for S3/DynamoDB: saved $380/month in data processing
  • Dev consolidation to single gateway: saved $130/month
  • Saved: $510/month

The archaeology dig: idle resources

Every AWS account accumulates orphaned resources. Engineers spin up an instance to test something, move on, and forget to clean it up. The costs are individually small, but they compound across a team of 20 engineers over 18 months.

What we found in a half-day audit:

  • 23 unattached EBS volumes totaling 2.8TB — remnants of deleted instances where the volume delete-on-termination flag had been off. At $0.10/GB/month: $280/month.
  • 6 Elastic IPs not attached to running instances — $3.65/month each, nothing individually, but $22/month and a sign of larger sprawl.
  • 4 idle Application Load Balancers with zero targets — $16/month each plus LCU charges. Three pointed at services that had been migrated to internal routing months ago.
  • 2 stopped EC2 instances still attached to allocated Elastic IPs and provisioned EBS volumes, serving no purpose.

We tagged every resource they intended to keep, wrote a Lambda function that runs weekly and alerts on untagged resources older than 7 days, and cleaned out the identified orphans. Going forward, their Terraform modules have delete_on_termination = true set on all EBS volumes.

  • Saved: $440/month

The results

  • Before: $34,000/month
  • After: $13,000/month
  • Reduction: 62%
  • Annual savings: ~$252,000

The implementation took three weeks, all in working hours, with zero production incidents. The largest change — the EC2 rightsizing and Karpenter migration — was done as a rolling deployment with two weeks of parallel monitoring before decommissioning the old capacity.

"We assumed infrastructure costs scaled with user growth. Turns out about 60% of our bill was complexity we'd accumulated and never cleaned up. The audit paid for itself in the first week."

What to watch going forward

Cost reduction is a one-time win. Cost control is ongoing. What we set up to prevent regression:

  • AWS Budgets with alerts at 90% and 120% of expected monthly spend. The 120% alert is what pages someone — the 90% alert is informational.
  • Cost Anomaly Detection for each service category. If EC2 spend doubles in a 24-hour window, something unusual happened — either a legitimate scaling event or an error worth investigating.
  • Quarterly cost review as a standing agenda item in the engineering leadership meeting. The staging RDS issue took 8 months to accumulate because nobody had a standing process to look at the bill with context about what had changed.

Most AWS bills look like this one. The specifics differ but the pattern doesn't: over-provisioned compute, data transfer charges no one audited, same configuration copy-pasted from production into environments that don't need it, and idle resources from experiments that ended months ago. A cost audit almost always finds more than the team expected.

Want to know what you're actually paying for?

We'll audit your AWS or GCP bill and deliver a prioritized list of savings — with implementation complexity and expected monthly impact for each item. No obligation, and we'll tell you if there's nothing significant to find.

Book Free Audit

Related: DevOps Services · True Cost of a Full-Time DevOps Engineer

← Back to all articles