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.
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
// 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.# 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-prodHow 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.