A B2B SaaS startup came to us with an AWS bill north of $42,000/month that was growing faster than their revenue. Their CTO knew something was off — the bill had doubled over 18 months while their user base had grown maybe 40% — but with a 12-person engineering team and no one dedicated to infrastructure, there was never time to actually dig in. The approach had been: don't touch it, it's working.
Three months later, their bill was $22,400/month. Same infrastructure. Same traffic. Same product. Here's exactly what we found and what we changed.
The audit
We started with a week of read-only access to their AWS Cost Explorer, Trusted Advisor findings, and Compute Optimizer recommendations. No changes yet — just understanding where the money was going and why.
The breakdown of their $42K/month bill:
- EC2: $18,200 (43%)
- RDS: $11,400 (27%)
- Data transfer / NAT Gateway: $6,800 (16%)
- S3: $3,100 (7%)
- Everything else: $2,500 (6%)
Compute Optimizer had flagged 31 EC2 instances as over-provisioned. They had dismissed those recommendations because "we don't want performance issues." Trusted Advisor had been ignored for over a year.
What we found and what we changed
1. Dev and staging environments running 24/7 on production-sized instances
Their dev environment was a mirror of production — same instance types, always-on, costing about $6,200/month. The staging environment added another $4,100/month. Both were used 8 hours a day by a team in a single timezone.
We implemented instance scheduling: dev and staging spin down at 7pm and back up at 8am, Monday through Friday. We also right-sized both environments — there's no reason dev needs to match production spec when the traffic is a handful of engineers.
Monthly saving: $7,400
2. No Savings Plans or Reserved Instances on baseline compute
Their production workload had been running on the same instance types for 14 months. All on-demand. The CTO knew Reserved Instances existed but "didn't want to commit to anything." At 14 months of consistent on-demand billing, they had already paid more than a 3-year Reserved Instance would have cost them.
We analyzed their last 90 days of compute usage to identify baseline vs. bursty demand. The baseline (always-on prod instances) was committed to 1-year Compute Savings Plans. Variable capacity stayed on-demand with Spot Instances for non-critical workers.
Monthly saving: $3,200
3. RDS Multi-AZ enabled in dev and staging
Multi-AZ roughly doubles your RDS costs — it maintains a hot standby in a second availability zone. For production, this is correct. For dev and staging, it's wasted money. They had Multi-AZ enabled on every database in every environment because it was checked by default when someone provisioned the first database three years ago.
We disabled Multi-AZ on non-production databases. We also identified two RDS instances that hadn't had a connection in 47 and 62 days respectively — orphaned from a migration. Deleted.
Monthly saving: $2,900
4. NAT Gateway egress charges from logs being shipped to S3
This one was subtle. They were shipping application logs from their EC2 instances to an S3 bucket — reasonable. But the S3 bucket was in a different region than the EC2 instances, which meant every log write was crossing the NAT Gateway and incurring inter-region data transfer charges. At their log volume, this was adding $2,800/month.
Moving the S3 bucket to the same region dropped the NAT Gateway egress charges to near-zero. We also added VPC endpoints for S3 and DynamoDB so traffic to those services bypasses the NAT Gateway entirely.
Monthly saving: $3,100
5. S3 storage class drift
They had 18TB of data in S3 Standard. About 14TB of it hadn't been accessed in more than 180 days — build artifacts, old database snapshots, log archives. Standard storage costs $0.023/GB/month. S3 Glacier Flexible Retrieval costs $0.0036/GB/month.
We implemented S3 Lifecycle policies: objects not accessed in 90 days move to Standard-IA, objects not accessed in 180 days move to Glacier. The first transition happened within 30 days of deployment.
Monthly saving: $2,100 (at full transition)
6. Orphaned EBS volumes and unattached Elastic IPs
A sweep of their account found 23 unattached EBS volumes (totaling 1.8TB) and 14 unallocated Elastic IPs. EBS volumes continue to incur storage charges after the EC2 instance they were attached to is terminated. Unallocated EIPs cost $0.005/hour.
We tagged everything, confirmed with the relevant teams that none of it was intentionally preserved, and deleted or released accordingly.
Monthly saving: $860
The results
- Month 1: Instance scheduling + RDS changes took effect → bill dropped to $34,200
- Month 2: Savings Plans committed, NAT Gateway routing fixed → $27,800
- Month 3: S3 lifecycle policies fully transitioned → $22,400
Total reduction: $19,600/month, or 47%. Annual saving: $235,200.
"We'd been meaning to look at this for over a year. The audit paid for itself in the first month. We're kicking ourselves for waiting."
What we didn't change
It's worth noting what we left alone. We didn't touch production instance sizing — their production workload ran at comfortable utilization and we didn't want to introduce performance risk for savings that were already covered by the Savings Plans commitment. We didn't migrate their database from RDS to self-managed Postgres — the operational overhead wasn't worth the cost difference at their scale.
Cloud cost optimization is not about cutting everything. It's about paying for what you're using, not paying for what you set up three years ago and forgot about.
The pattern we see every time
This client wasn't unusual. In almost every AWS cost audit we run, we find the same four categories: non-production environments running 24/7 as if they were production, no commitment pricing on baseline compute, data transfer charges from routing decisions made without understanding their cost implications, and storage tiering that was never set up because "we'll do it later."
In aggregate, these four issues typically represent 30–50% of AWS spend for a startup that hasn't done a dedicated cost review. The changes are not technically complex. They don't require a rebuild. They require someone to have the time to find them, understand the risk of each change, and execute methodically.