Skip to main content
emnode
Cost

Schedule auto-shutdown for idle dev/test VMs

One capability across every non-production virtual machine: deallocate the VMs nobody uses out of hours on a daily schedule, so you stop paying for compute the moment the working day ends instead of leaving meters running through the night and the weekend.

13 min·10 sections·AZURE

Last reviewed

Auto-shutdown for idle VMs: the basics

What does a dev/test VM running 24/7 actually cost you?

An Azure virtual machine bills for compute by the second for as long as it is running, whether or not anyone is using it. A dev box, a test rig, a build agent or a demo environment that a team touches during office hours still runs through every night and every weekend unless something stops it. A VM used for roughly 40 working hours a week is allocated and billed for all 168 hours in that week, so the majority of its compute cost buys nothing. Auto-shutdown is the feature that closes that gap: it stops and deallocates the VM on a daily schedule, so the meter stops out of hours.

The important word is deallocate. A VM that is merely shut down from inside the guest operating system stays allocated, and Azure keeps charging for the compute. Only when a VM reaches the 'Stopped (deallocated)' state do the compute charges stop. Azure's auto-shutdown feature deallocates the VM, which is exactly what you want: you keep paying for the OS and data disks, which is cheap, and you stop paying for the cores and memory, which is the expensive part.

Most of this waste is not a decision anybody made. It is the default behaviour of a VM that nobody told to turn off. A dev environment spun up for a sprint, a proof of concept that outlived its proof, a test fleet provisioned by a module that never set a schedule. The work is to find every non-production VM that does not need to run around the clock, put a daily deallocation schedule on it, and let the platform reclaim the out-of-hours hours that were only ever being paid for out of habit.

In this lesson you will learn how Azure bills a running VM, what 'deallocated' really means for your invoice, how to find the non-production VMs that are running around the clock, and how to put a daily auto-shutdown schedule on them safely without disrupting the few that must stay on. The lesson uses Azure Advisor to surface the candidates and the built-in auto-shutdown feature to act on them.

Fun fact

The 40-hour week on a 168-hour bill

A team using a dev VM during normal office hours touches it for roughly 40 hours a week, yet a VM left running bills for all 168 hours in that week. That means well over two thirds of the compute spend on an unmanaged dev box pays for time when nobody is logged in. Auto-shutdown started life as a feature exclusive to Azure DevTest Labs, precisely because lab VMs were the worst offenders, and Microsoft later made it available to every Resource Manager VM. The feature has not changed the maths in years: turn the machine off at night and the expensive part of the bill simply stops.

Finding always-on idle VMs across a subscription

Priya runs the platform team at a scale-up whose Azure bill has been creeping up. Azure Advisor is flagging a cost recommendation to shut down or resize underutilised virtual machines, and she suspects a layer of dev and test boxes are running through every night.

Rather than guess, she starts by listing which VMs are currently running and which already carry an auto-shutdown schedule, so she can separate the machines that are managed from the ones quietly billing around the clock.

Start by listing the VMs that are powered on but tagged or named as non-production, so you can see which idle machines are still running.

$ az vm list -d --query "[?powerState=='VM running'].{name:name, group:resourceGroup, size:hardwareProfile.vmSize}" -o table
Name Group Size
---------------- ----------- ---------------
dev-api-box rg-dev Standard_D4s_v5
test-runner-01 rg-test Standard_D2s_v5
prod-web-01 rg-prod Standard_D4s_v5
# dev-api-box and test-runner-01 are non-prod and running 24/7. Schedule them off.

A running non-production VM with no shutdown schedule is the highest-value target: it bills for nights and weekends nobody uses. Schedule these first, and leave prod alone.

How auto-shutdown actually stops the billdeep dive

Azure's auto-shutdown feature is implemented as a schedule resource attached to the VM, of type 'Microsoft.DevTestLab/schedules' with a task type of 'ComputeVmShutdownTask'. At the scheduled time it does not just power the guest down; it deallocates the VM, moving it to the 'Stopped (deallocated)' state. This distinction is the whole point. A VM stopped from inside the operating system, or sitting in the plain 'Stopped' state, is still allocated and still billed for compute. Only 'Stopped (deallocated)' releases the underlying cores and memory and stops the compute charge. You continue to pay for the OS and data disks, which is the small part of the bill, while the expensive compute meter is off.

The schedule has a daily recurrence time and a time zone. A subtle but important detail: when you set the schedule with the 'az vm auto-shutdown' CLI command, the '--time' value is interpreted in UTC, whereas the portal lets you pick a named time zone. If you set '1800' on the CLI expecting local 6pm and your team is several hours off UTC, the VM will shut down at the wrong time, so either convert to UTC for the CLI or set the time zone explicitly in the underlying schedule resource.

Auto-shutdown stops machines; it does not start them. Azure has no built-in auto-start paired with the shutdown schedule, so if a team needs their dev boxes powered on for the start of the working day, that is a separate job, typically an Azure Automation runbook, a Logic App or a Function on a morning trigger. For most dev/test cases the developer simply starts the VM when they sit down, which takes a minute or two, and that manual start is part of why auto-shutdown is so low-risk: nothing comes back on by surprise, and nobody pays for a machine that was started automatically and then left idle.

What is the impact of leaving idle VMs running?

The direct impact is recurring waste. A non-production VM with no shutdown schedule bills for compute 24 hours a day, every day, and the hours outside the working week, the nights and the weekends, buy nothing. Across a fleet of dev, test, build and demo machines this is one of the largest avoidable line items in a typical Azure bill, and because it recurs every month, the cost of leaving it untouched compounds.

The second-order impact is drift you cannot see. Always-on VMs make it harder to spot the ones that are genuinely abandoned, because a stale machine running quietly looks the same on the bill as one in daily use. Putting schedules on the estate forces the question of whether each VM is needed at all, and the deallocation event itself becomes a cheap signal: a VM nobody ever bothers to start back up is a strong candidate for deletion.

Azure Advisor makes this concrete. Its cost recommendation to shut down or resize underutilised virtual machines identifies low-utilisation VMs, typically those with CPU and outbound network usage below a low threshold over a lookback window, and attaches an estimated saving. That estimate is the finance-visible version of the same waste: it is the money currently spent on compute that the workload is not using, and auto-shutdown is the most direct way to act on it without re-architecting anything.

How do you schedule idle VMs off safely?

Work the estate as one loop rather than scheduling machines ad hoc. The order matters: confirm which VMs legitimately need to run after hours before you start scheduling, so you do not deallocate something mid-job.

1. Inventory the non-production VMs that run continuously

List the running VMs that are dev, test, build or demo, by tag, resource group or naming convention, and note which already carry an auto-shutdown schedule. Cross-reference Azure Advisor's cost recommendation to shut down or resize underutilised VMs, which surfaces the low-utilisation machines and an estimated saving. Treat this inventory as the source of truth.

2. Separate the machines that must stay on

Most non-production VMs do not need to run out of hours. The exceptions are real but few: a build agent running nightly jobs, a shared environment used across time zones, a long-running test. For each, decide whether it genuinely needs evening or weekend hours, and record it as a named exception with a later shutdown time rather than no schedule at all.

3. Set a daily deallocation schedule, highest spend first

Put a daily auto-shutdown time on each remaining VM, prioritising the largest VM sizes because they waste the most per idle hour. Remember the CLI '--time' is UTC: convert your local cut-off time accordingly, or set the time zone explicitly. Enable the pre-shutdown notification so anyone working late gets a warning and can delay it. The change takes effect immediately and the VM deallocates at the next scheduled time.

4. Make a schedule the default for new VMs

A one-off sweep saves money once; a default keeps it saved. Bake the auto-shutdown schedule into the Bicep or template that provisions dev/test VMs so every new machine is born with one, and use Azure Policy to audit for VMs in non-production scopes that lack a schedule. That turns auto-shutdown from a thing someone remembered into the standing behaviour of the estate.

# Schedule a single dev/test VM to deallocate every evening.
# NOTE: --time is UTC and uses hhmm. 1800 UTC, not local 6pm.
az vm auto-shutdown \
  --resource-group rg-dev \
  --name dev-api-box \
  --time 1800 \
  --email "[email protected]"

# Sweep every running VM in a non-production resource group.
for id in $(az vm list -g rg-dev --query "[].id" -o tsv); do
  az vm auto-shutdown --ids "$id" --time 1800
done

# Make it the default in IaC: attach a shutdown schedule in Bicep so
# every new dev/test VM is born deallocating out of hours.
# --- shutdown.bicep ---
# param vmName string
# param vmId string
# resource shutdown 'Microsoft.DevTestLab/schedules@2018-09-15' = {
#   name: 'shutdown-computevm-${vmName}'
#   location: resourceGroup().location
#   properties: {
#     status: 'Enabled'
#     taskType: 'ComputeVmShutdownTask'
#     dailyRecurrence: { time: '1800' }
#     timeZoneId: 'GMT Standard Time'
#     notificationSettings: {
#       status: 'Enabled'
#       timeInMinutes: 30
#       emailRecipient: '[email protected]'
#     }
#     targetResourceId: vmId
#   }
# }

Quick quiz

Question 1 of 5

Why does Azure's auto-shutdown feature reduce the bill, when shutting a VM down from inside the guest OS does not?

You can now treat idle non-production compute as one capability rather than a per-VM afterthought: inventory the machines running around the clock, separate the few that legitimately need after-hours time, put a daily deallocation schedule on the rest highest-spend first, and bake the schedule into your templates so new VMs inherit it. The result is a non-production estate that turns itself off out of hours and stops paying for time nobody uses.

Back to the library