Azure VM stopped vs deallocated: how to deallocate

Published · Updated · OhChimp Engineering

Azure VM in Stopped allocated state billing full compute next to the same VM deallocated at zero compute cost

You shut an Azure VM down last month and assumed the bill stopped with it. Open the cost analysis and the compute charge is still there, every hour, for a machine nobody has touched. A stopped Azure VM keeps its CPU and memory reserved, so Azure keeps charging you 100% of the compute rate until you release it. Deallocate the VM and that compute charge drops to zero. The fix is one word, and which tool you shut the VM down with decides whether you have ever run it.

Why a stopped VM still costs full price

Azure has two different off states, and only one of them stops the compute meter.

When you shut a VM down from inside the guest OS, the machine powers off but Azure keeps the physical host reserved for it. That state is called Stopped (allocated). The vCPUs and RAM are still set aside for you, so you keep paying the full compute rate as if the VM were running.

Deallocation is the state that saves money. When a VM is deallocated, Azure releases the compute back to the pool. You stop paying for vCPUs and memory completely. You keep paying only for the managed disks attached to the VM, because those still exist on storage whether the VM runs or not.

Here are the two off states side by side.

State How you get there Compute billed Disks billed Dynamic public IP Temp disk
Stopped (allocated) Guest OS shutdown, az vm stop, Stop-AzVM -StayProvisioned Yes, full rate Yes Kept Kept
Deallocated Portal Stop, az vm deallocate, Stop-AzVM No Yes Released Wiped

As of June 2026, a Standard_D4s_v5 (4 vCPU, 16 GiB) lists around $0.192 per hour for pay-as-you-go Linux in East US on the Azure VM pricing page, which is about $140 per month. Its 128 GiB Premium SSD P10 OS disk lists around $19.71 per month on the managed disks pricing page. Leave that VM Stopped (allocated) and you pay the full $140 of compute for a machine doing no work. Deallocate it and compute drops to zero, leaving roughly $20 a month for the disk you chose to keep.

Windows VMs make the trap cost more. Azure folds the Windows Server license into the per-hour rate, so a Windows box bills for the operating system on top of the hardware. Leave it Stopped (allocated) and the license charge rides along with the compute, so deallocating saves you both. The exception is Azure Hybrid Benefit: bring your own Windows Server license and you already dropped that charge, so a stopped Windows VM wastes only the hardware rate.

Azure Reservations and savings plans bill whether the VM runs or not, so deallocating a covered VM only saves money when the reservation moves to another running VM of the same size. For pay-as-you-go VMs, the full compute charge simply stops.

The trap: "Stop" does not mean deallocate

Shutting down from inside the operating system never deallocates. The guest has no way to tell Azure to release the host, so the VM sits in Stopped (allocated) and bills on. A shutdown /s on Windows or a shutdown on Linux saves you nothing.

The Azure CLI sets the same trap. az vm stop powers the VM off but leaves it allocated and billing. The command that actually stops the meter is az vm deallocate.

Azure PowerShell flips the trap around. Stop-AzVM on its own deallocates the VM, so the default does the right thing and the meter stops. Add -StayProvisioned and it stops the VM without deallocating, and Azure keeps charging you for the stopped machine. The safe command and the expensive one sit one flag apart, so an old runbook that still passes -StayProvisioned keeps paying full compute for a box that does nothing.

The portal is the exception, because its Stop button deallocates the VM for you. So a team that stops VMs by clicking in the portal pays nothing extra, while a team that automated the same task with az vm stop or a guest shutdown script has been paying full compute the whole time.

How to deallocate an Azure VM

Any of these three releases the host and stops the compute charge. Pick the one that matches how you already manage the VM.

  1. Azure portal. Open Virtual machines, select the VM, click Stop in the toolbar, and confirm. The status column reads Stopped (deallocated) once the host is released.
  2. Azure CLI. Run az vm deallocate --resource-group my-rg --name my-vm. Use this command in place of az vm stop, which leaves the VM allocated.
  3. Azure PowerShell. Run Stop-AzVM -ResourceGroupName my-rg -Name my-vm. Leave -StayProvisioned off, because that flag keeps the VM allocated and billing.

Starting the VM again brings back the same disks. A dynamic public IP and the temp disk do not survive, so read Deallocate without surprises before you run any of these on a machine other systems point at. The sections below find every stopped-but-billing VM first, so you deallocate the whole list instead of the one you remembered.

Find your stopped-but-billing VMs by hand

Power state does not appear in a plain az vm list. You need the -d flag, which pulls the instance view and includes powerState.

List every VM with its power state in the current subscription:

az vm list -d \
  --query "[].{name:name, rg:resourceGroup, state:powerState}" \
  --output table

The state you are hunting is VM stopped. That is the allocated-but-off state that still bills compute. VM deallocated is the safe one, and VM running is working as intended.

The exact string changes with the surface you read it on, which trips people up when they search for it. The portal shows Stopped (allocated) and Stopped (deallocated). The CLI returns VM stopped and VM deallocated. The REST API and ARM templates use PowerState/stopped and PowerState/deallocated. All three describe the same two states.

Azure Advisor flags low-use running VMs under its cost recommendations, but a VM that is stopped and still allocated can slip past it, so the sweep below is worth running yourself.

Filter straight to the VMs that are costing you money for nothing:

az vm list -d \
  --query "[?powerState=='VM stopped'].{name:name, rg:resourceGroup, size:hardwareProfile.vmSize}" \
  --output table

Every row is a VM you are paying full compute for while it does no work. The size column tells you which ones hurt most, since a stopped GPU or memory-optimized VM burns far more than a stopped B-series box.

The query is scoped to one subscription at a time, so sweep every subscription you own in a loop:

for sub in $(az account list --query "[].id" -o tsv); do
  az vm list -d --subscription "$sub" \
    --query "[?powerState=='VM stopped'].{name:name, rg:resourceGroup, size:hardwareProfile.vmSize}" \
    --output table
done

Sweep every subscription in one query

That loop works, and it gets slow. Each subscription is a separate round trip, and every -d call pulls the instance view for each VM inside it. Across twenty subscriptions you are waiting minutes for an answer you wanted in one shot.

Azure Resource Graph does the whole sweep in a single query. It reads a pre-indexed copy of your resource metadata, so it returns in about a second regardless of how many subscriptions you own.

Add the extension once:

az extension add --name resource-graph

Then ask for every VM sitting in the allocated-but-off state:

az graph query -q "
resources
| where type =~ 'microsoft.compute/virtualmachines'
| extend powerState = tostring(properties.extended.instanceView.powerState.code)
| where powerState == 'PowerState/stopped'
| project name, resourceGroup, subscriptionId, vmSize = tostring(properties.hardwareProfile.vmSize)
| order by vmSize asc
" --output table

PowerState/stopped is the state that still bills compute, and PowerState/deallocated is the one that stops the compute charge. Those are the same two states the CLI prints as VM stopped and VM deallocated, spelled the way the REST API writes them.

Two things will bite you here. Resource Graph returns results for every subscription your account can read, so pass --subscriptions when you want to narrow the blast radius of the report. And property paths under properties are case sensitive, so a lowercase powerstate returns empty results instead of an error, which reads exactly like a clean bill of health.

Find and deallocate them from Azure PowerShell

Every command above is Azure CLI. If your runbooks are PowerShell, the cmdlets do the same work, and the discovery step hides a trap of its own.

Get-AzVM returns the model view by default, and the model view carries no power state. Add -Status and every VM comes back with a PowerState property. Leave the switch off and that property is absent rather than empty, so a filter on it quietly matches nothing and reads like good news.

Get-AzVM -Status |
  Where-Object { $_.PowerState -eq 'VM stopped' } |
  Select-Object Name, ResourceGroupName, PowerState

PowerShell reads the same instance view as az vm list -d, so the strings match. A VM reading VM stopped is powered off with its host still reserved and billing, and one reading VM deallocated has handed that host back.

Resource Graph ships a cmdlet too, so the cross-subscription sweep is one call here as well. Install the module once:

Install-Module -Name Az.ResourceGraph -Scope CurrentUser

Then run the query from the previous section:

Search-AzGraph -UseTenantScope -First 1000 -Query "
resources
| where type =~ 'microsoft.compute/virtualmachines'
| extend powerState = tostring(properties.extended.instanceView.powerState.code)
| where powerState == 'PowerState/stopped'
| project name, resourceGroup, subscriptionId, vmSize = tostring(properties.hardwareProfile.vmSize)
"

Two flags there are load-bearing. -UseTenantScope runs the query across every subscription in the tenant, which is the job the CLI loop was doing. And -First defaults to 100, so a tenant with more than a hundred VMs returns a truncated list that looks exactly like a complete one. The ceiling is 1000, and past that you page with -SkipToken.

Deallocating is a single cmdlet, and the default is the safe one.

Stop-AzVM -ResourceGroupName my-rg -Name my-vm -Force

Stop-AzVM deallocates unless you add -StayProvisioned, and -Force skips the confirmation prompt so the command runs unattended. Starting the VM again is the mirror image.

Start-AzVM -ResourceGroupName my-rg -Name my-vm

The public IP and temporary disk checks further down apply the same way, whichever tool you drive.

The scale set instances the sweeps never return

Every sweep so far asks Azure for virtual machines, but a scale set instance is not always one of them.

Scale sets carry the same two off states, behind the same pair of commands that sound interchangeable. az vmss stop powers instances down and leaves them allocated, so compute keeps billing. az vmss deallocate releases the compute, and the REST reference states you are not billed for the capacity it hands back. On one VM that mistake costs the $140 a month from earlier, and on a scale set it costs that per instance.

Whether the sweeps above already found them comes down to how the scale set was created.

Flexible orchestration instances are ordinary Azure VMs. Microsoft's orchestration modes comparison gives their virtual machine type as Microsoft.Compute/virtualMachines, the same type a standalone VM carries. Every command above already returns them, so there is nothing extra to run.

Uniform orchestration instances are a different resource type, Microsoft.Compute/virtualMachineScaleSets/virtualMachines, and the same page records that those instances are not compatible with the standard Azure IaaS VM commands. So az vm list -d skips them, and Get-AzVM -Status skips them. The Resource Graph sweep skips them for a second reason. Uniform instances are absent from the resources table altogether, and sit in a separate table called ComputeResources.

Query that table to find the ones stopped and still allocated.

az graph query -q "
ComputeResources
| where type =~ 'microsoft.compute/virtualmachinescalesets/virtualmachines'
| extend powerState = tostring(properties.extended.instanceView.powerState.code)
| where powerState == 'PowerState/stopped'
| project name, resourceGroup, subscriptionId
" --output table

Run AKS and you own this population without having picked it. The comparison table above lists AKS as supported on Uniform and unsupported on Flexible, so every node pool you have is a Uniform scale set, and none of those nodes appear in a microsoft.compute/virtualmachines sweep.

For a single scale set, the CLI reads every instance in one call. Passing --instance-id "*" is the documented way to ask for all of them at once.

az vmss get-instance-view \
  --resource-group my-rg \
  --name my-vmss \
  --instance-id "*" \
  --output table

Then release the instances you do not need, either by id or by passing * for the whole set.

az vmss deallocate \
  --resource-group my-rg \
  --name my-vmss \
  --instance-ids 3 7

Deallocating node pool instances underneath AKS takes capacity away from a control plane that still believes it has nodes. Scale the node pool with az aks nodepool scale instead, and let AKS retire the instances itself.

Deallocate without surprises

Once you have the list, deallocating is one command per VM.

az vm deallocate \
  --resource-group my-rg \
  --name my-vm

The VM moves to VM deallocated, the compute charge stops on the next billing interval, and the disks stay exactly where they are. Starting it again is just as direct.

az vm start \
  --resource-group my-rg \
  --name my-vm

Deallocation does nothing destructive to your managed disks, so that single start call is your whole rollback.

Two things are worth checking before you deallocate, because both can bite on restart.

First, the public IP. A dynamic public IP is released when a VM deallocates, and the VM can come back on a different address. If anything points at that IP (DNS records, a firewall allowlist, a partner's config), convert it to a static address first so it survives the restart (the update below changes the allocation method only, so a static IP keeps the same address across a deallocate and start). A static Standard IP does carry a small standing charge while the VM is down, which the billing section below covers.

az network public-ip update \
  --resource-group my-rg \
  --name my-vm-ip \
  --allocation-method Static

Second, the temporary disk. The local temp disk (D: on Windows, /dev/sdb1 on Linux) is wiped when a VM deallocates. It is meant for scratch data like page files and caches, so confirm nothing important lives there before you release the host.

Deallocation returns the VM's compute capacity to the regional pool, which is fine for common sizes. For scarce SKUs like GPU or HPC series in a busy region, the same size may not be free when you try to start the VM again. If a machine has to come back on demand, a capacity reservation holds the size for you while it sits deallocated.

Deallocate the whole list in one command

Deallocating a sweep's output one VM at a time costs a round trip each, so a tenant with forty stopped machines is forty waits.

The --ids parameter takes the whole list at once. The CLI reference describes it as one or more space-delimited resource IDs, and Microsoft's tips page records that the CLI processes multiple ids in parallel rather than walking them in order.

Project the resource id instead of a table, then pipe the ids into the deallocate call. @- reads them from standard input.

az vm list -d \
  --query "[?powerState=='VM stopped'].id" \
  --output tsv | az vm deallocate --ids @-

Run the az vm list half on its own first and read what comes back. Everything the query returns gets deallocated, so that output is the change you are about to make.

Resource Graph feeds the same pipe across every subscription. The KQL goes in -q, and the global --query pulls the ids out of the data array the response wraps them in.

az graph query -q "
resources
| where type =~ 'microsoft.compute/virtualmachines'
| extend powerState = tostring(properties.extended.instanceView.powerState.code)
| where powerState == 'PowerState/stopped'
| project id
" --query "data[].id" --output tsv | az vm deallocate --ids @-

Truncation is the trap in that version. Resource Graph pages its results and reports it in a resultTruncated field, and az graph query --first tops out at 1000 rows. A --query "data[].id" reads past that field without ever showing it to you, so a tenant large enough to truncate hands the pipe a partial list, deallocates exactly that list, and exits clean. Check resultTruncated on the raw query before you treat the sweep as complete, the same way -First has to be raised on the Search-AzGraph version above.

xargs -P is the other documented way to run these in parallel, and it starts a separate az process per VM. Microsoft's concurrency guidance is that CLI processes running at the same time on one machine can hit write conflicts on the shared MSAL token cache, and the fix is to point each script at its own AZURE_CONFIG_DIR. Piping into a single az vm deallocate --ids @- runs one process, so that conflict has nowhere to happen.

The VM that will not deallocate

Some VMs have no deallocated state to move to, and Azure only says so when you try.

A VM built with an ephemeral OS disk keeps its operating system on the host's local SSD instead of on managed disks. Releasing the host would take the OS disk with it, so Azure refuses the request outright with Operation 'deallocate' is not supported for VMs or VM Scale Set instances using an ephemeral OS disk. The ephemeral OS disks documentation lists the stop-deallocated state as "Not Supported" for these VMs, along with plain stop and start.

This is a different thing from the temporary disk in the previous section. The temp disk is scratch space sitting next to a normal OS disk. An ephemeral OS disk is the OS disk itself, living on local storage.

None of the sweeps above surface these, because they hunt for machines already sitting in VM stopped, and an ephemeral VM can never be in that state. Its waste has a different shape: a running box you cannot switch off.

Find them by the OS disk setting that defines them. diffDiskSettings.option reads Local on an ephemeral VM and is absent on every other one.

az vm list \
  --query "[?storageProfile.osDisk.diffDiskSettings.option=='Local'].{name:name, rg:resourceGroup, size:hardwareProfile.vmSize}" \
  --output table

Resource Graph runs the same filter across every subscription at once.

az graph query -q "
resources
| where type =~ 'microsoft.compute/virtualmachines'
| extend diffDisk = tostring(properties.storageProfile.osDisk.diffDiskSettings.option)
| where diffDisk == 'Local'
| project name, resourceGroup, subscriptionId, vmSize = tostring(properties.hardwareProfile.vmSize)
" --output table

Deleting is the only thing that stops the compute charge on one of these. That is a smaller decision than it sounds. An ephemeral OS disk was never holding state you meant to keep, because Azure reprovisions the OS from the image whenever the VM is reimaged, resized, or redeployed. Rebuilding one is a template run rather than a restore.

Ephemeral OS disks are set at creation time only, so putting an existing VM onto a persistent OS disk means recreating it. If a machine turns out to need an off switch, rebuild it with a managed OS disk and it joins the deallocation candidates like any other VM.

Deallocating and Stopping: one of them bills

Stopped and deallocated are where a VM comes to rest. Reaching either one takes a moment, and Azure names that moment too. Those in-between states split on billing the same way the resting states do.

Stopping is the transition between running and stopped, and Azure bills it at the full compute rate. Deallocating is the transition between running and deallocated, and Azure bills nothing for it. The states and billing reference marks them exactly that way, under the same footnote that keeps disks and networking charging throughout.

A VM that has been reading Deallocating for a while is therefore already off the compute meter. The charge stopped when Azure began handing the host back, not when the state finished settling. Chasing that one is an availability problem rather than a billing one.

A VM sitting in Stopping is the reverse case: it bills full compute, and somebody already decided they were done with it. A guest OS that hangs partway through its shutdown can hold it there.

Every sweep further up this page walks straight past it. They all match PowerState/stopped or VM stopped exactly, and PowerState/stopping is a different string. So it keeps billing at the full compute rate and never shows up in the list you built to catch it.

Point the same Resource Graph sweep at the states nothing else filters for.

az graph query -q "
resources
| where type =~ 'microsoft.compute/virtualmachines'
| extend powerState = tostring(properties.extended.instanceView.powerState.code)
| where powerState in ('PowerState/stopping', 'PowerState/deallocating')
| project name, resourceGroup, subscriptionId, powerState
" --output table

Everything it returns was caught mid-move. Run it again ten minutes later. A name that shows up once was an ordinary transition you happened to sample, and a name that shows up both times is wedged.

For a single machine, the instance view carries the code and the display string side by side.

az vm get-instance-view \
  --resource-group myResourceGroup \
  --name myVM \
  --query "instanceView.statuses[?starts_with(code, 'PowerState/')].{code:code, status:displayStatus}" \
  --output table

Read the provisioning state next to it. That one tracks the control-plane operation rather than the power, so the pair answer different questions about the same stuck VM.

az vm show --resource-group myResourceGroup --name myVM --query provisioningState --output tsv

Succeeded under a transitional power state means Azure finished its side and the machine has yet to catch up. Failed means the operation gave up, and the VM keeps billing at whatever state it stalled in. Reissue az vm deallocate in either case. If it lands back in the same place, az vm deallocate --force-deallocate is the documented way to force the release on a VM that will not give up its host on the polite path.

Deallocated but the bill did not drop to zero

Deallocation stops the compute meter, but other meters keep running, so the monthly cost of a released VM often falls only part way.

The managed disks keep billing. They sit on storage whether the VM runs or not, so the 128 GiB P10 OS disk from earlier still costs about $20 a month. That charge is expected, and it is the price of keeping the VM ready to start.

A static public IP keeps billing too, and this one catches people out. When you made the IP static so it would survive the restart, you handed Azure a resource it charges for on its own. A Standard static public IP lists around $0.005 an hour, about $3.65 a month, and it bills whether or not a running VM is attached to it. Basic SKU public IPs, which used to be the cheap way around this, were retired on 30 September 2025, so Standard is the only SKU you can create now.

A reservation or savings plan is a prepayment for a running VM of a given size, so it charges on whether or not this particular VM runs. Deallocating a covered VM saves money only once the reservation lands on another running VM of the same size.

Any snapshots or images you took of the disks bill as storage as well, at the snapshot rate for their size.

Here is what changes the moment a pay-as-you-go VM deallocates.

Charge Stopped (allocated) Deallocated
Compute (vCPU + RAM) Full rate Stops
Windows license (no Hybrid Benefit) Full rate Stops
Managed disks Billing Billing
Standard static public IP Billing Billing
Reservation or savings plan Billing Billing

Compute is the line that drops to zero, and the rest is the standing cost of keeping the VM and its address ready to come back. If you are done with the VM for good, delete the disks and release the static IP so those meters stop too.

Deallocate or delete: pick the one you mean

Deallocate and delete both stop the compute charge, but only deallocate lets you take it back.

Deallocate keeps the VM. The configuration, the disks, and the network wiring all stay in place, so a single az vm start brings the machine back exactly as it was. Reach for it whenever there is any chance you want the VM again, even months out.

Delete is for VMs you are finished with for good. It stops compute the same way, and it can stop the disk meter too, but only if you remember to remove the disks. az vm delete removes the VM object and nothing else.

# Deletes the VM only. Disks, NIC, and public IP stay behind and keep billing.
az vm delete --resource-group my-rg --name my-vm --yes

The OS disk, any data disks, the NIC, and a static public IP all outlive the VM as orphaned resources, and every one of them keeps charging. So a cleanup that ran nothing but az vm delete leaves a pile of disks billing storage for a machine that no longer exists. That is the same trap as the stopped VM, one step further along.

Find the disks that are now attached to nothing, then delete the ones you are sure about.

az disk list \
  --query "[?managedBy==null].{name:name, rg:resourceGroup, sizeGb:diskSizeGb}" \
  --output table

If you would rather not clean up by hand every time, set the delete options when you build the VM (--os-disk-delete-option Delete, --data-disk-delete-option Delete, --nic-delete-option Delete) so the disks and NIC leave when the VM does. When in doubt, deallocate. Delete is the door that does not open from the other side.

Put the deallocation on a schedule

Sweeping for stopped VMs cleans up what already went wrong, but does nothing about the next dev box someone powers off from the guest OS in three weeks. If you want the waste to stay gone, put the deallocation on a clock.

Azure ships a schedule on every VM. Auto-shutdown fires once a day at a time you set and deallocates the machine, so it stops the compute meter rather than parking the VM in the allocated state.

az vm auto-shutdown \
  --resource-group my-rg \
  --name my-vm \
  --time 0200

Clear it again with --off, and add --email if you want a warning before it fires.

az vm auto-shutdown \
  --resource-group my-rg \
  --name my-vm \
  --off

Two things about this feature catch people, and both cost you the hours you thought you were saving.

The clock is the first one. The CLI reference describes --time as "The UTC time of day the schedule will occur every day", in hhmm form, and the command has no timezone flag at all. The portal does give you a timezone picker, and it starts on UTC. So a team in Los Angeles that sets --time 1800 expecting an evening shutdown gets one at 11am local, in the middle of the working day, on every VM they just scripted. Set the hour from the portal when you want local time, or write timeZoneId directly on the underlying Microsoft.DevTestLab/schedules resource, which is where the portal picker stores it.

The second is that the schedule only runs one way. Auto-shutdown deallocates on a timer and Azure gives you no built-in auto-start to match it. Whoever needs the machine in the morning runs az vm start by hand. To automate both ends you need Azure Automation, a Logic App, or the Start/Stop VMs during off-hours solution, and each of those is a separate thing to deploy and keep working.

Point it at dev, test, build agents, and demo boxes, the machines with an obvious overnight. Leave it off anything that runs a nightly job, because a schedule that deallocates a VM at 2am is indistinguishable from an outage to whoever owns that job.

A quick decision tree

graph TD
  A[VM doing no work] --> B{Need it again soon?}
  B -- No --> C[Delete the VM and its disks]
  B -- Yes --> D{Power state from az vm list -d or Get-AzVM -Status}
  D -- VM deallocated --> E[Already not billing compute]
  D -- VM stopped --> F{Dynamic public IP or temp-disk data to keep?}
  D -- VM running --> I[Still powered on, so deallocate stops it. Confirm it is truly idle first]
  D -- VM stopping or deallocating --> O{Same state ten minutes later?}
  O -- No --> P[Ordinary transition. Re-read the state once it settles]
  O -- Yes --> Q[Wedged. Read provisioningState, then force the deallocate]
  I --> F
  F -- Yes --> G[Make the IP static, a small monthly charge, and move temp data first]
  F -- No --> H[Run az vm deallocate now]
  G --> H
  H --> M{Refused as an ephemeral OS disk?}
  M -- Yes --> N[No deallocated state exists. Delete and rebuild from the image]
  M -- No --> J{Idle on a predictable schedule?}
  J -- Yes --> K[Set az vm auto-shutdown, remembering the time is UTC, and plan who starts it]
  J -- No --> L[Re-run the sweep next month]

The same waste hides wherever you reserve capacity and forget to use it.

Common questions

Does a stopped Azure VM still cost money?

If it reads Stopped (allocated), yes. Azure holds the vCPUs and memory for that VM, so the full compute rate keeps billing until you deallocate it. A Stopped (deallocated) VM bills nothing for compute.

What is the difference between stopped and deallocated?

Stopped means the VM is powered off but its host is still reserved for you, so compute keeps billing. Deallocated means the host goes back to Azure, so compute drops to zero, unless an Azure Reservation or savings plan covers the VM, in which case that commitment keeps billing until it moves to another running VM of the same size. Either way the managed disks keep billing at their usual rate.

How do I stop paying for an Azure VM I am not using?

Deallocate it with az vm deallocate, or hit the portal Stop button, which deallocates for you. If you will never need the VM again, delete it and its disks so the storage charge stops too.

Do I still pay for storage after deallocating?

Yes. The managed disks attached to the VM sit on storage whether the VM runs or not, so they bill every month until you delete them. Only the compute charge stops when you deallocate.

Does an Azure VM in the Deallocating state still cost money?

No. Azure lists Deallocating as a transitional state that is not billed for instance usage, so the compute charge stops as the host is handed back rather than when the state finishes settling. Disks and networking keep charging the whole time, as they do in every other state.

Why is my Azure VM stuck on Deallocating?

The state is transitional, so it normally clears on its own within minutes. Read it twice a few minutes apart before treating it as stuck. If it has not moved, check provisioningState with az vm show, since a Failed control-plane operation and a slow release look identical from the power state alone. Reissuing az vm deallocate, and then az vm deallocate --force-deallocate, is the documented path. A VM sitting there is already off the compute meter, so this is a question of getting the machine to a known state rather than of stopping a charge.

Does a VM in the Stopping state still bill compute?

Yes. Azure bills Stopping at the full compute rate, the same as Stopped (allocated). It is the transition into the expensive off state rather than the free one, so a VM that hangs partway through shutdown keeps paying. Sweeps that filter for PowerState/stopped will not return it, because PowerState/stopping is a different string.

Why is my deallocated Azure VM still charging me?

Compute stops when you deallocate, but a few meters keep running. The managed disks bill as storage, a Standard static public IP bills about $3.65 a month whether a VM is attached or not, and any Azure Reservation or savings plan keeps charging until it covers another running VM of the same size. Delete the disks and release the static IP if you are finished with the VM for good.

Does Azure auto-shutdown deallocate the VM?

The built-in Auto-shutdown schedule on a VM deallocates it, so it does stop the compute bill on the hours you set. A shutdown triggered from inside the guest OS leaves the VM Stopped (allocated) and billing, so confirm which one your automation actually runs.

Why did my Azure auto-shutdown schedule fire at the wrong time?

Almost always the timezone. The az vm auto-shutdown --time flag takes a UTC hour in hhmm form and has no timezone option, and the portal picker also starts on UTC. A schedule set to 1800 from the CLI in US Pacific shuts the VM down at 11am local. Set the hour in the portal, or set timeZoneId on the Microsoft.DevTestLab/schedules resource behind it.

Can Azure start a VM automatically after auto-shutdown?

Not with the built-in feature. Auto-shutdown deallocates on a schedule and there is no matching auto-start, so the next person to need the VM runs az vm start. Scheduling both ends means Azure Automation, a Logic App, or the Start/Stop VMs during off-hours solution.

How do I find every stopped VM across all my Azure subscriptions at once?

Use Azure Resource Graph. A single az graph query filtering on properties.extended.instanceView.powerState.code == 'PowerState/stopped' covers every subscription your account can read and returns in about a second. Looping az vm list -d over each subscription gets the same answer, and it costs you a round trip per subscription.

How do I deallocate multiple Azure VMs at once?

Pipe the resource ids into a single az vm deallocate --ids @-. The --ids parameter takes a space-delimited list, @- reads that list from standard input, and the CLI runs the deallocations in parallel instead of one after another. Build the list with az vm list -d --query "[?powerState=='VM stopped'].id" --output tsv, and read that output on its own before you pipe it, because every id it returns gets deallocated.

How do I find stopped Azure VMs with PowerShell?

Run Get-AzVM -Status and filter on the power state, for example Where-Object { $_.PowerState -eq 'VM stopped' }. The -Status switch is the part people miss: without it the returned objects have no PowerState property at all, so the filter matches nothing and looks like a clean result. To cover every subscription in one call, use Search-AzGraph -UseTenantScope from the Az.ResourceGraph module, and raise -First from its default of 100 so a large tenant does not come back truncated.

Does Stop-AzVM deallocate the VM?

Yes, that is the default. Stop-AzVM -ResourceGroupName my-rg -Name my-vm releases the host, so the compute charge stops. Adding -StayProvisioned is what leaves the VM allocated and billing full compute, so grep your runbooks for that flag before you trust them.

Does az vmss stop deallocate scale set instances?

No. az vmss stop powers the instances off and leaves them allocated, so they keep billing compute exactly like a standalone VM in the Stopped (allocated) state. az vmss deallocate is the command that releases the capacity and stops the charge. The two sit one word apart, the same way az vm stop and az vm deallocate do.

Does restarting an Azure VM deallocate it?

No. A restart never releases the host, so the compute meter runs straight through it. Azure bills the Starting, Running, and Stopping power states at the full rate, and a reboot only ever moves the VM between those three, so az vm restart, Restart-AzVM, and a reboot from inside the guest all cost the same as leaving the machine up. A restart is also invisible to a power-state sweep, because Azure records it as the Updating provisioning state rather than a change to powerState. Restarting a VM that already reads Stopped (allocated) starts it back up, and the compute charge does not move, because that state was already billing the full rate. Deallocating is what ends it.

Why do my AKS nodes not show up in az vm list?

Because AKS node pools run on Uniform orchestration scale sets, and Uniform instances are the resource type Microsoft.Compute/virtualMachineScaleSets/virtualMachines rather than Microsoft.Compute/virtualMachines. The standard VM commands do not read that type, so az vm list and a Resource Graph query against the resources table both come back without them. Query the ComputeResources table instead, or list the instances per scale set with az vmss get-instance-view --instance-id "*".

Does a stopped Windows VM still bill for the Windows license?

Yes, if it reads Stopped (allocated). The Windows Server license is baked into the VM's per-hour rate, so a stopped-but-allocated Windows VM keeps paying for the license along with the compute. Deallocate it and both charges stop. If you use Azure Hybrid Benefit, you are not paying the license on that VM to begin with, so only the hardware rate is at stake.

Why does az vm deallocate fail with "not supported for VMs using an ephemeral OS disk"?

Because that VM's operating system lives on the host's local storage rather than on managed disks. Deallocating hands the host back to Azure, which would take the OS disk with it, so the platform blocks the operation. A VM like that can be restarted, reimaged, or deleted, and deleting is the only one of the three that stops the compute charge.

Can I deallocate an Azure VM with an ephemeral OS disk to save money?

No. Stop-deallocate is unsupported on ephemeral OS disks, so nothing short of deleting the VM stops its compute meter. Rebuilding is cheaper than it sounds, since Azure reprovisions the OS from the image on every reimage, resize, and redeploy anyway. Ephemeral OS disks are also fixed at creation time, so moving a VM onto a persistent OS disk means recreating it. To find these VMs, filter on storageProfile.osDisk.diffDiskSettings.option == 'Local', which is absent on every VM with a normal managed OS disk.

Does deallocating an Azure VM delete it or lose my data?

No. Deallocating releases the host and stops the compute charge, and it leaves the VM, its managed disks, and its configuration in place. One az vm start brings it back as it was. Only the temporary disk is wiped, since that is scratch space by design.

What is the difference between deallocating and deleting an Azure VM?

Both stop the compute charge. Deallocating is reversible and keeps the disks, so you can start the VM again later. Deleting is permanent, and az vm delete removes only the VM object, so the managed disks, the NIC, and any static public IP stay behind and keep billing until you delete them too.

How OhChimp approaches this

A sweep clears today's list. Then a runbook calls az vm stop, or someone shuts the box down from inside the guest, and it lands back in Stopped (allocated). No alert fires for that.

OhChimp scans your Azure subscriptions and finds the VMs sitting in Stopped (allocated). Each VM gets a reviewable plan: the monthly compute it wastes, a confidence score, the risk level of the change, and the start command that undoes it. The public IP and temp-disk questions are answered per VM, so you read a recommendation instead of redoing the checks. Deallocation happens when you click apply and not before. OhChimp cannot yet measure what one deallocated VM saves, so the monthly figure on the plan stays an estimate.

The Azure integration page covers how the connection works.

All OhChimp posts