Towards the end of the year I spend a fair amount of time helping organisations tidy up their Azure estate before the next budget cycle starts. Orphaned Azure resources come up every time. Unattached managed disks, unused public IPs, empty resource groups, orphaned NICs, old snapshots and App Service plans with no apps on them. These leftovers push up costs, sometimes by a surprising amount, and if nobody looks at them they can also turn into governance and even security problems. In many of the tenants I review, this kind of drift eats into the budget without anyone noticing.
Orphaned resources tend to fall into the same few categories
I see the same patterns in almost every Azure environment. Managed disks stay behind after a VM is deleted, public IPs keep getting billed long after the server is gone, resource groups sit empty, and network interfaces (NICs) hang around with nothing attached to them. App Service plans are easy to forget once the last app has been removed, and old snapshots pile up quietly. These are the types I run into most often:
- Managed disks with
diskState == "Unattached" - Public IP addresses not assigned to any resource
- Empty resource groups
- NICs with no associated VM or load balancer
- Snapshots not linked to any production resource
- App Service plans without web apps
Unattached managed disks and public IPs are the worst offenders when it comes to ongoing costs. If you take cost control seriously, you need to check for them regularly.
Azure Resource Graph finds orphans across the whole tenant
When I want to find orphaned Azure resources across a subscription or a whole tenant, I usually start with the Resource Graph Explorer in the Azure portal. A few KQL queries are enough to show you what has outlived its purpose. This is a query I've used many times to find all unattached managed disks (Azure portal → Resource Graph Explorer, Reader role or higher):
Resources
| where type == "microsoft.compute/disks"
| where properties.diskState == "Unattached"
| project name, resourceGroup, location, sku, properties.diskSizeGb
It returns every managed disk that isn't currently attached to a VM, along with the name, resource group, location and size. With small changes, the same approach finds orphaned public IPs (microsoft.network/publicipaddresses where ipConfiguration.id is empty) or unused NICs (microsoft.network/networkinterfaces where virtualMachine.id is empty). If you're new to this, the Resource Graph documentation and the sample queries are good places to start.
Practical tip: If you run these queries often, save them as favorites or pin them to an Azure Workbook so they're ready the next time cleanup comes around.
Azure Advisor provides a baseline of cost recommendations
Resource Graph gives you full control over what you query. Azure Advisor (Azure portal → Advisor → Cost) takes a different angle and points out the obvious savings for you. It looks at your current environment and flags unused disks, idle public IPs and outdated snapshots. In smaller or less mature environments, the cost recommendations catch a good share of the usual waste. In larger or more complex tenants, Advisor on its own will miss things, but I still check it after running my queries.
Tagging and governance prevent most of the cleanup work
I keep coming back to the same conclusion: it's easier to prevent orphans than to clean them up later. If you tag resources consistently for ownership (for example Owner, Project or CostCenter), it becomes much easier to work out who is responsible for what. This is a core part of landing zone governance, and I've written about it in /blog/azure-landing-zone-management-groups-policy and in /blog/azure-cost-control-budgets-tags-advisor. When tagging is enforced and reviewed regularly, both cost allocation and cleanup get a lot simpler. Good tags also make your cost reports more useful, which takes some of the pressure off during the budget review.
Deletion should follow a grace period
Once you have your list of orphaned resources, don't rush to delete them. Some resources only look orphaned and are actually kept on purpose, for backups, staged migrations or compliance reasons. In practice I use a two-phase process with a grace period:
flowchart TD
Find["Identify resources"] --> Tag["Tag as 'ToDelete'"]
Tag --> Notify["Notify owners"]
Notify --> Wait["7-day grace period"]
Wait --> Delete["Delete resources"]
Figure: Suggested cleanup workflow with tagging, notification and a grace period.
Tag the resources with something like ToDelete and notify the owner listed on them. That gives people a chance to object before anything is gone for good. After a fixed period (I usually choose 7 days), you can go ahead with the deletion. For particularly sensitive resource types, an approval workflow can make sense.
Cleanup works best as a habit rather than a project
Orphaned Azure resources aren't something you fix once. They're a normal part of how cloud environments change over time. If you tag consistently and review regularly with Resource Graph Explorer, Advisor and Workbooks, your Azure environment stays tidy, cheaper to run and more secure. Going into the new year with a clean estate helps the budget, lowers your risk and makes the next audit a lot less stressful for everyone.


