Back to Blog

Finding Orphaned Azure Resources Before They Find Your Bill

StratoLens Team
Cost OptimizationAzure Resource GraphFinOps

Every Azure environment accumulates clutter. A VM gets deleted but its managed disk stays behind. A public IP loses the load balancer it fronted. A NIC outlives its VM. None of these resources do anything anymore, but all of them keep billing.

Individually they look like rounding errors. Across dozens of subscriptions and a few years of churn, they add up to real money, and they make every audit, migration, and security review slower than it needs to be.

The Problem: Deletion in Azure Is Not Cleanup

Azure rarely deletes dependent resources for you. When you remove a VM through the portal, the OS disk, data disks, NICs, and public IPs are all separate resources with their own lifecycles. Unless someone explicitly checks the cleanup boxes (or your automation handles it), they survive.

The result is a class of resources that:

  • Cost money with zero value. A premium SSD managed disk bills the same whether or not anything is attached to it.
  • Are invisible in day-to-day work. Nobody opens a resource group to admire an unattached disk. They only surface when someone goes looking.
  • Lack context. Six months later, nobody remembers what pip-prod-eastus-04 used to front, so nobody is confident enough to delete it.

That last point is the killer. Finding orphans is a query problem; deleting them safely is a history problem.

Finding Orphans Manually with Azure Resource Graph

If you want to do this by hand, Azure Resource Graph is the right tool. It queries across all your subscriptions at once, which the portal cannot do well.

Unattached managed disks:

Resources
| where type =~ 'microsoft.compute/disks'
| where properties.diskState =~ 'Unattached'
| project name, resourceGroup, subscriptionId, location,
          skuName = sku.name, sizeGB = properties.diskSizeGB

Public IPs with nothing behind them:

Resources
| where type =~ 'microsoft.network/publicipaddresses'
| where isempty(properties.ipConfiguration) and isempty(properties.natGateway)
| project name, resourceGroup, subscriptionId, location, skuName = sku.name

NICs not attached to any VM:

Resources
| where type =~ 'microsoft.network/networkinterfaces'
| where isempty(properties.virtualMachine)
| where isempty(properties.privateEndpoint)
| project name, resourceGroup, subscriptionId, location

These queries work, and you should know how to write them. But notice what they don't give you:

  1. Cost impact. Resource Graph tells you a disk exists, not that it costs $147/month. You have to join against billing data yourself.
  2. History. The query shows today's state. It can't tell you what that public IP was attached to last quarter or when it was orphaned.
  3. Usage-based idleness. Some waste isn't structurally orphaned at all. An Azure Bastion host with zero connections in 60 days passes every "is it attached" test while billing around $140/month.
  4. Repeatability. Someone has to remember to run these queries, across every subscription, on a schedule, and chase down the results.

How StratoLens Automates It

StratoLens runs this hunt continuously as part of its scheduled scans of your environment, then layers on the context the raw queries can't provide:

  • Broad detection out of the box. Unattached disks, unused public IPs, orphaned NICs, and more, with no KQL to write or maintain.
  • Cost impact per resource. Every finding shows what it's actually costing you, so you clean up the $147/month premium disk before the $2/month standard one.
  • Usage-based analysis. Idle resources like Bastion hosts with no connections over a configurable timeframe get flagged too, not just structurally detached ones.
  • Historical context. Because StratoLens snapshots your environment over time, it can show what an orphaned resource used to be attached to and when it was last connected. That turns "is it safe to delete this?" from an archaeology project into a one-line answer.

And since StratoLens runs entirely inside your own Azure tenant, the inventory and cost data behind these findings never leaves your subscription.

Make It a Habit, Not a Project

Orphaned-resource cleanup works best as a small recurring routine, like a monthly review of new findings sorted by cost impact, rather than a heroic annual purge. The KQL queries above are a fine starting point. If you'd rather have the detection, cost impact, and attachment history handled for you, take a look at Orphaned Resources in StratoLens.

Start Your 28-Day Free Trial

Every feature unlocked. Deploys to your Azure tenant. No data leaves your tenant.

Available now on the Azure Marketplace.

Built for Azure infrastructure teams who need complete visibility across their entire estate.