Identifying Expensive Resources in Azure

Before you can optimise Azure costs, you need to know exactly which resources are responsible for them. Most Azure environments have a small number of resources generating the majority of the bill — finding them precisely is the first step toward meaningful savings.

The Pareto Principle in Azure Billing

In practice, roughly 20% of your Azure resources typically account for 80% of your costs. These high-cost resources are usually VMs, managed databases, AKS node pools, and data services. Identifying and optimising these top contributors delivers far better returns than trying to optimise the entire environment simultaneously.

The goal of this investigation is to produce a prioritised list: resources ranked by monthly cost, with a note about whether each one is appropriately sized, actively used, and on the right pricing tier.

Finding Top Resources in Cost Analysis

The fastest way to identify expensive resources is through the Azure portal’s Cost Analysis tool:

  1. Open Cost Management + Billing in the portal.
  2. Select Cost analysis.
  3. Set the time range to Last 30 days.
  4. Change the chart type from Area chart to Table.
  5. In the Group by dropdown, select Resource.
  6. Sort the table by cost descending.

This gives you a ranked list of every resource by cost for the past month. The top 10 entries are your investigation starting point.

Note

Resource costs in Cost Analysis reflect the raw Azure charges. If you have Reserved Instances or Savings Plans, switch to the Cost (amortised) metric to see an accurate per-resource cost that accounts for your discounts.

Querying Top Resources with the CLI

# List top 20 most expensive resources in the last 30 days
az costmanagement query \
  --type Usage \
  --scope "/subscriptions/$(az account show --query id -o tsv)" \
  --timeframe Custom \
  --time-period from="$(date -d '30 days ago' +%Y-%m-%d)" to="$(date +%Y-%m-%d)" \
  --dataset-granularity None \
  --dataset-aggregation "{\"totalCost\":{\"name\":\"Cost\",\"function\":\"Sum\"}}" \
  --dataset-grouping "[{\"name\":\"ResourceId\",\"type\":\"Dimension\"},{\"name\":\"ResourceType\",\"type\":\"Dimension\"}]" \
  --dataset-filter "{\"and\":[{\"dimensions\":{\"name\":\"ResourceType\",\"operator\":\"In\",\"values\":[\"microsoft.compute/virtualmachines\",\"microsoft.sql/servers/databases\",\"microsoft.containerservice/managedclusters\"]}}]}" \
  --query "properties.rows | sort_by(@, &[0]) | reverse(@) | [:20]" \
  --output table

# Find all stopped VMs that are still incurring disk charges
az vm list \
  --query "[?powerState=='VM stopped'].{Name:name, RG:resourceGroup, Size:hardwareProfile.vmSize}" \
  --show-details \
  --output table

# Find all orphaned managed disks (not attached to a VM)
az disk list \
  --query "[?diskState=='Unattached'].{Name:name, RG:resourceGroup, SizeGB:diskSizeGb, SKU:sku.name}" \
  --output table

Understanding stopped VMs vs deallocated VMs

A VM in a “stopped” state (from within the OS) still reserves compute capacity and incurs the full VM compute charge. A VM in a “deallocated” state (stopped from the Azure portal or CLI) releases compute capacity and does not incur compute charges, though you still pay for attached disks and any reserved IPs.

# Properly deallocate a VM (stops compute billing)
az vm deallocate \
  --resource-group my-rg \
  --name my-vm

# Check the actual power state of all VMs
az vm list \
  --show-details \
  --query "[].{Name:name, State:powerState, Size:hardwareProfile.vmSize, RG:resourceGroup}" \
  --output table

Azure Advisor Cost Recommendations

Azure Advisor analyses your subscription’s usage patterns and produces specific, actionable cost recommendations. These are not generic suggestions — they are calculated based on your actual resource metrics over the past 7, 14, or 30 days.

Advisor cost recommendations typically include:

  • Right-size or shut down underutilised VMs: VMs with average CPU below 5% and low network activity are flagged as candidates for shutdown or downsizing, with specific projected savings.
  • Purchase Reserved Instances: resources with consistent usage patterns are identified as RI candidates with calculated savings amounts.
  • Eliminate idle Azure ExpressRoute circuits: ExpressRoute circuits with no provider activity for 30+ days.
  • Resize or delete unused virtual network gateways.
  • Delete or repurpose unused public IP addresses.
# Get all cost recommendations from Azure Advisor
az advisor recommendation list \
  --category Cost \
  --query "[].{Title:shortDescription.solution, Impact:impact, PotentialSavings:extendedProperties.savingsAmount, Resource:resourceMetadata.resourceId}" \
  --output table

# Get only high-impact cost recommendations
az advisor recommendation list \
  --category Cost \
  --query "[?impact=='High'].{Title:shortDescription.solution, Resource:resourceMetadata.resourceId}" \
  --output table
Tip

Azure Advisor aggregates projected savings across all recommendations. The total figure visible in the Advisor dashboard represents the estimated monthly savings if you implemented every recommendation. Even implementing the top three usually captures the majority of achievable savings.

Finding Orphaned and Forgotten Resources

Orphaned resources — those that exist in your subscription but serve no current purpose — are a major source of unnecessary cost. Common culprits include:

Orphaned managed disks

When a VM is deleted, its managed disks are not automatically deleted unless the “Delete OS disk when VM is deleted” option was enabled. An unattached 1 TB Premium SSD costs approximately $135/month in East US — an easy saving.

# List all unattached Premium SSDs with estimated cost
az disk list \
  --query "[?diskState=='Unattached' && sku.name=='Premium_LRS'].{Name:name, RG:resourceGroup, SizeGB:diskSizeGb, SKU:sku.name}" \
  --output table

Unused public IP addresses

Static public IP addresses cost approximately $0.005/hour (~$3.65/month) whether used or not. An environment with dozens of unused static IPs from deleted resources can accumulate $50–100/month in charges.

# Find all public IPs not associated with any resource
az network public-ip list \
  --query "[?ipConfiguration==null].{Name:name, RG:resourceGroup, IPAddress:ipAddress, SKU:sku.name}" \
  --output table

Empty App Service Plans

App Service Plans incur charges based on the plan tier, regardless of how many applications are deployed. An empty Standard S2 plan (~$150/month) with no apps deployed is pure waste.

# Find App Service Plans with no apps deployed
az appservice plan list \
  --query "[?numberOfSites==`0`].{Name:name, RG:resourceGroup, Tier:sku.tier, Size:sku.name, Cost:sku.tier}" \
  --output table

Snapshot and backup retention

Old VM snapshots and Azure Backup recovery points accumulate storage costs indefinitely. A 1 TB LRS snapshot costs approximately $20–30/month. Check your Recovery Services vaults and disk snapshot lists for items older than your retention policy.

# List VM snapshots older than 90 days
az snapshot list \
  --query "[?timeCreated < '2025-12-15'].{Name:name, RG:resourceGroup, SizeGB:diskSizeGb, Created:timeCreated}" \
  --output table

Monthly Cost Investigation Checklist

Run this checklist monthly to maintain visibility over cost drivers:

CheckToolTarget
Top 10 resources by costCost Analysis, group by ResourceUnderstand all resources over $50/month
Underutilised VMsAzure Advisor, Cost categoryAny high-impact recommendations
Stopped (not deallocated) VMsCLI: az vm list —show-detailsZero VMs in “VM stopped” state
Orphaned managed disksCLI: az disk list, filter UnattachedZero unattached disks with no active project
Unused public IPsCLI: az network public-ip listZero IPs with no associated resource
Empty App Service PlansCLI: az appservice plan listZero plans with numberOfSites=0
Old snapshotsCLI: az snapshot listNo snapshots older than retention policy
Idle ExpressRoute circuitsAzure AdvisorAll circuits actively used

Common Mistakes

  1. Looking at resources but not services. The top-10-resources view identifies individual resource instances, but sometimes an entire service category (e.g., Log Analytics data ingestion) costs more than any single resource. Run both views: group by Resource and group by Service Name.
  2. Assuming stopped VMs are not billing. VMs stopped from within the OS still accrue compute charges. Only deallocated VMs stop compute billing. Check for the “VM stopped” power state specifically.
  3. Only running investigations after a budget alert. A monthly scheduled review — not reactive investigation — is what keeps costs under control. By the time a budget alert fires, the cost has already happened.
  4. Deleting resources without verifying ownership. Before deleting any resource you identify as unused, check with the owning team. A “dev VM” might be a key part of someone’s workflow. Use a “quarantine” tag approach — tag the resource as a deletion candidate and give the team 2 weeks to respond before deleting.

Frequently asked questions

Which Azure services are typically the most expensive?

Compute (VMs and databases) typically accounts for 60–80% of most Azure bills. Azure SQL Database, Azure Virtual Machines, and Azure Kubernetes Service (node pool VMs) are usually the top three cost drivers. Storage and data egress are the next tier, depending on workload type.

How do I find resources that are running but not being used?

Azure Advisor identifies idle and underutilised VMs based on CPU, memory, and network usage. You can also query the Azure Monitor metrics API for VMs with average CPU under 5% over 30 days. For orphaned disks and unused IPs, query for resources in a "Detached" or "Unassociated" state.

Can I see per-resource cost in the Azure portal?

Yes. Navigate to a specific resource and click Cost analysis in its left menu. This shows the cost history for that individual resource. Alternatively, use Cost Management > Cost analysis and group by Resource to see a ranked list of all resource costs.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.