Orphaned Azure resources: an inventory of the things nobody remembers to delete
Deleting a virtual machine in the portal does not delete the things attached to it. It deletes the VM. The disk stays unless you ticked the box. The network interface stays. The public IP stays, and if it is a Standard SKU address it carries on billing at the same rate it did when something was actually listening on it.
That is the shape of most orphaned azure resources: not mistakes, but deletions that stopped one object short. Someone removed the VM, the alert went quiet, the ticket closed, and four objects carried on existing in a resource group nobody opens.
What counts as orphaned azure resources
Six categories turn up repeatedly. They differ in how much they cost, which matters when you are deciding what to look at first.
Unattached managed disks. The expensive ones. Billed by provisioned tier rather than by the data on them, so a 128 GB Premium SSD holding 12 GB still bills as a P10. There is a separate note on why disk pricing works that way, and it is the single most common source of wrong cost estimates.
Unassociated public IP addresses. Standard SKU addresses bill whether or not anything is behind them. This catches people out because Basic SKU addresses behaved differently, and Microsoft has been retiring Basic, so estates that were free of this problem have been acquiring it. The public IP address documentation covers the SKU differences.
Load balancers with an empty backend pool. Basic Load Balancer was free. Standard is not, and it bills on rules and processed data regardless of whether the backend pool contains anything. A Standard load balancer pointing at nothing is a rule set with a monthly charge.
Network interfaces with no VM. These genuinely cost nothing. They are worth listing anyway, because a NIC with no VM is evidence that a deletion did not finish, and where you find one you usually find the disk and the IP as well.
Stale snapshots. Cheaper per gigabyte than disks and easier to accumulate, because every backup tool and every cautious engineer creates them. Covered properly in the note on snapshot cost.
App Service plans with no apps. An empty plan on anything above the Free tier bills for the compute it reserved. This happens when someone deletes a web app and leaves the plan, which the portal does not warn about.
Sample output
Eight findings from a small estate, ranked by monthly cost, which is the order that matters when you have an afternoon:
Type Name USD/month
Managed Disk (unattached) disk-sqlbackup-temp 88.59
Managed Disk (unattached) disk-web01-old 23.85
Managed Disk (unattached) disk-scratch-2021 9.86
Public IP (unassociated) pip-legacy-vpn 3.65
Public IP (unassociated) pip-buildagent-old 3.65
Network Interface (no VM) nic-web01-old 0.00
Load Balancer (no backend) lb-internal-basic 0.00
Snapshot (>365 days) snap-migration-final 7.66
--------------------------------------------------------------
8 findings 137.26
Two things about that output are deliberate. The zero-cost rows are still listed, because they are the trail of breadcrumbs that leads to the expensive ones. And the total sits at the bottom with a caveat rather than being presented as a saving, for reasons covered in the note on reporting findings to finance.
Finding them
The queries are not difficult. Resource Graph will do most of it across every subscription you can see, which beats iterating with the management cmdlets:
resources
| where type =~ 'microsoft.compute/disks'
| where properties.diskState == 'Unattached'
| project subscriptionId, resourceGroup, name,
sku = sku.name,
sizeGB = properties.diskSizeGB,
created = properties.timeCreated
| order by sizeGB desc
And for addresses, where the absence of a configuration is the signal:
resources
| where type =~ 'microsoft.network/publicipaddresses'
| where isnull(properties.ipConfiguration)
| project subscriptionId, resourceGroup, name, sku = sku.name
The harder part is pricing them, because that means mapping a SKU to a regional rate rather than counting objects. A count tells you there are fourteen unattached disks. A cost tells you which three are worth the conversation.
Read-only, and loud about failure
Two rules matter more than the queries.
The first is that a cleanup tool should not clean up. It should tell you what it found and let you decide. Deletion is a separate, deliberate act, ideally after a snapshot, and the value of the tool is the inventory rather than the removal. The free Orphaned Resource Finder works this way, as does everything in the AzClean Toolkit.
The second is that it must fail loudly. A script that loses its token and prints "no orphaned resources found" has produced a clean report from an empty search, and that looks exactly like good news. It should exit non-zero so whatever scheduled it goes red rather than green. "Found nothing" and "could not look" are different answers and only one of them is reassuring.
Before you delete anything
Three checks, in this order, learned by getting the order wrong.
Snapshot first if the disk might hold something. A snapshot of a 128 GB disk costs a fraction of the disk and buys you a month of being able to change your mind.
Check the creation date and any tags. A disk created last Tuesday is a different
proposition from one created in 2021, and an owner tag turns a guess into an
email.
Look for the pattern rather than the object. Three orphans in the same resource group usually means one incomplete deletion, and finding the fourth is easier than finding the first.
Questions
Do unassociated public IP addresses really cost money?
Standard SKU addresses do, whether or not anything is associated with them. Basic SKU addresses behaved differently, which is why this surprises people who learned Azure a few years ago.
Does an empty load balancer cost anything?
A Basic Load Balancer does not. A Standard one does, and it bills on its rules regardless of whether the backend pool has any members. An internal Standard load balancer pointing at nothing is a common leftover from a rebuild.
Why list resources that cost nothing?
Because they are evidence. A network interface with no VM costs nothing and tells you a deletion stopped halfway, which means the disk and the address from that same VM are probably still there too.
Is it safe to delete an unattached disk?
It is safe in the sense that nothing is using it. Whether it is wise depends on what is on it, which is why snapshotting first is worth the small cost. The disk state itself will not tell you whether the data matters.
How often should I run this?
Monthly is enough for a small estate, and after any decommissioning work. The useful trigger is not the calendar, it is the week after somebody deletes a handful of VMs.