INSIGHTS

Azure cost optimization checklist: 30 checks for this quarter

Thirty Azure cost checks grouped by area, each with what to look for, how to check it in the portal or Resource Graph, and how the saving actually works.

Patrick Rezende

·

An Azure cost optimization checklist for a mid-market team has to cover five areas: leftovers that bill with nothing attached, compute that runs longer or larger than needed, platform services sized for a peak that never came, commitments that are idle or missing, and the guardrails that stop the waste coming back. Below are 30 checks, each with what to look for, how to check it, and how the saving works.

Prices quoted are Azure list prices for East US, pay-as-you-go, as of October 2026, taken from the Azure Retail Prices API. Your rates will differ by region and agreement, so treat them as ratios.

How should you work through the list?

Not every check deserves the same week. The leftovers are low risk and quick. Sizing changes touch running systems and need an owner who knows the workload. Commitments need a finance conversation. A sensible order for a team with no full-time FinOps person:

Area

Checks

Effort

Risk of the change

Leftovers billing with nothing attached

1 to 7

An afternoon in Resource Graph Explorer

Low, once you confirm the owner

Compute running too long or too large

8 to 16

A few days, spread over owners

Medium: restarts, performance

Platform services sized for a peak

17 to 25

A week or two

Medium: needs usage data

Commitments

26 to 28

One review, then quarterly

Financial, not technical

Guardrails

29 to 30

One day to set up

Low

Resource Graph Explorer, in the Azure portal, runs Kusto queries across every subscription you can read. Every query below runs there as is.

Which leftovers are still billing? (checks 1 to 7)

1. Unattached managed disks

  • Look for: disks whose state is Unattached. When you delete a VM, its disks are kept by default, and Microsoft states plainly that you continue to pay for them.

  • How to check: in the portal, search for Disks and look at the disk state, or run:

resources
| where type =~ 'microsoft.compute/disks'
| where properties.diskState =~ 'Unattached'
| project name, resourceGroup, subscriptionId,
    sku = tostring(sku.name),
    sizeGB = toint(properties.diskSizeGB),
    lastDetached = todatetime(properties.LastOwnershipUpdateTime)
| order by sizeGB desc
resources
| where type =~ 'microsoft.compute/disks'
| where properties.diskState =~ 'Unattached'
| project name, resourceGroup, subscriptionId,
    sku = tostring(sku.name),
    sizeGB = toint(properties.diskSizeGB),
    lastDetached = todatetime(properties.LastOwnershipUpdateTime)
| order by sizeGB desc
resources
| where type =~ 'microsoft.compute/disks'
| where properties.diskState =~ 'Unattached'
| project name, resourceGroup, subscriptionId,
    sku = tostring(sku.name),
    sizeGB = toint(properties.diskSizeGB),
    lastDetached = todatetime(properties.LastOwnershipUpdateTime)
| order by sizeGB desc
  • The prize: a managed disk is billed on its provisioned size tier, regardless of how much data sits on it. A 1 TiB Premium SSD (P30) lists at about $135 a month. One forgotten disk is that, every month. Exclude Azure Site Recovery replica disks in your DR region, which sit unattached by design; Advisor skips them, your query will not.

2. Public IP addresses with no association

  • Look for: public IPs not attached to any network interface, load balancer, gateway or NAT gateway. Basic SKU public IPs were retired on September 30, 2025, so most of what you have is Standard, and Microsoft's pricing FAQ says a static public IP is charged irrespective of the associated resource.

  • How to check: filter microsoft.network/publicipaddresses on isnull(properties.ipConfiguration) and isnull(properties.natGateway).

  • The prize: small per address, about $3.65 a month. The reason to look is that they accumulate by the dozen. Before deleting, search DNS records and partner allowlists for the address: a deleted static IP is released and cannot be recovered.

3. Old and oversized snapshots

  • Look for: snapshots older than 90 days, full (non-incremental) snapshots taken on a schedule, snapshots stored on Premium storage, and snapshots whose source disk no longer exists.

  • How to check: query microsoft.compute/snapshots and project properties.timeCreated, properties.incremental, sku.name and properties.creationData.sourceResourceId.

  • The prize: snapshots are billed on used size, at about $0.05 per GB-month on Standard storage and $0.132 per GB-month when kept on Premium storage, roughly 2.6 times more. A scheduled full snapshot with no deletion step grows the bill every week, forever.

4. App Service plans with no apps

  • Look for: plans in a dedicated tier (Basic and above) hosting zero apps. Microsoft's own cost guide says that when you delete all apps in a plan, the plan keeps accruing charges at its tier and instance count.

  • How to check: Advisor has a preview recommendation for unused plans. In Resource Graph, filter microsoft.web/serverfarms on toint(properties.numberOfSites) == 0 and exclude the Free, Shared and Dynamic tiers.

  • The prize: the full plan price times its instance count. A single P1v3 Windows instance lists at about $230 a month.

5. Backups of VMs that no longer exist

  • Look for: protected items in Recovery Services vaults whose source VM was deleted without stopping protection. Microsoft documents that these items stay active, and that the most recent recovery point is retained forever and charged at backup pricing.

  • How to check: in each vault, open Backup items and compare against the VMs that exist. Resource Graph exposes the source VM ID as properties.sourceResourceId in the RecoveryServicesResources table, which you can join against your VM list.

  • The prize: the protected-instance fee plus the backup storage, every month, with no end date. Use "Stop protection and delete backup data". Deleted data stays in soft delete for 14 days, and recovery points moved to the Archive tier carry a 180-day early deletion charge.

6. Gateways and load balancers with nothing behind them

  • Look for: VPN gateways with zero connections, NAT gateways attached to no subnet, Standard load balancers with rules but empty backend pools, and Application Gateways with empty backends.

  • How to check: Advisor flags virtual network gateways with zero connections. For NAT gateways, filter on an empty properties.subnets. Skip AKS-managed resource groups (the MC_ ones), where the cluster manages its own load balancer.

  • The prize: a VpnGw1 gateway lists at $0.19 an hour, about $139 a month, and VpnGw2 at more than twice that. NAT gateways bill hourly whether or not a subnet is attached. A Standard load balancer has no hourly charge when it has no rules, so the ones worth finding are those with rules and nothing to route to.

7. Fixed-fee services nobody uses

  • Look for: Azure Bastion hosts deployed for one emergency session, API Management instances left over from a proof of concept, and Premium container registries using none of the Premium features.

  • How to check: ask who last used each one. For registries, check whether geo-replication or private endpoints are configured; those are what Premium adds.

  • The prize: Bastion is billed hourly from deployment until deletion, whether or not anyone connects ($0.19 an hour for Basic, $0.29 for Standard). The Developer SKU is free for dev/test, one connection at a time. A Premium registry costs 2.5 times a Standard one per day. A Developer-tier API Management unit is about $48 a month; a Premium unit is about $2,800.

Is compute running longer or larger than it needs to? (checks 8 to 16)

8. VMs stopped but not deallocated

  • Look for: VMs in the Stopped state, which Microsoft's power state table marks as billed. Only Deallocated stops the compute charge. We explain the trap in why your stopped dev VMs are still billing.

  • How to check:

resources
| where type =~ 'microsoft.compute/virtualmachines'
| where tostring(properties.extended.instanceView.powerState.code) =~ 'PowerState/stopped'
| project name, resourceGroup, subscriptionId,
    size = tostring(properties.hardwareProfile.vmSize)
resources
| where type =~ 'microsoft.compute/virtualmachines'
| where tostring(properties.extended.instanceView.powerState.code) =~ 'PowerState/stopped'
| project name, resourceGroup, subscriptionId,
    size = tostring(properties.hardwareProfile.vmSize)
resources
| where type =~ 'microsoft.compute/virtualmachines'
| where tostring(properties.extended.instanceView.powerState.code) =~ 'PowerState/stopped'
| project name, resourceGroup, subscriptionId,
    size = tostring(properties.hardwareProfile.vmSize)
  • The prize: the full compute rate, for a machine nobody is using.

9. Deallocated VMs still carrying their baggage

  • Look for: VMs deallocated for weeks. Their disks, static IPs and backups keep billing. A disk attached to a deallocated VM shows the state Reserved.

  • How to check: filter disks on properties.diskState =~ 'Reserved' and sort by size.

  • The prize: decide whether the VM is coming back. If not, decommission it fully: backup protection first, then the VM, disks, IPs and NICs. If it might, a snapshot billed on used size is often cheaper than a provisioned Premium disk.

10. Non-production VMs running all night

  • Look for: dev, test and staging VMs with no shutdown schedule.

  • How to check: in the VM, Operations, Auto-shutdown. Auto-shutdown schedules are microsoft.devtestlab/schedules resources whose properties.targetResourceId points at the VM, so a left join in Resource Graph lists every running VM without one. Check the time zone: it defaults to UTC.

  • The prize: a VM that runs 12 hours on weekdays instead of 24/7 runs 60 of 168 hours a week, which cuts its compute by about 64%. Disks still bill.

11. Idle VMs

  • Look for: running VMs with near-zero CPU and network for weeks.

  • How to check: Advisor raises a shutdown recommendation for VMs it judges unused over the last seven days. Seven days is short, so confirm in the VM's metrics over a month and ask the owner.

  • The prize: the whole compute cost, then the disks and IPs once you decommission.

12. Oversized VMs

  • Look for: VMs whose average CPU sits well under 40% for weeks.

  • How to check: Advisor resize recommendations, then the VM's metrics. Look at memory too, if the guest reports it; a memory-bound VM can show low CPU and still need its size.

  • The prize: inside a family, price scales with size. A D8s_v5 lists at $0.384 an hour and a D4s_v5 at half that. One step down halves the compute cost. A resize restarts the VM, so schedule it.

13. Pricier variants of the same size

  • Look for: sizes where a sibling series is cheaper for the same vCPU and memory.

  • How to check: compare list prices for your region. In East US, a Linux D4as_v5 (AMD) lists at $0.172 an hour against $0.192 for the D4s_v5, about 10% less. Newer is not automatically cheaper: the D4s_v6 lists at $0.202. Read the target size's requirements on Microsoft Learn before moving.

  • The prize: the price gap between the two series, about 10% in this example, for the same vCPU and memory.

14. Windows and SQL Server without Hybrid Benefit

  • Look for: Windows Server VMs and SQL resources paying the licence-included rate while your organization holds eligible licences with Software Assurance or subscriptions.

  • How to check: list Windows VMs where properties.licenseType is empty, then reconcile against what procurement holds. We cover the details in the Azure discount you already paid for.

  • The prize: the Windows licence premium on every compute hour. A Windows D2s_v5 lists at $0.188 an hour against $0.096 for Linux, so the licence is almost half the price.

15. Premium SSD where Standard SSD would do

  • Look for: Premium SSD disks on VMs that barely touch their IOPS: dev boxes, jump hosts, light application servers.

  • How to check: the VM's disk IOPS consumed percentage metrics over a month.

  • The prize: a P10 lists at $19.71 a month and the equivalent E10 Standard SSD at $9.60. A P30 is $135.17 against $76.80. Standard SSD adds a per-transaction charge, capped per hour. You can change a disk's type only twice a day, and Microsoft's documented procedure deallocates the VM first.

16. Disks provisioned far above what they hold

  • Look for: a 1 TiB disk holding 100 GB.

  • How to check: free space inside the guest operating system, from VM insights or the OS itself. Azure only sees the provisioned size.

  • The prize: the step between tiers. Microsoft does not support shrinking a managed disk, so the fix is a new smaller disk and a data copy. Worth it only for large gaps on expensive tiers.

Which platform services are sized for a peak? (checks 17 to 25)

17. App Service plans with too many instances or too few apps

  • Look for: plans scaled out to fixed instance counts with low CPU, and many plans each hosting one small app.

  • How to check: the plan's CPU and memory metrics, and the app count per plan. Microsoft publishes density guidance: a P1v3 can host about 16 apps, a P2v3 about 32. Autoscale needs Standard or above.

  • The prize: every instance is billed, whether or not apps use it, so consolidation and scale-in remove whole instances. On Linux, a P0v3 (1 vCPU, 4 GB) lists below an S1 (1 core, 1.75 GB): $0.0775 an hour against $0.095.

18. AKS node pools that never shrink

  • Look for: user node pools without the cluster autoscaler, dev clusters running nights and weekends, and dev/test pools on regular VMs.

  • How to check: in the cluster, Node pools: autoscaling state and node counts.

  • The prize: the autoscaler removes underused nodes when pods can be rescheduled. Spot node pools suit dev/test and batch, with no SLA and possible eviction. A stopped AKS cluster stops compute and keeps its state for up to 12 months.

19. Azure SQL databases sized above their peak

  • Look for: single databases whose peak DTU or vCore use sits well under capacity, and servers with several small databases each paying for its own capacity.

  • How to check: the database's CPU and DTU (or vCore) percentage metrics over a month, at peak rather than average.

  • The prize: one service objective down saves the price step. For groups of databases with low average use and occasional spikes, an elastic pool shares capacity. Microsoft notes the eDTU unit price is 1.5 times the single-database DTU price, so pools save money only when the databases do not all peak together.

20. Databases that could pause or stop

  • Look for: dev and test databases running 24/7, and production databases used a few hours a day.

  • How to check: activity by hour over a typical week.

  • The prize: Azure SQL Database serverless in General Purpose bills compute per second and pauses after a configurable delay (minimum 15 minutes), leaving only storage billed. Azure Database for PostgreSQL flexible server can be stopped for up to seven days before it restarts on its own; MySQL flexible server, up to 30 days.

21. Cosmos DB throughput on the wrong model

  • Look for: containers on manual throughput with spiky traffic, and containers on autoscale running near the maximum all day.

  • How to check: the Normalized RU Consumption and Provisioned Throughput metrics.

  • The prize: autoscale scales between 10% and 100% of the maximum and bills each hour for the highest level reached. Microsoft's rule of thumb: if you use the full maximum for 66% or fewer of the hours in a month, autoscale saves money. Above that, manual throughput is cheaper.

22. Redis caches sized for launch day

  • Look for: Azure Cache for Redis instances whose used memory stays far below the tier they were created on.

  • How to check: the used memory percentage over a month, at peak.

  • The prize: the price step to the next size down in the same family. Throughput and connection limits drop with it, so test.

23. Log Analytics workspaces keeping too much, too long

  • Look for: analytics retention far beyond what anyone queries, verbose tables on the Analytics plan, and workspaces near 100 GB a day on pay-as-you-go.

  • How to check: the workspace's Usage and estimated costs page, and the retention setting per table.

  • The prize: Analytics Logs include 31 days of retention at no extra cost; beyond that you pay. Basic and Auxiliary table plans ingest at reduced flat rates but charge per GB scanned when you query, so they suit logs you rarely read. Commitment tiers start at 100 GB a day, can save up to 30%, and lock in for 31 days. Our Log Analytics cost guide works through this in detail.

24. Geo-redundant storage nobody asked for

  • Look for: storage accounts on GRS, RA-GRS or GZRS holding logs, build artefacts, test data or anything you can recreate.

  • How to check: the Redundancy setting on each storage account, against what the data actually needs.

  • The prize: geo-redundancy keeps a second copy in another region, and you pay for that copy. Keeping it is a disaster-recovery decision, so make it deliberately per account.

25. Storage with no lifecycle policy

  • Look for: accounts with large, ageing blob data and no lifecycle management policy.

  • How to check: Data management, Lifecycle management on the storage account.

  • The prize: policies move blobs to cooler tiers or delete them by age, and the policy itself is free (tier changes are billed as operations). Mind the minimums: cool 30 days, cold 90, archive 180. Data deleted or moved earlier is charged for the remainder. Archiving small, short-lived objects can cost more than it saves.

Are your commitments working? (checks 26 to 28)

26. Reservations you are not fully using

  • Look for: reservations used less than about 95% of the time.

  • How to check: Reservations in the portal shows each reservation's last known usage percentage; select it for the history. You can also set alerts on low usage.

  • The prize: an idle reserved hour is prepaid and lost. Fix it by widening the scope (single subscription to shared), exchanging, or returning: refunds are capped at USD 50,000 of cancelled commitment in a rolling 12 months per billing profile or enrollment.

27. Renewals and the 2027 exchange change

  • Look for: reservations expiring in the next 90 days and their renewal setting. Microsoft turns renewal on by default at purchase.

  • How to check: Reservations, Renewal on each.

  • The prize: avoiding two mistakes: a renewal that silently re-buys capacity you no longer run, and an expiry that drops a steady workload back to pay-as-you-go. Also note, as of October 2026: reservations bought after February 1, 2027 for services that savings plans cover (VMs, App Service, SQL Database and others) will not be exchangeable. Reservations bought before that date keep one final exchange.

28. Steady usage with no commitment at all

  • Look for: compute that has run at a stable baseline for months on pay-as-you-go.

  • How to check: Advisor's reservation and savings plan recommendations, and the recommendations shown when you start a purchase in Reservations.

  • The prize: the discount on the baseline. Reservations go deeper for a fixed size and region; a savings plan for compute applies across VMs, App Service, Functions Premium and Container Apps. On App Service, only the Premium v3, Premium v4 and Isolated v2 plans are eligible; Basic and Standard are not. We compare the two in reservations vs savings plans.

What stops the waste coming back? (checks 29 and 30)

29. Resources nobody owns

  • Look for: resources without an owner and environment tag. Most items on this list survive because nobody knows whose they are.

  • How to check: Azure Policy's compliance view, with the built-in definitions "Require a tag on resources" and "Inherit a tag from the resource group if missing".

  • The prize: indirect but large: every later check becomes a message to a named person instead of a meeting.

30. Budgets and anomaly alerts per subscription

  • Look for: subscriptions with no budget, or budgets nobody receives.

  • How to check: Cost Management, Budgets, and the anomaly alert option in Cost analysis.

  • The prize: earlier warning. Cost data typically lands within 8 to 24 hours and budgets are evaluated every 24 hours, so an alert tells you about yesterday, not about this hour. That is still weeks earlier than the invoice.

What to do after the first pass

The first pass usually produces a long list. The work that matters is turning each line into a decision with a name on it, an approval, and a check against next month's bill. Advisor stops at the recommendation, and so does any checklist. These 30 checks are close to what Elos, the scanner Fidelis Solutions builds, runs every week across a tenant. Run them yourself this quarter, starting with checks 1 to 7, and keep the results with owners attached.

Frequently asked questions

How often should we run an Azure cost optimization checklist?

Run the leftover checks (disks, IPs, snapshots, empty plans, orphaned backups) monthly, because they come back every time someone decommissions something in a hurry. Run the sizing and commitment checks quarterly, since they need a few weeks of usage data to be trustworthy. Budgets, anomaly alerts and tag policies are set once and then reviewed when the organization changes.

Which Azure cost checks are safest to start with?

Start with resources that bill while nothing uses them: unattached managed disks, public IP addresses with no association, App Service plans with no apps, and backup items for virtual machines that no longer exist. Deleting them does not change how any running workload behaves. Take a snapshot or confirm with the owner first, and check for resource locks before you delete.

Does Azure Advisor cover this checklist?

Partly. Advisor flags unattached disks, idle and underutilized VMs, empty App Service plans, virtual network gateways with no connections, and reservation and savings plan opportunities. It does not check Hybrid Benefit gaps, dev/test schedules or geo-redundant storage choices, and it does not assign an owner or verify that a saving reached the bill.

What is the biggest Azure cost saving for a mid-market company?

It depends on the tenant, but in mid-market environments the largest single items are usually compute: non-production VMs running around the clock, oversized VMs and App Service plans, and steady usage with no reservation or savings plan. Leftovers are smaller per item but are the fastest to remove and carry almost no risk.

Sources

See where your Azure money is leaking.

Start with the free Score: ten questions, no tenant access. Or go straight to the 20-minute call.