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:
- Open Cost Management + Billing in the portal.
- Select Cost analysis.
- Set the time range to Last 30 days.
- Change the chart type from Area chart to Table.
- In the Group by dropdown, select Resource.
- 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.
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 tableUnderstanding 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 tableAzure 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 tableAzure 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 tableUnused 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 tableEmpty 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 tableSnapshot 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 tableMonthly Cost Investigation Checklist
Run this checklist monthly to maintain visibility over cost drivers:
| Check | Tool | Target |
|---|---|---|
| Top 10 resources by cost | Cost Analysis, group by Resource | Understand all resources over $50/month |
| Underutilised VMs | Azure Advisor, Cost category | Any high-impact recommendations |
| Stopped (not deallocated) VMs | CLI: az vm list —show-details | Zero VMs in “VM stopped” state |
| Orphaned managed disks | CLI: az disk list, filter Unattached | Zero unattached disks with no active project |
| Unused public IPs | CLI: az network public-ip list | Zero IPs with no associated resource |
| Empty App Service Plans | CLI: az appservice plan list | Zero plans with numberOfSites=0 |
| Old snapshots | CLI: az snapshot list | No snapshots older than retention policy |
| Idle ExpressRoute circuits | Azure Advisor | All circuits actively used |
Common Mistakes
- 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.
- 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.
- 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.
- 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.
Summary
- 20% of resources typically account for 80% of Azure costs. Focus optimisation effort on the top contributors first.
- Use Cost Analysis grouped by Resource for an immediate ranked list of top cost drivers. Switch to amortised cost when you have Reserved Instances.
- Azure Advisor provides specific, calculated recommendations for underutilised VMs and RI purchases based on actual usage data.
- Orphaned resources — unattached disks, unused IPs, empty App Service Plans, old snapshots — are common sources of quiet but avoidable cost.
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.