Originally published on PrepStack. Cross-posting the TL;DR here.
Finance pinged us on a Monday: our Azure bill was up ~150% month-over-month and still climbing — and nobody had shipped a "big" feature. This is the investigation on a real production SaaS (.NET 9 / ASP.NET Core, Angular 19, ~110k MAU, ~3,200 req/sec on Azure): the seven causes, and the fixes that cut the bill 60%.
Where the money went (before -> after)
| Cost driver | Spiked | After fix | What it was |
|---|---|---|---|
| Log / telemetry ingestion | $3,100/mo | $340/mo | A Debug log level left on -> GBs/day to App Insights |
| Compute (autoscale) | $3,400/mo | $1,500/mo | Scale-out rule, no scale-in |
| Orphaned resources | $1,900/mo | $0 | Load-test env + unattached disks/IPs |
| Egress / bandwidth | $1,200/mo | $280/mo | Large files, no CDN |
| SQL + Redis | $1,600/mo | $900/mo | Premium tiers + no reservations |
| Storage transactions | $500/mo | $190/mo | Millions of tiny blob ops, hot tier |
| AI / LLM tokens | $400/mo | $190/mo | Uncached RAG, oversized model |
| Total | ~$12,100/mo | ~$4,700/mo | -60% |
What it covers
- Turning the lights on first: tagging, a cost dashboard, anomaly alerts
- The diagnostic queries (
az consumption, KQL log-ingestion-by-source) - Before/after config for each fix (adaptive sampling, autoscale Bicep, blob lifecycle, CDN + compression, LLM caching + model routing)
- A FinOps checklist so it doesn't recur
- The honest limits of cost optimization
The lesson: most runaway bills are a visibility problem, not an architecture one — two config mistakes and some deleted waste did most of the work. ~3 days to fix, paid back in ~2.
Full post with real Azure config, queries, and dollar figures: https://prepstack.co.in/blog/azure-bill-spiked-how-we-cut-it-60-percent
Top comments (5)
Finance pinged us on a similar issue and we found $390,000 in unused resources, which was 30% of our monthly cloud bill, after a thorough audit we were able to optimize and save $35000 per month, what was the first step you took when finance pinged you about cloud costs?
Nice — $390K in unused resources and $35K/month recovered is a serious find, and it tracks with what we saw: the big money is almost never in clever optimization, it's in stuff nobody remembered was running.
The first step, before touching a single resource, was making the bill legible — because when finance pinged us, the honest answer was "I don't actually know what's driving it," and you can't cut what you can't attribute. Concretely:
Turn on cost attribution by tag, and find out how much is untagged. That number is your real problem statement. For us a large chunk of spend had no owner tag at all — which is exactly where the zombie resources hide, because nothing untagged has anyone watching it.
Sort by cost, descending, and look at the top 10 line items — not the long tail. It's tempting to start optimizing the thing you understand; the money is usually in two or three resources you'd forgotten about. Ours was dominated by over-provisioned SQL tiers and idle compute that scaled up for a load test and never scaled back.
Separate "spike" from "baseline." Finance felt a spike, so the first cut was: what changed in the last billing period vs. the trailing average? That instantly split the investigation into "a specific thing broke recently" (fast win) vs. "we've been overpaying steadily" (bigger, slower win). Both matter, but they need different fixes.
So the first move was diagnostic, not surgical — get attribution + the top-N + spike-vs-baseline in front of me, then start cutting. The audit-first discipline is also what let us defend the savings afterward, same as you found: "here's what it was, here's what it is, here's why."
Curious what your $390K skewed toward — was it mostly orphaned storage/snapshots, or idle compute? Ours was compute-heavy, and I've noticed which one dominates says a lot about how a team's provisioning habits drift.
Good push - I don't actually have the full breakdown in front of me anymore (that engagement's a while back), so I'd rather not guess at a split than make one up to sound complete
What actually struck me in your writeup was the untagged-spend framing as the real problem statement: "you can't cut what you can't attribute" is a better first sentence for a cost audit than anything I usually open with
Does the untagged number ever creep back up post-fix, or did the attribution habit stick once finance had eyes on it?
Appreciate that — and honestly, "you can't cut what you can't attribute" only became my opening line because of the exact situation you described: finance asking a question I couldn't answer. The untagged number is the one metric that turns "the bill is high" into something you can actually act on.
To your question: it absolutely creeps back up if you leave it to willpower — and it did, the first time. We cut untagged spend way down in the audit, felt good, and three months later it had crept back to ~15% because new resources were still being created without tags. Discipline doesn't survive contact with a deadline.
What made it stick was moving it from a habit to a gate, in two layers:
Provisioning-time: tag policies that refuse to create an untaggable resource, and IaC that won't merge without owner/env/cost-center tags. If it can't be created untagged, the number can't grow.
Ongoing: a weekly "untagged spend" number on the same dashboard finance sees, with a threshold alert. The thing that actually changed behavior wasn't the alert — it was that finance had eyes on it. Once the untagged % was visible to the people who sign off on budget, it became someone's job to keep it near zero instead of everyone's job to remember.
So the honest answer: the habit didn't stick, but the system did. Untagged now sits around 2–3% (mostly a few managed-service resources that genuinely can't carry custom tags) instead of drifting back to 30%. The lesson I took: attribution isn't a cleanup you do once, it's a control you enforce at creation — same as any other invariant. If it lives in a runbook, it rots; if it lives in the pipeline and on finance's dashboard, it holds.
That 2-3% floor sounds like the honest baseline then, not a residual failure, some AWS managed resources genuinely don't expose a tag field, older RDS snapshot copies, a chunk of CloudFront distributions, service-linked roles that inherit from the parent service instead of carrying their own tags
For that specific slice, Cost Categories with rule-based mapping by linked account, service, or resource-name pattern gets you attribution without needing the tag at all, worth layering that under the tag-based system as the catch-all instead of accepting untaggable as permanently unattributed
The provisioning-gate-plus-dashboard combo is the real takeaway though, most cost-attribution advice stops at "add tags" and skips the part where the tag has to be unbypassable at creation time or it's optional the moment someone's in a hurry