Skip to content
AI & Engineering9 min read

Where AWS Bills Actually Come From, and the Fixes That Don't Hurt Reliability

Idle instances, orphaned volumes, and missing Savings Plan coverage drive most AWS overspend. A practical guide to cutting waste without touching redundancy.

Published By the Safe Tech AI team

Ask an engineering team why last month's AWS bill went up and the honest answer is almost always "we don't know" — followed, a week later, by a Cost Explorer screenshot that still doesn't quite answer the question. That gap is not a tooling failure. It is the natural result of a billing model where every team, every environment, and every forgotten proof of concept adds a line item independently, and nobody is assigned to notice when one of those line items stops earning its keep.

The waste is rarely exotic. It is not a misconfigured Lambda burning through invocations — it is an m5.2xlarge sized for a launch that happened eight months ago, still running at 6% CPU because resizing it means asking someone to schedule a maintenance window they've been avoiding. Cloud cost problems are overwhelmingly problems of accumulation and neglect, not architecture mistakes made on purpose. That is good news, because accumulation and neglect are fixable without touching anything that makes the system reliable.

The usual suspects, in the order we find them

Every account we've looked at during a cloud cost audit has some subset of the same categories of waste. None of them are surprising once named; the trouble is that none of them show up unless someone goes looking.

  • Oversized or idle EC2 and RDS instances. Provisioned for a peak that never recurred, or for a load estimate made before the product had real users. Left at the original size because downsizing means downtime, and downtime means a conversation nobody wants to schedule.
  • Orphaned EBS volumes and snapshots. An instance gets terminated; its attached volume does not, because deleting a volume feels riskier than leaving it. Snapshots taken before a risky migration accumulate for years after the migration succeeded, at a few cents each, multiplied by thousands.
  • Unattached Elastic IPs. A small per-hour charge for an address that's been unattached since the instance it pointed to was replaced. Trivial individually, and one of the easiest line items to simply never audit.
  • Over-provisioned container and Kubernetes clusters. Node groups sized for worst-case concurrency and left there permanently, cluster autoscalers configured with a floor that's really a ceiling in disguise, and requests/limits copied from a template rather than set from observed usage.
  • Data transfer costs that are invisible until the bill arrives. Cross-AZ chatter between services that could have been placed together, NAT gateway charges for outbound traffic that could have used a VPC endpoint instead, and inter-region replication nobody re-costed after the architecture changed.
  • Dev and staging environments that run 24/7. Full-sized replicas of production, used nine hours a day on weekdays, billed for a hundred and sixty-eight.
  • Missing Reserved Instance or Savings Plan coverage. Stable, predictable workloads still paying full on-demand rates because nobody has revisited commitment coverage since the account was first set up.

None of these require a redesign to fix. They require someone to look at utilisation data, which is the part that keeps not happening.

Right-sizing is a data discipline, not a guess

The instinct when a bill looks high is to eyeball the instance list and downsize anything that sounds oversized. That produces roughly the results you'd expect from guessing: some correct calls, some near-misses that quietly reintroduce latency, and no way to tell which is which until something breaks under load.

The alternative is boring and it works: pull real CloudWatch utilisation — CPU, memory where the agent is installed, network throughput, IOPS — over a window long enough to capture your actual peak, not just the last quiet Tuesday. A 30-day window that includes a month-end batch job or a Black Friday-equivalent traffic spike tells you something a 7-day window cannot. From that data, the right-sizing decision becomes arithmetic rather than opinion: an instance running at 8% average CPU with a peak of 20% is not a candidate for a slightly smaller instance, it's a candidate for an instance one or two full sizes down, or for a different instance family entirely if the workload is memory-bound rather than compute-bound.

The same discipline applies to storage class and volume type — gp3 instead of a gp2 sized years ago for IOPS the workload never used, S3 lifecycle rules that move cold data to Glacier automatically instead of leaving it in Standard by default — and to Kubernetes, where the equivalent exercise is comparing pod requests/limits against actual usage via a metrics pipeline rather than the values a template shipped with. In every case the sequence is the same: measure first, then act, then re-measure to confirm the change didn't quietly erode headroom.

The false economy: cost cuts that break redundancy

This is the point worth being blunt about. A cost cut that removes a standby replica, drops a Multi-AZ deployment to single-AZ, or eliminates the headroom that absorbs a traffic spike is not a saving — it is a cost deferred into an outage, and outages are more expensive than the line item they replaced, in ways that don't show up on the same invoice. The distinction that matters is between waste and capacity you are paying for on purpose. An idle dev instance running at 2am is waste. A standby database replica sitting mostly idle is not waste; it is the thing that makes your RTO real instead of aspirational.

We treat this as a hard constraint rather than a nice-to-have: every proposed change gets weighed against its effect on availability and performance before it gets weighed against its saving. Deleting an orphaned snapshot or shutting down a non-production environment overnight carries no reliability risk and is a safe win to act on immediately. Removing a replica, consolidating an autoscaling floor, or resizing a production database is a change that needs a load test or a staged rollout first, precisely because getting it wrong doesn't just cost money — it costs the availability the redundancy existed to protect. Anyone proposing cost cuts should be able to point to which category a given change falls into, and treat "we're not sure" as a reason to test before deploying, not a reason to skip the test.

Non-production environments are the fastest safe win

Of everything on the waste list, scheduling dev and staging environments to shut down outside working hours is the one with the best ratio of saving to risk, because by definition nobody is using them the rest of the time. A start/stop schedule tied to tags, or an auto-shutdown Lambda triggered by inactivity, turns a resource billed for 168 hours a week into one billed for something closer to 45 — with zero effect on anyone's workflow, because the environment is back up before the team that uses it arrives. The same logic applies to short-lived feature-branch environments spun up for a pull request and never torn down once the branch merged; if nothing tears them down automatically, something eventually will have to notice them manually, and manual noticing is exactly the step that doesn't happen at scale.

Reserved capacity is a commitment, not a discount code

Savings Plans and Reserved Instances only pay off against usage that is genuinely stable, and that ordering matters: commit to discounted capacity before right-sizing and you have locked in a discount on waste, for one to three years, with an early-termination cost if the architecture changes underneath it. The correct sequence is right-size first, observe the resulting baseline for long enough to trust it, and only then model commitment coverage against what's left — typically covering the steady floor of usage with a commitment and leaving genuine burst capacity on-demand or on a shorter Savings Plan term. A coverage analysis that maximises the discount on paper, without asking whether that baseline will still exist in eighteen months, is optimising the wrong variable.

Building so the waste doesn't reaccumulate

A cost audit that ends with a one-time cleanup solves this month's problem and guarantees next year's, because the underlying cause — infrastructure provisioned by hand, without consistent tagging, without anyone accountable for its lifecycle — is still there. The fix that actually holds is structural: infrastructure-as-code so every resource is created from a reviewed definition rather than a console click nobody remembers making, consistent tagging enforced at creation so spend is attributable to a team or product without an afternoon of manual reconciliation, and budgets with anomaly alerts so a runaway resource is caught within days rather than at the next monthly review. The same pipeline discipline that makes deploys reproducible and reviewable is what keeps a cost audit's savings from eroding — a resource created outside that pipeline is, almost by definition, a resource nobody is tracking. This is the same principle that applies to early infrastructure decisions in an MVP: the habits you establish before you have a cost problem are cheaper than the audit you need once you do.

The short version

Most AWS overspend traces back to a small, boring list — idle compute, orphaned storage, unscheduled non-production environments, and commitment coverage nobody has revisited — and fixing it is a data exercise, not a redesign. The discipline that matters is measuring real utilisation before resizing anything, separating the changes that are free wins from the ones that need a load test first, and building the tagging and automation that stop the waste from quietly returning six months later. None of that is in tension with reliability; the cuts that are are the ones to refuse. If you want a cost audit that ranks the specific waste in your account against the actual risk of touching it — rather than a generic percentage-savings promise — get in touch and we'll scope what an honest review of your setup would look like.

Topics:awscloud-cost-optimizationfinopsdevops

Related reading

Dealing with this in your own systems?

Tell us where you're stuck and we'll tell you plainly whether it's something to fix yourselves or worth bringing us in for.

Talk to our team