The call came on a Tuesday afternoon. A Series A SaaS company — 35 engineers, solid ARR growth, good product-market fit — had just had their first real cost review with their CFO. $68,000 in AWS spend the previous month. Their CTO's question was blunt: "We know we're wasting money. We just don't know where."
This is one of the most common conversations we have. Fast-moving engineering teams make infrastructure decisions under time pressure, and those decisions accumulate. Nobody ever sat down to overprovision RDS or route internal traffic through NAT. It just happened, one reasonable-at-the-time choice at a time.
Here's exactly what we found and what we did about it.
The audit process
A cost audit is a two-day exercise — one day in AWS Cost Explorer and one day in the actual infrastructure. The Cost Explorer pass tells you where money is going by service and by resource. The infrastructure pass tells you whether those resources are doing useful work.
We look at four ratios specifically:
- Compute utilization: what percentage of the CPU and memory you're paying for is actually being used, across the trailing 30 days
- Data transfer patterns: how much egress traffic is flowing, where it's going, and whether it's taking the most expensive route
- Storage-to-use ratio: how much you're storing vs. how much is being actively read
- On-demand vs. committed spend: what percentage of stable, predictable workloads are paying on-demand prices
Every company fails at least two of these. Most fail three.
The six findings
Finding 1: RDS over-provisioned by 4x
The production PostgreSQL instance was running on a db.r6g.4xlarge — 16 vCPUs, 128 GiB of RAM. Average CPU utilization over 30 days: 7%. Peak over the same period: 23% during a batch job that ran on Sunday nights.
The instance had been sized for a traffic spike that happened 18 months earlier and never repeated. The team upsized to handle it and never revisited it. Understandable. But a db.r6g.xlarge handles this workload comfortably — and a 1-year reserved instance at that size costs about a quarter of what the 4xlarge was running on-demand.
Monthly savings: $14,200. One change. One maintenance window. Two hours of work.
Finding 2: NAT Gateway as an accidental toll booth
NAT Gateway charges $0.045 per GB of data processed. It's easy to ignore this line item until you look at what's actually flowing through it.
In this case: $8,400/month in NAT Gateway data transfer. The culprit was application pods in private subnets making API calls to S3 and DynamoDB — routing through the NAT Gateway to reach AWS services that have free VPC endpoints. Every S3 GetObject, every DynamoDB query, every Secrets Manager call: all taking the paid route for no reason.
VPC endpoints for S3, DynamoDB, Secrets Manager, and ECR (they were pulling container images through NAT too) took an afternoon to configure. The NAT Gateway line item dropped by 90% the following month.
Monthly savings: $7,600.
Finding 3: On-demand pricing for predictable workloads
The EC2 fleet running application workloads was entirely on-demand. This makes sense during early growth when you don't know what your steady-state looks like. Two years post-launch with consistent traffic patterns, it's just overpaying.
We analyzed 6 months of instance usage. The baseline fleet — the instances running 24/7 regardless of traffic — was clear. We purchased 1-year reserved instances for that baseline and layered Compute Savings Plans on top for flexibility. The on-demand fleet was kept for overflow and auto-scaling headroom.
Monthly savings: $6,100. No operational change. Just a purchasing decision.
Finding 4: The inventory nobody had reviewed
Every AWS account accumulates orphaned resources. Snapshots from instances terminated a year ago. EBS volumes detached from deleted instances. Load balancers pointing at nothing. Elastic IPs allocated but unattached.
In this account: 31 snapshots averaging 14 months old (the instances they backed up no longer existed), 6 detached EBS volumes totalling 2.4TB, 3 idle Application Load Balancers with no healthy targets, and 4 unattached Elastic IPs.
None of this individually is catastrophic. Together it was $1,900/month for resources doing no work. We ran a cleanup pass and scheduled a quarterly orphan review as part of the ongoing engagement.
Monthly savings: $1,900.
Finding 5: S3 storing everything forever
The application was logging extensively — which is good. But it was storing every log file in S3 Standard indefinitely — which is expensive and pointless. Debug logs from 2023 are not going to be useful to anyone.
We added S3 lifecycle policies: transition to S3 Infrequent Access after 30 days, Glacier Instant Retrieval after 90 days, and expire (delete) after 365 days for application logs. Audit logs had a separate policy: move to Infrequent Access after 90 days, retain for 3 years (compliance requirement).
Monthly savings: $1,400 once the policy applied across the existing data (took about 60 days to show fully in billing).
Finding 6: Static assets served the expensive way
The frontend was a React SPA with assets — JS bundles, images, fonts — stored in S3 and served directly via S3 static website hosting. No CloudFront. Every asset request went S3-to-internet, billed at S3's data transfer rate of $0.09/GB.
A CloudFront distribution in front of the S3 bucket changes the billing model: S3-to-CloudFront transfer is free, and CloudFront egress is cheaper ($0.0085/GB for the first 10TB in most regions). For a product with meaningful user traffic, this is a significant difference. Setup took two hours. DNS propagation took 24 hours.
Monthly savings: $2,800. Also: ~40% improvement in asset load times for users outside the hosting region.
The total
Six findings. $34,000/month identified. $28,000/month implemented in the first six weeks — the remaining $6,000 is tied to an application-level change (moving some batch processing to a different architecture) that's on the roadmap for Q3.
The $28,000 monthly reduction is annualized savings of $336,000 from a two-day audit and six weeks of execution work.
"We were embarrassed by how obvious some of the waste was. But without someone dedicated to looking at it, it just compounds month after month. We'd been paying for that RDS instance for over a year."
The four questions behind every finding
Cloud cost optimization isn't a one-time project — it's an audit discipline. Every finding above came from the same four questions:
- What is the actual utilization, not the allocated capacity? Utilization metrics don't lie. Provisioning decisions from 18 months ago do.
- Is traffic taking the most expensive route available? NAT Gateway, public egress, cross-region transfer — all expensive defaults that have cheaper alternatives.
- What are you storing that you don't need to store? S3 and EBS costs grow silently. Lifecycle policies are the fix; the absence of lifecycle policies is the cause.
- Where are you paying on-demand prices for predictable workloads? On-demand is the right default when you're uncertain. Reserved and Savings Plans are the right choice once you're not.
What takes real time
Most of these changes are operationally simple. A few require care:
Reserved instances require commitment. You're signing up for 1 or 3 years. Before purchasing, we need 90+ days of usage data showing stability. Don't buy reservations for instances you might resize or switch to Graviton next quarter.
RDS right-sizing needs a maintenance window. The instance has to restart to change the class. For a production database, that's a 5–10 minute window that requires coordination with the engineering team and clear rollback criteria.
VPC endpoint configuration has gotchas. Routing tables in each subnet need to point to the endpoint. If you have multiple VPCs, each needs its own endpoint configuration. We've seen teams configure an endpoint, miss a routing table update in one subnet, and wonder why 20% of their traffic is still going through NAT.
None of these are blockers — they're just reasons to do the implementation carefully rather than quickly.
What comes after the audit
The six findings above are the acute waste — the easy wins with clear ROI. After they're addressed, we shift to structural cost discipline: tagging enforcement so every resource is attributable to a team or product, budget alerts so cost surprises stop happening, and a quarterly review cadence to catch the next generation of orphaned resources before they compound.
Cloud costs at a growing startup are never "done." They're managed. The difference between a team with cost discipline and one without is usually 25–40% of their cloud bill — every month.