Why this matters
Idle resources in Azure keep charging after the work stops. A deallocated virtual machine stops compute billing, but its managed disks, public IP address, snapshots, and load balancer rules can remain. An App Service plan bills for reserved capacity even when every app on it is stopped. Log Analytics keeps ingesting and retaining data. The bill is granular, so leftovers hide in resource groups.
The cost is not the only issue. Idle resources consume subscription quotas, add noise to monitoring, and widen the attack surface. Orphaned network interfaces and public IP addresses confuse troubleshooting. Old disks hold data that no owner tracks. Removing them makes cost reports and architecture diagrams match reality.
Azure Advisor cost recommendations are a useful start, but they do not cover every idle pattern. You need an inventory, usage evidence, and a named owner. Treat removal as a change with a rollback path, not a one-time purge.
Before you start
Get read access first. You need Reader on each subscription, Cost Management Reader for cost views, and Log Analytics Reader for workspace queries. Use a separate change role and an approval step for deletes. Do not run a cleanup from a personal account with Owner at the tenant root.
Define idle by resource type and write the rule down. For a virtual machine, idle can mean low CPU, low network, and no recent logins. For a disk, it means Unattached. For a public IP address, it means no association. For an App Service plan, it means no running apps and low worker usage. Use a lookback window that covers your monthly cycle and known seasonal work, such as 30 to 45 days.
Build a baseline. Export a Resource Graph inventory and a Cost Management view grouped by resource group, resource type, and tag. Record owner, environment, cost center, and created date. Check resource locks, backup vaults, DNS records, and deployment pipelines. Tell application owners what you plan to review. Keep the evidence in a ticket.
How to do it
Work subscription by subscription, even when you query across all of them. Use Azure Resource Graph Explorer to save each query, then narrow the results to resource types that bill. Cost Management shows where the money goes, but Resource Graph shows what exists.
For each candidate, collect three things: usage evidence, an owner, and a recovery path. If any one is missing, tag the resource for review instead of deleting it. The steps below move from inventory to safe removal.
- Pull an inventory with Resource Graph. In Azure Resource Graph Explorer, query resources across subscriptions and project id, name, type, resource group, location, tags, sku, and properties. Export the result to CSV. Keep a dated copy so you can show what existed before the cleanup.
- Check VM power state and activity. Use Azure Monitor metrics for CPU, network, and disk, and the Activity Log for start, stop, and redeploy events. A VM shown as Stopped was shut down from the guest OS and can still bill compute. Stopped (deallocated) releases the compute allocation.
- Find unattached disks, snapshots, and images. Query managed disks where diskState is Unattached, then check snapshots and images with no source or owner tag. Look at the managedBy field and the last attach time. An unattached disk still bills for provisioned size and tier.
- Review public IPs, NICs, and load balancers. Find public IP addresses with no IP configuration and network interfaces with no virtual machine. Check load balancer frontend and backend pools for empty entries. Release public IPs only after the DNS and firewall owners confirm they are unused.
- Inspect App Service plans, AKS, and data services. An App Service plan bills even when its apps are stopped, so check worker count and plan tier. In AKS, review node pools with low pod density and autoscaling limits. Review Azure SQL Database serverless auto-pause settings and pause Synapse dedicated SQL pools when idle.
- Check storage and log retention. Use storage account lifecycle management to move old blobs to cooler tiers and delete stale containers with owner approval. In Log Analytics, review table usage, retention, and daily cap. Drop custom tables that no query or alert uses.
- Confirm owner and dependencies. Search tags, deployment history, and DNS for each candidate. Ask the application owner to confirm in writing. Check resource locks, backup recovery points, and pipeline references. If a resource has no owner, tag it and set a review date.
- Remove in a safe order. Deallocate VMs first, then snapshot or back up disks, detach, and wait. Delete unattached disks, orphaned NICs, and unused public IPs after confirmation. Remove empty App Service plans and node pools last, then close the ticket with the evidence.
How to check it worked
Re-run the same Resource Graph queries and compare counts by type and resource group. Check the Activity Log for delete operations and confirm each one maps to a change ticket. Verify that VMs are deallocated or gone, disks show Attached only for active VMs, and public IPs have associations.
Use Cost Management daily costs, grouped by resource and tag. Billing data lags, so wait a day or two before you judge the result. Look for the line items you removed and confirm they stop. Check that budget alerts and anomaly alerts did not fire for an unexpected reason.
Test the remaining system. Open key apps, run smoke tests, check backups, DNS, and monitoring. Review Advisor again and close recommendations tied to removed resources. Update the CMDB or tag inventory so the next review starts with better data.
Mistakes to avoid
Do not delete on CPU alone. A database VM can look idle while it serves nightly batch work. A jump host can sit quiet for days and still be needed. Check network, disk, logins, and the owner's schedule.
Do not treat Stopped as deallocated. Do not delete a public IP that still has a DNS record or firewall rule. Do not delete a disk before the backup job has a verified restore point. Do not remove a resource group to clean up one resource, because it takes everything with it.
Do not skip locks and pipelines. A delete lock is a signal, not a nuisance. A resource may be referenced by an Azure Resource Manager template, a Bicep file, or Terraform state. Remove the reference first or the next deployment will recreate it.
If you want a second pass, the Cloud cost audit from Cloudverg is a fixed price $4,500 engagement that reviews up to three subscriptions and ranks idle resource and storage tier findings. It is one option among others, and it is read-only.
Frequently asked questions
How do I find idle resources in Azure?
Start with Azure Advisor cost recommendations, then query Azure Resource Graph for unattached disks, unassociated public IPs, and stopped VMs. Add Azure Monitor metrics and Cost Management to confirm usage. Tag candidates and get owner approval before deletion.
Does a stopped Azure VM still cost money?
Yes, if it is only stopped from inside the guest OS. The status Stopped keeps the compute allocation and bills. Use Stop in the portal or Stop-AzVM to reach Stopped (deallocated), which releases compute billing, though disks and IPs can still cost.
What Azure resources are commonly idle but still billed?
Unattached managed disks, unused public IP addresses, snapshots, empty App Service plans, idle AKS node pools, and Log Analytics retention are common. Load balancers and private endpoints can also carry cost or quota. Check each type with its own evidence.