Compute waste is loud. Somebody notices the oversized virtual machine eventually. Storage waste is quiet: it never pages anyone, it grows a little every day, and the per-gigabyte price is small enough that nobody thinks it is worth an afternoon.
On a mature Azure estate, storage and data transfer together are routinely 15 to 25 percent of the bill, and most of it is structural rather than volume-driven. Here is what we check.
Redundancy is the biggest multiplier
A storage account's redundancy setting multiplies its price before any other decision matters. Geo-redundant storage costs roughly twice locally-redundant, and the read-access variant more again. That is the right choice for data whose loss would end the business. It is the wrong choice, made by default, for build artefacts, logs, temporary exports and the landing zone of a pipeline that re-reads from the source system anyway.
Go through the accounts and ask one question per account: if this region were gone for a week, would we restore this from somewhere else? If yes, zone-redundant or locally-redundant is enough. Changing redundancy on a live account is an operation with real constraints, so plan it, but the saving is a straight halving on everything in the account.
Tiers, and the penalty nobody reads
Hot, cool, cold and archive trade storage price against access price. Moving rarely-read data down a tier is obviously right, and it is also where teams lose money, because each cool tier carries a minimum retention period with an early-deletion charge if you move or delete the blob before it elapses. Cool is 30 days, cold 90, archive 180. A lifecycle rule that tiers aggressively on day one and deletes on day 45 pays a penalty on every object.
Two rules that work:
- Tier on last access time, not creation date, if the access pattern is uneven. Azure tracks it when you enable access tracking on the account, and it stops you cooling the one file everyone opens.
- Make the delete rule's age larger than the tier's minimum retention, always. If you want a 30-day lifecycle, keep it hot.
Archive deserves its own caution: rehydration takes hours and costs real money per gigabyte. It is the right home for backups and compliance copies, and the wrong home for anything an engineer might need during an incident.
Transactions can exceed storage
Cool and archive tiers charge more per transaction than hot. A workload doing millions of small reads against cool storage can pay more in transactions than it saves in storage price, which is the most common way a well-intentioned lifecycle policy backfires. If a container serves a high-frequency read pattern, leave it hot regardless of object age.
The same arithmetic applies to Premium block blobs: high price per gigabyte, very low price per transaction. For small objects read constantly, it is sometimes the cheapest option in total, not the most expensive.
The things that accumulate
- Orphaned managed disks and snapshots from deleted virtual machines and old restore points. Every estate has them, and on estates that have run for years they are substantial.
- Soft-delete and versioning with no expiry. Both are on by default in many templates. Both are correct to enable. Neither is correct to leave unbounded, because you are then storing every version of every object forever. Add a lifecycle rule that expires old versions and soft-deleted blobs after 30 to 90 days.
- Incomplete multipart uploads. Failed uploads leave uncommitted blocks that bill and that no blob listing shows you. A lifecycle rule cleans them up.
- Backup vaults with a policy nobody has revisited, holding daily recovery points from two years ago at a retention somebody chose to be safe.
- Empty or forgotten file shares on Premium, which bill on provisioned capacity rather than what you used.
Data transfer, in the order it bites
Inbound is free. Everything else is a matter of where the boundary sits.
- Cross-region replication and traffic. Geo-redundant storage replicates continuously, and a chatty application split across two regions pays on every hop. This is usually an architecture finding, not a knob.
- Availability zone traffic. Cross-zone transfer inside a region is billed. In a multi-zone Kubernetes cluster with zone-unaware services, most pod-to-pod traffic crosses a boundary, the same effect we describe in AKS spend.
- Internet egress, which has a free monthly allowance and then tiers down with volume. Put a CDN in front of anything that serves the same bytes repeatedly; the CDN's own egress price is lower and the origin stops paying entirely for cached objects.
- NAT gateway data processing, charged per gigabyte on top of the hourly fee. Private endpoints and service endpoints keep traffic to Azure services off the NAT path entirely, which is a cost argument on top of the security one made in private endpoints and the DNS problem.
- Private endpoint data processing. Private Link is not free per gigabyte either. For very high-volume internal traffic, service endpoints cost less; the trade-off is the weaker security boundary.
Then, and only then, reserve
Azure sells reserved capacity for blob storage and for some database and file workloads, at a discount for a one or three year commitment. It works, and it goes last, for exactly the reason it goes last on the Azure bill: reserving a footprint you were about to cut in half means paying a discounted rate for data you should have deleted.
What to do this week
List every storage account with its redundancy setting, and every managed disk with no attached virtual machine. The first list tells you whether you are paying twice for data you would not miss. The second is free money, and it is always longer than anyone expects. We do both in the cost phase of a cloud engagement.