Cloud & infrastructure

Azure Cost Optimization Checklist: Reservations, Right-Sizing and Cleanup

A practical order of work for cutting an Azure bill: budgets first, then orphaned resources, idle VMs, Advisor right-sizing, Azure Hybrid Benefit, and only then reservations and savings plans.

13 min read
On this page

The fastest way to cut a monthly Azure bill is to work in a fixed order: set budgets so you can see the effect of each change, delete orphaned resources such as unattached disks and static public IPs, deallocate or schedule idle VMs, right-size what remains with Azure Advisor, apply Azure Hybrid Benefit where you already own licences, and only then buy reservations and savings plans for the clean baseline. Commitments bought before cleanup lock in waste, because a discount reduces the rate you pay, not the amount you use.

Who this is for and what you will have at the end

This checklist is for Azure administrators, platform teams and FinOps practitioners who own one or more subscriptions and need a repeatable way to reduce spend without breaking workloads.

At the end you will have:

  • Budgets with actual and forecasted alerts on the scopes you own.
  • Azure Resource Graph queries that list orphaned disks, public IPs, network interfaces, NAT gateways, gateways and load balancers.
  • A list of VMs that are stopped but still allocated, and auto-shutdown on non-production machines.
  • Advisor right-sizing recommendations tuned to your own CPU threshold and lookback period.
  • A decision on reservations versus savings plans, based on Microsoft's recommended purchase sequence.

If you are still moving workloads into Azure, the landing-zone and subscription design in the enterprise Azure cloud migration playbook makes every step below easier, because costs land on predictable scopes.

Prerequisites

TaskWhat you need
View budgetsRead access on the scope
Create or edit budgetsOwner, Contributor or Cost Management Contributor
Run Resource Graph queriesRead access to the subscriptions you query
Change Advisor right-sizing filtersAdvisor permissions on the subscription (the option is disabled without them)
Exchange or refund reservationsOwner or Reservation administrator on the reservation order
Change VM licence type or auto-shutdownContributor-level rights on the VM

You also need Azure CLI or Azure PowerShell. Resource Graph queries can run from Resource Graph Explorer in the portal, az graph query or Search-AzGraph. A brand-new subscription can take up to 48 hours before all Cost Management features, including budgets, are available.

Step 1: Set budgets before you change anything

Budgets don't stop resources, but they give you a baseline and an early warning. Create one per subscription or resource group that has an owner.

  1. In the Azure portal, open the subscription (or resource group) and select Budgets, then Add.
  2. Confirm the scope, add filters if the budget should cover only some costs, name it, and choose a reset period of monthly, quarterly or annually.
  3. Set an expiration date. When a budget expires it's deleted.
  4. Select Next and add alert conditions. Use the Type field to switch between Actual and Forecasted.

Points to know when you configure alerts:

  • Each budget needs at least one threshold and one email address, and supports up to five thresholds and five email addresses.
  • Cost data is typically available within 8-24 hours and budgets are evaluated every 24 hours, so alerts aren't real time.
  • Budget evaluation includes reservation and purchase data. To evaluate first-party Azure consumption only, add the filters Publisher Type: Azure and Charge Type: Usage.
  • Action groups are supported only at subscription and resource group scopes. Use them for webhooks or automation rather than email alone.

The Azure CLI equivalent, based on Microsoft's example, creates a monthly budget on a resource group with an 80% alert routed to an action group:

az consumption budget create-with-rg \
  --amount 5000 \
  --budget-name rg-app-prod-monthly \
  -g rg-app-prod \
  --category Cost \
  --time-grain Monthly \
  --time-period '{"start-date":"2026-10-01","end-date":"2027-09-30"}' \
  --notifications "{\"Key1\":{\"enabled\":\"true\", \"operator\":\"GreaterThanOrEqualTo\", \"contact-emails\":[], \"threshold\":80.0, \"contact-groups\":[\"$ActionGroupId\"]}}"

Step 2: Remove orphaned resources

Deleting a VM doesn't delete its disks by default, and public IPs, NICs, NAT gateways and gateways are often left behind after a migration or a rebuild. These are the safest savings because nothing is using them, but deletion is permanent, so review every result with the resource owner first.

Unattached managed disks

An attached managed disk has the VM's resource ID in its managedBy property; an unattached one has managedBy set to null. List them per subscription:

az disk list --query '[?managedBy==`null`].[id]' -o tsv

Before deleting, run az disk show and check LastOwnershipUpdateTime, which shows when the disk was unattached. Take a snapshot if there is any doubt. Advisor also raises a preview recommendation, Review disks that aren't attached to a VM and evaluate if you still need the disks, which excludes Azure Site Recovery replica disks.

Unattached static public IPs

Static public IPs are charged whether or not they're associated with a resource. This Resource Graph query, adapted from Microsoft's FinOps networking guidance, lists static IPs that aren't attached to an IP configuration or a NAT gateway:

resources
| where type =~ 'Microsoft.Network/publicIPAddresses'
    and isempty(properties.ipConfiguration)
    and isempty(properties.natGateway)
    and properties.publicIPAllocationMethod =~ 'Static'
| project id, name, SKUName = tostring(sku.name), resourceGroup, location, subscriptionId

Before you delete an address, check DNS records and partner allow lists. Once a public IP is deleted, the address can't be recovered.

Other networking leftovers

ResourceWhy it costs moneyResource Graph filter
NAT gateway with no subnetsHourly and data processing chargestype == "microsoft.network/natgateways" and isnull(properties.subnets) or array_length(properties.subnets) == 0
Virtual network gateway with no connectionsHourly charge by SKUGateways with no matching microsoft.network/connections resource
Standard load balancer with no backend poolsStandard load balancers incur costs when idlearray_length(properties.backendAddressPools) == 0 and sku.name != 'Basic'
DDoS protection plan with no virtual networksFixed monthly chargeisnull(properties.virtualNetworks) or array_length(properties.virtualNetworks) == 0
ExpressRoute circuit not provisionedMonthly charge without trafficproperties.serviceProviderProvisioningState == "NotProvisioned"
Network interface with no VM or private endpointNo direct charge, but often keeps a public IP aliveisnull(properties.virtualMachine) and isnull(properties.privateEndpoint)

For example, idle NAT gateways:

resources
| where type == "microsoft.network/natgateways"
| where isnull(properties.subnets) or array_length(properties.subnets) == 0
| project id, name, SKUName = tostring(sku.name), location, resourceGroup, subscriptionId

Advisor surfaces some of the same items on its Cost tab, including Delete Azure virtual network gateways with no connections, Delete or provision ExpressRoute circuits in a not-provisioned state and Unused or empty App Service plan, all in preview at the time of writing.

Step 3: Stop paying for idle compute

A VM shut down from inside the guest OS, or with the PowerOff operation, sits in the Stopped state, which is still billed for compute. Only Stopped (deallocated) releases the hardware and stops compute charges. Disks and networking continue to bill in both cases.

Find VMs that are stopped but still allocated across every subscription you can read:

Resources
| where type == 'microsoft.compute/virtualmachines'
| extend PowerState = tostring(properties.extended.instanceView.powerState.code)
| where PowerState == 'PowerState/stopped'
| project name, resourceGroup, subscriptionId, PowerState

Deallocate them, or delete them if they're no longer needed. For development and test VMs that only run in working hours, enable auto-shutdown: open the VM, select Auto-shutdown under Operations, switch it on, set the time and optionally a notification email or webhook. The portal's time zone defaults to UTC, so set it explicitly. From the CLI, --time takes a UTC time in hhmm format:

az vm auto-shutdown -g rg-dev -n vm-dev-01 --time 1800

Step 4: Right-size with Azure Advisor

Advisor's Right-size or shutdown underutilized virtual machines recommendation (and the scale set equivalent) uses CPU, memory and outbound network metrics:

  • Shutdown is recommended when P95 of maximum CPU summed across cores is below 3%, P100 of average CPU over the last 3 days is 2% or less, and outbound network utilization is below 2% over seven days.
  • Resize targets a cheaper SKU where user-facing workloads would stay at or below 40% P95 CPU and network and 60% P99 memory, and non-user-facing workloads at or below 80% for each. The new SKU must keep the same Accelerated Networking and Premium Storage capabilities and be available in the region.
  • Burstable recommendations suggest B-series sizes for workloads with low average CPU but occasional spikes, where the VM doesn't use accelerated networking.

The default lookback is seven days. You can change it to 7, 14, 21, 30, 60 or 90 days; recommendations can take up to 48 hours to refresh. A longer lookback avoids resizing a VM that is busy at month end.

To show only recommendations for VMs below a CPU level you're comfortable acting on, open Advisor, select Configuration, then the VM/Virtual Machine Scale Sets right sizing tab, select the subscriptions, select Edit, choose the average CPU value and select Apply. The filter can take up to 24 hours to apply.

Two limitations matter when you act on these recommendations:

  • Estimated savings use retail rates and ignore negotiated discounts, so real savings are usually lower.
  • Recommendations ignore your reservations and savings plans. A cross-series resize can move a VM off a reserved size and raise the bill. Check coverage before resizing reserved VMs.

Use Dismiss or Postpone for VMs sized for planned growth, disaster recovery or SKU uniformity, so the list stays actionable.

Step 5: Apply licences you already own

Azure Hybrid Benefit for Windows Server lets you use on-premises Windows Server core licences with active Software Assurance or qualifying subscription licences. Each VM needs at least 8 core licences, and the benefit is valid only during the Software Assurance or subscription term. Changing the licence type only changes a metadata flag; the VM doesn't restart.

List Windows Server VMs that already use the benefit, then convert the ones that qualify:

az vm list --query "[?licenseType=='Windows_Server']" -o table
az vm update --resource-group rg-app-prod --name vm-app-01 --set licenseType=Windows_Server

In the portal, the same setting is under the VM's Operating system page as Azure Hybrid Benefit. A VM reservation or a savings plan for compute covers only the compute cost, not the Windows software cost, so Hybrid Benefit for Windows Server stacks with them rather than overlapping. (The separate savings plan for databases does cover some SQL Server software costs, so check before combining it with SQL Server licences you already own.)

Step 6: Commit with reservations and savings plans

Once the baseline is clean, commitments give the largest discount on what remains.

ReservationSavings plan
CommitmentA specific SKU or family, usually in one regionA fixed spend per hour
Term1 or 3 years1 or 3 years
Applies toMatching resources in the selected scopeEligible compute or database usage across regions
Best forStable workloads that won't change size, family or regionWorkloads that change family, service or region
CancellationRefunds allowed up to 50,000 USD of canceled commitment per rolling 12 monthsCan't be canceled or refunded

Reservations can reduce costs by up to 72% compared with pay-as-you-go prices, and up-front and monthly payment cost the same in total. Savings plans come in two kinds: Savings plan for compute (Virtual Machines, App Service, Functions premium plan, Container Instances, Dedicated Host, Container Apps and others) and Savings plan for databases. Unused hourly savings plan commitment expires; it doesn't roll over.

Microsoft's recommended sequence is:

  1. Right-size first. Discounts reduce rates, not waste.
  2. Exchange underused reservations for configurations that fit.
  3. Trade in underused reservations for savings plans where usage varies.
  4. Buy new reservations only for stable workloads.
  5. Buy a savings plan sized to the remaining optimized baseline.

Use the purchase recommendations in Advisor (for example Consider virtual machine reserved instance to save over the on-demand costs and Consider purchasing a savings plan to unlock lower prices) or in the purchase experience in the portal. Also act on Configure automatic renewal for the expiring reservations, because an expired reservation falls back to on-demand rates without any outage to tell you.

Plan for the exchange policy change: reservations purchased after 1 February 2027 for services that savings plans cover, such as Virtual Machines, App Service and SQL Database, won't be exchangeable. Reservations bought before that date keep one final exchange. Instance size flexibility and the refund limit don't change.

Verify the savings

  • Cost analysis. Compare the month after cleanup with the baseline your budgets captured. Remember that cost data lags by up to a day.
  • Reservation utilization. Open Reservations in the portal and check utilization for each order. Low utilization means the reservation no longer matches the workload; exchange it or trade it in.
  • Re-run the queries. Schedule the Resource Graph queries above, for example monthly, so new orphans are caught early.
  • Advisor Cost tab. Confirm dismissed or postponed recommendations still have a valid reason.

Troubleshooting

You can't create a budget on a new subscription. Cost Management features can take up to 48 hours to become available on a new subscription.

Budget alert emails never arrive. Add azure-noreply@microsoft.com to your approved senders list; the messages often land in junk mail.

Budget created with PowerShell sends no notifications. Microsoft notes that budgets created with PowerShell don't send notifications, and recommends the REST API for programmatic creation. Microsoft Customer Agreement customers should use the Budgets REST API.

Advisor recommendations didn't change after you changed the lookback period. Allow up to 48 hours for the lookback change and up to 24 hours for a CPU filter change.

The bill went up after a right-size. The new size probably isn't covered by your reservation. Exchange the reservation to the new size or trade it in for a savings plan.

Exchange fails with an error about the purchase amount. The new purchase must be equal to or greater than the amount returned. Reduce the quantity you return or increase the quantity you buy.

A deleted disk or public IP was still needed. Neither can be recovered after deletion. Restore the data from a snapshot or backup and update DNS for the new address. Taking a snapshot first, and tagging resources with an owner, prevents this.

Checklist

  • Budgets with actual and forecasted alerts on every owned subscription and key resource group.
  • Unattached disks reviewed, snapshotted where needed, and deleted.
  • Unattached static public IPs, idle NAT gateways, gateways, load balancers, DDoS plans and unprovisioned circuits removed.
  • No VMs left in Stopped (allocated) state; auto-shutdown on non-production VMs.
  • Advisor lookback and CPU filter set; right-size recommendations actioned or dismissed with a reason.
  • Azure Hybrid Benefit enabled on every eligible Windows Server VM.
  • Reservations exchanged or traded in where underused; new reservations only for stable workloads; savings plan sized to the clean baseline.
  • Automatic renewal configured for expiring reservations, and the February 2027 exchange change noted.

References

Questions people ask

Should I buy Azure reservations or a savings plan first?

Neither, until you have removed waste. Microsoft's recommended order is to right-size first, fix or exchange underused reservations, trade rigid reservations for savings plans where usage varies, then buy reservations for stable workloads and finally size a savings plan to the clean baseline.

Does a stopped Azure VM still cost money?

A VM in the Stopped (allocated) state, for example after a shutdown from inside the guest OS, is still billed for compute. Only Stopped (deallocated) stops compute charges, and disks and some networking charges continue even then.

Can I cancel an Azure savings plan or reservation?

Savings plans can't be canceled or refunded. Reservations can be refunded, but the total canceled commitment can't exceed 50,000 USD in a rolling 12-month window per billing profile or enrollment, and exchanges follow separate rules.

Why are Azure Advisor savings estimates higher than what I actually save?

Advisor calculates savings from retail rates and doesn't account for your negotiated discounts, reservations or savings plans. Treat the figure as an upper bound and check the effect in Cost Management after the change.

Azure Cost ManagementAzure AdvisorAzure ReservationsAzure Savings PlanAzure Resource Graph
  1. AVD scaling plans: autoscale session hosts and Start VM on Connect

    Configure an Azure Virtual Desktop power management scaling plan, Start VM on Connect and disconnected-session limits so pooled session hosts are deallocated when nobody needs them.

  2. Azure Policy Tag Enforcement: Require, Inherit and Remediate Tags

    Enforce CostCenter and Owner tags with built-in Azure Policy definitions: deny untagged resource groups, inherit tags onto resources, and remediate existing resources with managed identities.

  3. Upgrade Azure Basic Public IPs to Standard SKU and Keep the Same Address

    Basic SKU public IPs were retired on 30 September 2025. Upgrade them to Standard and keep the address, with Microsoft's VM and load balancer scripts or a manual detach and upgrade.