Why does my Azure bill keep going up?
Most Azure bill increases carry three kinds of change at once: work the business added, resources and settings left over from earlier changes, and meters whose unit price rose while usage held steady. Only the leftovers are waste. Added work belongs in next year's budget, and a rate change comes down to a purchase decision about reservations and savings plans. Sorting an Azure bill increase into those three takes about an hour in Cost Management. The Reader role on the subscription covers the cost views, the CLI queries, the vault check and the Log Analytics query, provided the workspace sits in the same subscription. Exports, budgets and alert rules need Cost Management Contributor. The result is a dollar figure for each kind of change, which finance needs before anyone deletes a resource or signs a new hosting contract.
Sorting the increase into growth, drift and rate changes
Each line of the increase belongs to one kind, set by the decision that produced it.
- Growth. A line tied to a decision someone can name, such as a new application, more users or a new site.
- Drift. Resources and settings that outlived their reason: a VM size raised during an incident, a trial VM shut down from inside Windows, disks left over after a VM was deleted, diagnostic settings turned on everywhere, backup retention raised for a one-off records request.
- Rate change. The same quantity billed at a different unit price, as with a lapsed reservation, a legacy-series reservation that could not be renewed after July 1, 2026, or a retail price change.
Take a hypothetical distributor, Pemberton Valve and Fitting, running Windows application servers and a few Linux web servers in South Central US. Its monthly bill climbed from $5,180.00 to $7,655.60 over twelve months. Priced from Azure's retail price list for that region at pay-as-you-go rates and 730 hours a month, the $2,475.60 increase breaks down like this:
| Change | Kind | Monthly |
|---|---|---|
| Two D4s v5 Windows VMs for a new quoting application | Growth | $604.44 |
| Two P10 OS disks for those VMs | Growth | $35.84 |
| VM backup for the two VMs, $10 each | Growth | $20.00 |
| Backup storage for their data, 1,000 GB at GRS | Growth | $44.80 |
| SQL Server VM left at D16s v5 after an incident (was D8s v5) | Drift | $605.17 |
| Trial VM shut down inside Windows, still Stopped (allocated) | Drift | $302.22 |
| Two P30 data disks left over when two earlier test VMs were deleted | Drift | $245.76 |
| Diagnostic settings: log ingestion up from 4 GB to 120 GB a month | Drift | $317.40 |
| Backup retention raised from 30 to 180 daily points, 2,400 GB more at GRS | Drift | $107.52 |
| Three one-year D4s v5 reservations on Linux web servers lapsed | Rate change | $192.45 |
| Total | Growth $705.08, drift $1,578.07, rate change $192.45 | $2,475.60 |
Growth accounts for 28 percent of the increase, drift for 64 percent and rate change for 8 percent. Only the $1,578.07 of drift is a cleanup target.
The resize line hides a license charge. Azure bills the Windows Server license per core, so doubling the SQL Server VM from D8s v5 to D16s v5 took its license portion from $268.64 to $537.28 a month. Of the $605.17 the resize added, $268.64 is Windows. Pemberton brought its own SQL Server licenses, so no SQL meter appears. SQL Server on a VM is licensed per virtual core, and if those licenses covered 8 cores, the resize left 8 uncovered with nothing on the bill to show it. A VM built from a pay-as-you-go SQL Server Standard image carries the license as a meter instead, at $0.10 per vCPU hour, and on that VM the same resize adds another $584.00 a month.
The kinds blur at the edges. Backup storage grows with data and with retention settings, and only the retention part is drift. A VPN gateway upgraded for a new office counts as growth only if the tunnel needs the throughput. When a line could go either way, ask which decision produced it. Any line nobody can explain counts as drift until someone claims it.
Five Cost Management views in one hour
Microsoft's page on analyzing unexpected Azure charges documents the starting point: Cost analysis at subscription scope, the Daily costs view, a date range of Last year and Monthly granularity. Switch the metric to Amortized cost before comparing months. Actual cost books an upfront reservation purchase in the month it was bought and shows the VMs it covers at zero after that, which puts a spike in one month and hides compute cost in the rest. A small estate needs no tagging program for this as long as each application sits in its own resource group.
Service totals, month over month
Open the Cost by service view and compare months. A new application shows as a step in Virtual Machines in the month it went live, with Storage and Backup stepping up alongside. Drift tends to appear as a service that climbed with no project to explain it, and Log Analytics after an incident is the usual one. When Virtual Machines rises while the VM count holds, the cause is a resize or a rate change, and the meter grouping further down separates the two.
Backup growth lands in two places. Vault storage bills under Backup. Instant restore snapshots bill as storage, on a restore point collection that Azure Backup keeps in its own resource group, named AzureBackupRG_ followed by the region and a number. The backup policy type sets the size of that line. Standard policy snapshots are incremental, while the Enhanced policy's first snapshot is a full copy of each disk, so a 100 GB VM with 2 percent daily change and five days of snapshots bills 10 GB of snapshot storage on Standard and 108 GB on Enhanced.
If Log Analytics grew, the Usage table names the table responsible:
Usage
| where TimeGenerated > ago(32d)
| where StartTime >= startofday(ago(31d)) and EndTime < startofday(now())
| where IsBillable == true
| summarize BillableDataGB = sum(Quantity) / 1000. by bin(StartTime, 1d), DataType
| render columnchartIngestion is free for the first 5 GB a month per billing account, then $2.76 per GB in South Central US, so Pemberton's 120 GB month costs $317.40. The 4 GB it used to send cost nothing, which is how a diagnostic setting turned on during an incident takes this line from zero to hundreds of dollars without anyone noticing.
Group by resource, then sort by change
The Resources smart view lists every resource that billed in the period, deleted ones included, and flags month-over-month changes. New VMs named after an approved project are growth. Drift looks different here: disks with no parent VM, public IP addresses left after a deletion (a Standard static IPv4 address costs $3.65 a month whether or not anything uses it), and VMs named trial or temp whose cost never moved.
Check the Recovery Services vault as well. A VM deleted without stopping its backup leaves a backup item behind, and Azure keeps that item's most recent recovery point, billing for it, until someone stops protection and deletes the data.
These two commands list managed disks attached to nothing and VMs shut down from inside the guest OS:
az disk list --query '[?managedBy==`null`].[id]' -o tsv
az vm list -d --query "[?powerState=='VM stopped'].{name:name, rg:resourceGroup}" -o tableRunning az disk show on each disk returns LastOwnershipUpdateTime, the time it was detached. A VM in the Stopped (allocated) state, which is what a shutdown inside Windows produces, bills at the full compute rate. Compute charges stop only at Stopped (deallocated), so Pemberton's trial VM costs $302.22 a month sitting where it is. The view names resources, and someone who knows the applications still has to say whether each one is wanted.
Meter swaps after a resize
Filter to one resource group, group by Resource, then group by Meter. Cost changes arrive as new, removed or changed costs, and a resize surfaces as a new meter replacing a removed one on the same resource. On Pemberton's SQL Server VM, the D8s v5 Windows meter at $605.17 drops to zero and a D16s v5 meter at $1,210.34 appears.
Read without that pairing, a per-meter comparison shows $1,210.34 of growth on one meter and a $605.17 saving on another. Pair meters by resource before classifying anything.
Resizing a reserved VM splits its hours. Instance size flexibility lets a D8s v5 reservation cover half the compute hours of a D16s v5, so the resized VM shows reservation hours and pay-as-you-go hours on the same resource.
License meters show up in the same grouping. A pay-as-you-go Windows VM carries the license inside its VM meter, whose product name ends in Windows. On a reserved Windows VM the license bills separately, as Reservation-Windows Svr with a core count, unless Azure Hybrid Benefit applies, so that meter's presence answers whether Hybrid Benefit is on.
When a reservation lapses
Group by pricing model (OnDemand, Reservation, SavingsPlan) by month. A lapse shows as VM hours shifting out of Reservation into OnDemand in the month after expiry while the VM count stays put. On a Windows VM the lapse also swaps meters: the reserved compute meter and its Reservation-Windows Svr meter stop, and the combined Windows pay-as-you-go meter starts. Pemberton's lapsed reservations covered Linux web servers, so each one stays on a single meter.
The renewal defaults set up a lapse one term later. Auto-renew is on by default at purchase, and the replacement reservation that renewal creates has auto-renew off, so a reservation renews once unless someone sets the flag again. Renewal can also fail outright. Azure skips it if payment fails, if the price changed after the renewal price was locked, or if the reservation was split, merged or transferred after renewal was set. Notices go out 30 days before expiry and on the expiry date. For CSP customers, they go to the partner's notification contact.
Older VM series lose some reservation terms entirely. Since July 1, 2026, Microsoft has stopped selling or renewing one-year reservations for series such as Dv2, Dsv2 and Fsv2, and both one- and three-year reservations for Dv3, Dsv3, Ev3 and Esv3, so the auto-renew flag does nothing for them. Existing ones run to term, then bill at pay-as-you-go unless the VM moves to a savings plan or a newer series. A Dsv3 SQL Server VM on a three-year reservation bought in late 2023 reaches that point in late 2026.
A one-year D4s v5 reservation costs $1,245, or $103.75 a month against $167.90 at pay-as-you-go, so each of Pemberton's three lapses added $64.15 a month. Reservations cover compute only, so on a Windows VM the license portion bills the same before and after a lapse.
Volume against price in the export
Export two months of cost details on the amortized dataset, so reservation hours carry their effective rate. Match rows on ResourceId and MeterId, then split each meter's change in two using Quantity and EffectivePrice:
- Volume effect: the change in quantity, multiplied by last month's price.
- Price effect: the change in price, multiplied by this month's quantity.
The two add up to the change in cost.
| Meter | Last month | Last price | This month | This price | Volume effect | Price effect |
|---|---|---|---|---|---|---|
| D4s v5 Linux web server, reservation lapsed | 730 h | $0.1421 | 730 h | $0.23 | $0.00 | $64.15 |
| D4s v5 Windows, new quoting VM | 0 h | none | 730 h | $0.414 | $302.22 | $0.00 |
A gap between PayGPrice and EffectivePrice on the same meter is the discount the account receives. When that gap closes, a reservation or savings plan stopped applying. Four limits apply to the split: a new meter has no earlier price, so all of it counts as volume; a Windows lapse or a resize changes the MeterId, so pair those rows by ResourceId first; a CSP export shows retail prices; and the free first 5 GB of log ingestion makes that meter's effective price shift with volume.
Where a quick cleanup loses data
Each obvious fix on Pemberton's list has a failure mode.
| Fix | What goes wrong | Safer way |
|---|---|---|
| Resize the SQL Server VM back to D8s v5 | A running VM restarts. If the size is not on the current hardware cluster, the VM needs deallocating, which releases dynamic IP addresses. If someone raised SQL Server's max server memory to fit the 64 GiB of a D16s v5, the 32 GiB VM cannot back it | Lower max server memory to fit 32 GiB first, book a maintenance window with the application owner, and confirm nothing depends on a dynamic IP. Saves $605.17 a month, $268.64 of it license |
| Deallocate the trial VM | Compute stops, and its disks keep billing | Run az vm deallocate -g <rg> -n <vm>, then snapshot and delete after a hold period in which nobody objects |
| Delete orphaned disks | Deletion is permanent | Run the unattached-disk script from the Azure docs in list mode first (deleteUnattachedDisks=0), check LastOwnershipUpdateTime, and snapshot anything in doubt |
| Shrink a disk raised during an incident | Microsoft says "Shrinking an existing disk isn't supported and might result in data loss." | Create a smaller disk, copy the data, and swap it in |
| Switch the backup vault from GRS to LRS | Redundancy locks once backup is configured. LRS means a new vault and new protection, and the old recovery points keep billing until their retention runs out | Keep GRS unless the backups have another off-region copy. In a single-region estate the GRS copy is the only one, and on Pemberton's added retention it costs $53.76 a month |
| Cut backup retention from 180 days back to 30 | Recovery points older than the new limit are marked for pruning at the next cleanup job, which runs about every 24 hours, including the ones kept for the records request | Confirm the request is closed. If it is still open, restore the recovery point it needs to a separate disk before changing the policy |
| Turn on the Log Analytics daily cap | Collection stops for the rest of the 24 hours, data above the cap still bills, and the cap was never meant as the main way to reduce data | Find the table with the Usage query, then change that diagnostic setting or filter it with an ingestion-time transformation. If an Azure Policy assignment deployed the setting, change the assignment, because a remediation task puts a deleted setting back |
Azure Hybrid Benefit removes the Windows license meter for businesses that own Windows Server licenses with Software Assurance or qualifying subscription licenses. On Pemberton's resized SQL Server VM, that meter is $537.28 of the $1,210.34 monthly rate, and a business without those licenses has no Hybrid Benefit to apply. Each VM needs at least 8 core licenses, so a 4-vCPU VM still uses 8. The flag applies without a restart:
az vm update --resource-group <rg> --name <vm> --set licenseType=Windows_ServerSetting that flag without the licenses breaks Microsoft's licensing terms, and the Azure bill gives no sign of it.
Work in this order. Reversible changes go first: deallocate stopped VMs and set the renewal flag on replacement reservations. Resizes follow in a maintenance window. Deletions and retention cuts wait for a hold period, and vault changes go last, because they open a gap in backup protection.
Anomaly detection trained on the last 60 days
Cost Management's anomaly detection compares each day with a forecast trained on the previous 60 days. It runs 36 hours after the day ends (UTC), works only at subscription scope, allows five alert rules per subscription, and sends one email at detection. That design catches an Azure cost spike, a day that jumps against its forecast. A lapsed D4s v5 reservation adds about $2.11 a day, which disappears against a $250 day, and a steady climb pulls the forecast up with it.
Set these instead:
- Budgets at the growth-priced figure from the sort: one for the subscription and one per application resource group for its share. Pemberton's subscription budget is $5,900, last year's $5,180.00 plus $705.08 of growth, rounded up, with an actual-cost alert at 90 percent and a forecast alert at 100 percent. Budgets notify and never stop resources. They evaluate once a day on cost data that is typically 8 to 24 hours old, and they count actual cost, so filter Publisher Type to Azure and Charge Type to Usage to keep a reservation purchase from tripping the alert.
- An alert on log ingestion, using a log search alert on
Usage | where IsBillable | summarize DataGB = sum(Quantity / 1000). Pemberton's normal month was 4 GB, so a 1 GB daily threshold leaves plenty of headroom. - Calendar entries 30 days before each reservation ends. CSP customers can ask the partner to forward the renewal notices.
- A monthly rerun of the volume and price split on the last two months of the export.
No alert watches unit prices, so a retail price change on a meter already in use shows up only in that monthly split.
Stay on Azure when spend tracks the business
Divide spend by a measure of the business and compare the two periods. Pemberton's orders rose from 9,000 to 11,700 a month. Spend per 1,000 orders went from $575.56 to $654.32, so growth alone does not explain the bill. On the growth-priced bill of $5,885.08, the figure is $503.00, lower than a year ago. Unit cost on Azure fell as orders grew, and the rest of the increase came from drift and the lapsed reservations. Pemberton's fix is the cleanup plus the budget, and the workload stays where it is. Pick the measure that fits the business, whether orders, users or locations, and keep it fixed across both periods.
Other situations point the same way:
- Applications built on Azure managed services such as Azure SQL Database, App Service or Functions. Leaving means rebuilding those pieces, while the drift cleanup takes a maintenance window and a hold period.
- Workloads that need several regions. Petrichor runs in one U.S. data center, and a hyperscaler suits a multi-region workload better.
- Reservations or savings plans with most of their term left. Counting engineer hours and support fees in cloud TCO covers how to weigh open commitments.
Paying on demand for servers that never shut down
Once the drift is gone, look at what still moves month to month. Servers that show 730 hours in every month of the export never used the hourly billing they pay for. A reservation or savings plan lowers that compute rate in exchange for a one- or three-year commitment. What keeps moving after that sits in per-GB meters: log ingestion at $2.76 per GB past the free 5 GB, internet egress at $0.087 per GB after the first 100 GB, and backup storage per GB-month. A pay-as-you-go subscription cannot turn on Azure's spending limit, which exists only on credit-based offers such as the free account. Of those three meters, only log ingestion has a hard stop, the daily cap, and it works by dropping data.
Moving has its own cost in migration work and a stretch of paying for both environments.
We have run Linux and Windows servers on our own hardware since 2015. Our engineers built that cloud to run on-premises software in unusual environments, the kind its vendors never designed for a cloud. When a customer on our managed hosting for business applications needs database work, a backup or a restore, the customer files a ticket and a Petrichor engineer does it. We agree the resources a workload uses with each customer, and the service agreement states the rates and any overage terms.
Bring last quarter's Azure invoices, or the two-month cost details export, to the call. Book a call with a consultant