Azure managed disk pricing: you are paying for the tier, not the gigabytes
There is a disk in your subscription called disk-web01-old. It was detached in
2021 by someone who meant to come back to it. It holds 43 GB of data on a 128 GB
Premium SSD, and for four years it has been billing about 23.85 USD a month for
the privilege of existing.
The trap in azure managed disk pricing is that the meter does not care how much data is on the disk, or even how big you asked the disk to be. It cares which performance tier your requested size lands in.
So open a spreadsheet and estimate what deleting it saves. If you multiply 43 GB by a per-GB rate you will get roughly a quarter of the real number, and you will hand that quarter to someone who controls budgets.
How azure managed disk pricing actually works
Premium SSD v1 and Standard SSD are billed in tiers, not by the gigabyte. Each tier has a fixed monthly price and a fixed size. Ask for anything inside a tier's range and you pay that tier's price.
The Premium ladder starts like this:
| Tier | Provisioned size | What you get billed for |
|---|---|---|
| P6 | 64 GB | 64 GB |
| P10 | 128 GB | 128 GB |
| P15 | 256 GB | 256 GB |
| P20 | 512 GB | 512 GB |
Standard SSD uses an E ladder with the same sizes and lower prices: E6, E10, E15, E20. Standard HDD uses S.
So a 100 GB Premium SSD is a P10. It is billed as 128 GB. A 128 GB Premium SSD is also a P10, billed identically. Provisioning 100 GB rather than 128 GB saves you nothing at all, and costs you 28 GB of headroom you have already paid for. If you have ever carefully specified 100 GB to be frugal, you did the frugal thing and were charged for the unfrugal one.
It gets better at the bottom of the ladder. A 4 GB Premium disk is a P1, and the smallest tier still has a floor price. There is no such thing as a nearly-free Premium disk, which is why the "just a small one for testing" disks are worth finding.
The primary source for the ladders is Microsoft's own page on managed disk types and their tiers, which lists the sizes and IOPS per tier. Prices vary by region, so read them from the Retail Prices API rather than a blog post, including this one.
Reading the tier rather than guessing it
The disk object tells you its tier directly, so there is no need to infer it from
the size. DiskSizeGB is what you asked for. Sku.Name is what you are paying
for.
Get-AzDisk | Select-Object `
Name,
DiskSizeGB,
@{ n = 'Sku'; e = { $_.Sku.Name } },
@{ n = 'State'; e = { $_.DiskState } },
@{ n = 'Owner'; e = { if ($_.ManagedBy) { $_.ManagedBy.Split('/')[-1] } else { 'unattached' } } } |
Sort-Object DiskSizeGB -Descending
DiskState of Unattached is the interesting one. Reserved means the disk is
still associated with a VM that is deallocated, which is a different problem and
still billing.
Then map the SKU to a tier price from the Retail Prices API rather than hardcoding anything:
$region = 'uksouth'
$filter = "serviceName eq 'Storage' and armRegionName eq '$region' " +
"and priceType eq 'Consumption' and contains(meterName, 'P10')"
$uri = "https://prices.azure.com/api/retail/prices?currencyCode='USD'&`$filter=$filter"
(Invoke-RestMethod -Uri $uri).Items |
Select-Object meterName, unitOfMeasure, retailPrice
That endpoint needs no authentication, which makes it pleasant to work with and
easy to accidentally hammer. It is paged, and the filter syntax is OData, so the
$filter needs escaping in PowerShell. The
Retail Prices API reference
documents the query parameters.
The 3.7x problem, and why finance remembers it
Take that 128 GB Premium SSD in a mid-priced region. Billed as P10, it comes to roughly 23.85 USD a month. Estimate it instead at a flat per-GB Premium rate against the 43 GB actually in use and you land near 6.40 USD.
That is an understatement of about 3.7x. On one disk it is a rounding error. On the eight findings in the sample output on the front page of this site, which total 137.26 USD a month, a flat-rate estimate would have reported something closer to 40 USD and the whole exercise would have looked like it was not worth anyone's afternoon. That figure and the findings behind it are on the front page of this site if you want to see the shape of it.
The first version of my own script priced every disk at zero, which finance found unpersuasive, so I am not in a position to be smug about this. But the failure mode matters more than the arithmetic. If you go into a cost conversation with numbers that are 3.7x light, one of two things happens. Either nobody acts, because the saving looks trivial. Or somebody acts, discovers the real saving was four times larger, and quietly stops trusting your numbers for the next thing you bring them. The second outcome is worse and lasts longer.
What the tier model does not include
A tier price is a starting point, not an invoice. Several things move it, and none of them are visible in a disk object:
- Reservations. A one or three year disk reservation changes the effective rate. The Retail Prices API returns pay-as-you-go consumption prices unless you ask otherwise.
- Savings plans. Compute savings plans do not cover storage, but people assume they cover everything, so it is worth saying.
- Azure Hybrid Benefit. Affects licensing, not disk meters, and is another thing that gets blamed for a discrepancy.
- Enterprise agreement discounts. Your negotiated rate is not the retail one.
- Snapshots and bursting. Snapshots bill separately, and on-demand bursting adds a charge that a size lookup will never show you.
So the honest framing for any number you produce is that it is for prioritising work, not for billing. A figure good enough to decide which twelve things to delete first does not need to survive an audit by the finance team, and claiming otherwise is how you end up defending a spreadsheet instead of deleting a disk.
Where to start
Unattached disks are the easiest money in Azure because there is no counterargument. Nothing is using them. Nobody has to be consulted about downtime. The only real question is whether the data matters, which is what snapshots are for.
Look for the three patterns that account for most of them. Disks left behind when
a VM was deleted without the "delete with VM" option set. Disks from a migration
that finished eighteen months ago. And disks with temp, test, old or copy
in the name, provisioned by someone who fully intended to tidy up.
Once disks are done, the inventory of everything else nobody deletes covers the categories that bill quietly: unassociated public IP addresses, empty load balancers, dead network interfaces and snapshots. The rest of the azure cost notes work through idle machines and tagging, and the AzClean Toolkit runs all of it as ten read-only scripts if you would rather not write the queries yourself.
Questions
Does a 100 GB Premium SSD really cost the same as a 128 GB one?
Yes. Both provision into the P10 tier and both bill at the P10 rate. If you need 100 GB, ask for 128 GB and use the headroom, because you are paying for it either way.
Do unattached disks still cost money?
Yes, at the full tier rate. A managed disk bills from creation until deletion regardless of whether anything is attached to it. This is the single most common source of avoidable Azure spend I find in small estates.
Is a deallocated VM's disk still billing?
Yes. Deallocating a VM stops the compute meter, not the storage one. The disk
shows a DiskState of Reserved and continues to bill at its tier price, which
is why "we turned that server off" and "that server costs nothing" are different
statements.
Why not just use the Azure Cost Management API for all of this?
You can, and for actual spend you should. The difference is that Cost Management tells you what you were charged last month, aggregated by resource, while a disk inventory tells you what you are still being charged for right now and why. Use Cost Management to check your numbers, and an inventory to decide what to delete.
Which tier ladder applies to Premium SSD v2?
Neither. Premium SSD v2 bills provisioned capacity, IOPS and throughput independently rather than in fixed tiers, so the tier reasoning in this post does not apply to it. Check the SKU before assuming which model you are in.