Azure VMs stopped but still billed: how to find and fix them
Shutting a virtual machine down from inside its operating system doesn't stop the Azure bill. Until the VM is deallocated, Azure keeps its hardware reserved and charges the full compute price, every hour.
Two kinds of “off” in Azure
An Azure VM can be off in two ways, and only one of them saves money:
- Stopped: the operating system is shut down, but the VM still holds its hardware. Azure charges for compute as if it were running. This is what you get when you shut down from inside Windows or Linux, or run az vm stop.
- Stopped (deallocated): the hardware is released and compute charges stop. This is what the Stop button in the Azure portal does, and what az vm deallocate does.
In both states you still pay for the VM's disks, and for any static public IP address attached to it.
What it costs
A stopped VM costs its full hourly compute rate for every hour of the month, about 730 hours. A VM at $0.20 an hour costs about $146 a month while doing nothing. Larger sizes, Windows licences and GPU VMs cost several times that.
How to find stopped VMs
In the Azure portal, open Virtual machines and look at the Status column. “Stopped” is still billed; “Stopped (deallocated)” is not.
With the Azure CLI, list the stopped VMs in the current subscription:
az vm list -d --query "[?powerState=='VM stopped'].{name:name, resourceGroup:resourceGroup}" -o tableAcross every subscription you can read, with Azure Resource Graph (the same query the scanner uses):
resources | where type =~ 'microsoft.compute/virtualmachines' | where tostring(properties.extended.instanceView.powerState.code) =~ 'PowerState/stopped' | project name, resourceGroup, subscriptionId, location
How to fix it
Deallocate the VM. In the portal, open the VM and click Stop. With the CLI:
az vm deallocate --resource-group <resource-group> --name <vm-name>
With PowerShell (Stop-AzVM deallocates unless you add -StayProvisioned):
Stop-AzVM -ResourceGroupName <resource-group> -Name <vm-name> -Force
Before you deallocate, check two things:
- Anything on the temporary disk (often D: on Windows or /dev/sdb on Linux) is lost.
- A dynamic public IP address is released and may change when the VM starts again. Make it static first if anything depends on it.
How to stop it happening again
- Ask your team to stop VMs from the portal, CLI or PowerShell, not from inside the operating system.
- Use the VM's Auto-shutdown setting for test and development machines. It deallocates the VM at a set time each day.
- If a VM has been stopped for weeks, ask whether it's still needed at all. Deleting it also ends the disk charges.
How Cloud Cost Scanner checks this
The scan reads each VM's power state with your read-only access. For every VM that is stopped but not deallocated, it prices the waste at the Azure retail rate for that size and region over 730 hours, and gives you the exact deallocate command. It never changes anything in your Azure.