Skip to main content
Back to the Azure Footguns Database
CostMediumCommonAZF-0098

A running VM that has done nothing for a month keeps billing full compute until someone deallocates it

A VM that is powered on and billing but has shown essentially no CPU or disk activity for a month or more. Advisor's shutdown rule can miss it, and nothing else in Azure will tell you to deallocate it.

Footgun ID
AZF-0098 (Azure Footgun No. 98)
Azure service
Azure Virtual Machines
Resource type
Microsoft.Compute/virtualMachines
Updated
October 28, 2026

What it is

A virtual machine in the running power state whose CPU and disk activity have been flat at the floor for 30 days or more. It is not stopped, not oversized in the usual sense, and not broken; it is simply doing nothing while billing its full compute rate every hour. The dev box someone left on in March, the migration source that was cut over months ago, the "temporary" test server: all of them look identical to a busy VM on the invoice.

Impact (cost)
~$140/month at list price for an idle Standard_D4s_v5 (Linux, East US) that could be deallocated to $0 compute
Basis: Azure list price. Figures are estimates, not measurements.

Why it happens

Azure bills a running VM on its provisioned SKU, not on work done, so a machine at 1% CPU costs exactly what the same machine costs at 90%. Azure Advisor does have a shutdown recommendation for this case: it suggests shutting a VM down when CPU and outbound network utilization both sit near zero across its lookback period. But that lookback defaults to seven days (it is configurable up to 90 per subscription), so a VM that did one real thing last Tuesday is not idle this week and will be idle next week, and the recommendation flickers. Advisor also wants near-zero network traffic, which a machine still receiving monitoring and management chatter may never show. And a recommendation is only a recommendation: it sits in a list, gets postponed or dismissed, and nothing in the platform follows up. Nobody owns turning the machine off.

Idleness is also harder to see than it sounds. An idle Windows VM still spikes to high CPU on Windows Update and Defender scan days, so a single-day peak proves nothing. Average CPU hides a nightly batch job that is real work. Memory is useless as a signal because the OS baseline sits at the same level whether the machine serves traffic or not. The honest test is sustained: the daily CPU maximums stay in the low single digits on nearly every day across a long window, and the disks are barely touched. That is a longer look than anyone takes at a VM that is not causing problems.

This is a different failure from AZF-0010, where the VM is stopped but still reserved, and from AZF-0022, where the VM works but is too big. The fix here is not a resize; it is deallocating or deleting a machine nobody is using.

What it costs / blast radius

A Standard_D4s_v5 (4 vCPU, 16 GiB) runs about $0.192/hour at Linux pay-as-you-go list price in East US, roughly $140/month or $1,680/year for a machine that does nothing; the Windows rate is nearer $274/month with the licence. (List price; your contract, region, and reservations will differ.) Deallocating stops the compute meter outright; the disks and any static public IP keep billing until you delete them, which is why the fix below mentions snapshots. Multiply by the forgotten boxes that accumulate in many subscriptions and it adds up to a real line item.

One caution before you pull the plug: CPU and disk silence does not prove nobody needs the VM. A jump box, a listener, or a DNS forwarder can idle at 1% CPU and still matter. Review the VM's owner and network traffic before deallocating, and prefer deallocate over delete until someone confirms.

See it

Find them: list running VMs, then check two months of daily CPU maximums and disk IOPS for each
// Step 1: candidates are the VMs that are actually powered on.
Resources
| where type =~ 'microsoft.compute/virtualmachines'
| where tostring(properties.extended.instanceView.powerState.code) =~ 'PowerState/running'
| project name, resourceGroup, subscriptionId, location,
vmSize = tostring(properties.hardwareProfile.vmSize), id
// Step 2 (per VM, az CLI): daily MAX 'Percentage CPU' and daily AVERAGE
// 'OS Disk IOPS Consumed Percentage' / 'Data Disk IOPS Consumed Percentage'
// over the last two months. Both flat near the floor on nearly every day = idle.
Fix: confirm with the owner, deallocate (not just stop), then snapshot and delete if nobody claims it
# Deallocate releases the compute host and stops the compute meter.
# A guest-OS shutdown does NOT (see AZF-0010).
az vm deallocate --name vm-app-07 --resource-group rg-prod

# Disks and any static public IP keep billing while deallocated. Once the
# owner confirms the VM is unneeded, snapshot the OS disk and delete the VM
# with its disks, NICs, and public IPs.
az snapshot create --name vm-app-07-osdisk-snap --resource-group rg-prod \
--source "$(az vm show --name vm-app-07 --resource-group rg-prod \
--query storageProfile.osDisk.managedDisk.id -o tsv)"
az vm delete --name vm-app-07 --resource-group rg-prod --yes
# Deleting the VM leaves its disks, NIC, and public IP behind unless their
# delete options were set at creation (see AZF-0003). Remove them too:
az disk delete --name vm-app-07_OsDisk --resource-group rg-prod --yes
az network nic delete --name vm-app-07-nic --resource-group rg-prod
az network public-ip delete --name vm-app-07-pip --resource-group rg-prod

How StratoLens helps

StratoLens flags running VMs that have sat idle for a month or more, with their monthly cost and how long they have been idle, automatically and continuously across every subscription, inside your own tenant. It keeps that finding separate from right-sizing because the right action is different: deallocate, not downsize.

Start your 14-day free trial

Every feature unlocked. Deploys to your Azure tenant. No data leaves your tenant.

When the 14 days end, billing starts on your Azure invoice at your tier. One toggle in Settings turns it off, any time.