Detect and remediate orphaned Azure Availability Sets that create governance problems, audit overhead, and risk of accidental VM placement in your Azure environment.
Orphaned availability sets accumulate silently when VMs are decommissioned, migrated to VMSS Flex, or moved between resource groups. They create no alerts, generate no cost line items, and sit in your subscription until someone trips over them during an audit or accidentally deploys a new VM into an obsolete fault-domain configuration. Spotto continuously scans for these empty availability sets and flags them for review, so your team can clean them up before they become a governance problem.
Availability sets were the original mechanism for distributing VMs across fault domains and update domains in Azure. As workloads have evolved -- and Microsoft now recommends VMSS Flexible orchestration for new deployments -- many organizations have migrated VMs out of availability sets without deleting the empty containers left behind.
These orphaned resources are not billed directly, but they impose real operational costs: polluted inventories, confused engineers, compliance findings, and infrastructure-as-code drift that compounds over time.
Orphaned availability sets cause five distinct problems that compound as your Azure estate grows:
The detection and remediation process follows a simple set of if/then rules:
Before deleting any availability set, validate that it is genuinely orphaned by checking:
virtualMachines array in the availability set properties is emptyKey constraint: Some organizations retain empty availability sets as placeholders for disaster recovery scenarios, where VMs are deployed into pre-configured availability sets during failover. Always confirm with DR documentation and the operations team before deleting an availability set that might be part of a recovery plan.
The following command lists all availability sets with zero VMs across the current subscription:
az vm availability-set list \
--query "[?length(virtualMachines)==`0`].{Name:name, ResourceGroup:resourceGroup, Location:location}" \
--output tableFor cross-subscription detection at scale, use Azure Resource Graph:
resources
| where type == "microsoft.compute/availabilitysets"
| where array_length(properties.virtualMachines) == 0
| project name, resourceGroup, subscriptionId, location, tags
| order by subscriptionId, resourceGroupOnce an availability set has been validated as genuinely orphaned and safe to remove:
az vm availability-set delete \
--name <AvSetName> \
--resource-group <ResourceGroupName>Deploy the following PowerShell runbook in Azure Automation to scan for orphaned availability sets on a recurring schedule and send notifications:
# Azure Automation Runbook: Detect Orphaned Availability Sets
# Schedule: Weekly (recommended)
Connect-AzAccount -Identity
$subscriptions = Get-AzSubscription
$orphaned = @()
foreach ($sub in $subscriptions) {
Set-AzContext -SubscriptionId $sub.Id | Out-Null
$availSets = Get-AzAvailabilitySet
foreach ($avSet in $availSets) {
if ($avSet.VirtualMachinesReferences.Count -eq 0) {
$orphaned += [PSCustomObject]@{
Subscription = $sub.Name
ResourceGroup = $avSet.ResourceGroupName
Name = $avSet.Name
Location = $avSet.Location
FaultDomains = $avSet.PlatformFaultDomainCount
UpdateDomains = $avSet.PlatformUpdateDomainCount
}
}
}
}
if ($orphaned.Count -gt 0) {
Write-Output "Found $($orphaned.Count) orphaned availability sets:"
$orphaned | Format-Table -AutoSize
# Optional: Send email or webhook notification
} else {
Write-Output "No orphaned availability sets found."
}Before deleting an availability set, verify that no active IaC deployment references it. Use the what-if operation to detect references:
# Check if any ARM/Bicep deployment references the availability set
az deployment group what-if \
--resource-group <ResourceGroupName> \
--template-file main.bicep \
--query "changes[?contains(resourceId, 'availabilitySets')]" \
--output tableIf the availability set appears in the deployment output, update or remove the IaC reference before deleting the Azure resource.
Understanding the differences between availability sets and VMSS Flexible orchestration helps determine whether orphaned sets should be replaced or simply deleted:
| Feature | Availability Set | VMSS Flexible Orchestration |
|---|---|---|
| Max instances | 200 | 1,000 |
| Fault domains | Up to 3 | Up to 3 |
| Update domains | Up to 20 | Not applicable (rolling upgrades) |
| Availability zone support | No (single datacenter) | Yes (cross-zone spreading) |
| Auto-scaling | No | Yes (built-in autoscale) |
| SLA | 99.95% (2+ VMs) | 99.95% (FD spreading) / 99.99% (AZ spreading) |
| Microsoft recommendation | Legacy -- use for existing workloads only | Recommended for all new deployments |
Orphaned Availability Set Remediation Flowchart
================================================
[Start] Is the availability set empty (0 VMs)?
|
+-- NO --> Not orphaned. No action required.
|
+-- YES --> Is the availability set referenced in IaC (Terraform, ARM, Bicep)?
|
+-- YES --> Remove the IaC reference first.
| |
| +-- Run terraform state rm or update ARM/Bicep template.
| +-- Deploy updated IaC to confirm no drift.
| +-- Then proceed to deletion below.
|
+-- NO --> Is the availability set part of a DR/failover plan?
|
+-- YES --> Document the retention reason.
| |
| +-- Tag with: Purpose=DR, RetentionReview=<date>
| +-- Schedule review in 90 days.
| +-- DO NOT DELETE.
|
+-- NO --> Has the set been empty for > 90 days?
|
+-- YES --> Delete immediately.
| |
| +-- az vm availability-set delete
| +-- Update resource inventory.
| +-- Log deletion for audit trail.
|
+-- NO --> Tag for review.
|
+-- Tag with: Status=Orphaned, DetectedDate=<date>
+-- Notify subscription owner.
+-- Re-evaluate in 30 days.Use this simplified decision tree for each orphaned availability set:
When an orphaned availability set is found, determine whether the original workload should be migrated to VMSS Flex or simply cleaned up:
Failure mode: IaC references not cleaned up
If you delete an availability set that is still referenced in Terraform state, the next terraform plan will show a drift and attempt to recreate the resource. If the availability set is referenced in an ARM or Bicep deployment, the deployment will fail with a "resource not found" error. Always remove IaC references before deleting the Azure resource.
Failure mode: DR runbook dependency
Disaster recovery automation may reference availability sets by name or resource ID. If you delete a set that is part of a failover procedure, the DR runbook will fail when it attempts to deploy VMs into the deleted set during a real disaster. Confirm with the DR team before deleting any availability set in a production subscription.
Failure mode: Availability set recreation is not reversible
Once deleted, an availability set cannot be restored. A new availability set can be created with the same name, but existing VMs cannot be added to an availability set after creation -- VMs can only be assigned to an availability set at deployment time. This means deleting an availability set that still has a valid purpose requires redeploying all associated VMs.
An orphaned availability set is an Azure availability set resource that contains zero virtual machines. This typically happens when VMs are decommissioned, migrated to VMSS Flexible orchestration, or moved to availability zones, but the original availability set container is not deleted afterward. The empty set remains in the subscription indefinitely until manually removed.
Not without validation. While most empty availability sets are genuinely orphaned, some may be intentionally retained for disaster recovery scenarios, where VMs are deployed into pre-configured availability sets during failover. Others may be referenced in IaC configurations that would break if the resource were deleted. Always check for DR dependencies and IaC references before deletion.
Terraform will detect the drift on the next terraform plan and propose recreating the availability set. If you intended to remove it, you should run terraform state rm for the resource before or after deleting it from Azure, and also remove the resource block from your Terraform configuration files. If you delete the Azure resource without updating Terraform, the state file becomes inconsistent and subsequent deployments may fail or produce unexpected results.
Empty availability sets have no direct Azure billing. However, the indirect cost is significant: they pollute resource inventories, trigger compliance audit findings, create IaC drift, and consume engineering time during governance reviews. For an MSP managing 100 tenants, the cumulative operational cost of investigating orphaned resources can reach 25+ hours per month.
Microsoft recommends VMSS Flexible orchestration for all new deployments. For existing workloads, migration should be evaluated based on the workload requirements. VMSS Flex provides availability zone support, auto-scaling, and higher instance limits. However, migration requires redeploying VMs -- you cannot move an existing VM from an availability set to a VMSS Flex in-place. This makes it a planned project, not a quick cleanup task.
Deploy an Azure Policy that audits or denies the creation of new availability sets (since Microsoft recommends VMSS Flex for new workloads). Implement tagging policies that require every availability set to have an owner and purpose tag. Set up automated detection via Azure Automation or Spotto to scan for empty availability sets on a weekly basis and notify the responsible team.
MSPs typically use Azure Lighthouse to query across all managed tenants with a single Azure Resource Graph query. The KQL query provided above works across all delegated subscriptions when run from the managing tenant. Results can be exported to a central governance dashboard and assigned to per-tenant remediation queues. Spotto automates this entire workflow, scanning all connected tenants and flagging orphaned resources for review.
Availability sets are not formally deprecated, but Microsoft's guidance has shifted clearly toward VMSS Flexible orchestration for new deployments. Availability sets remain supported for existing workloads, and there is no announced retirement date. However, new features and improvements are being added to VMSS Flex rather than availability sets, making them a legacy construct in practice.
An availability set distributes VMs across fault domains and update domains within a single datacenter. It does not support availability zones, auto-scaling, or more than 200 instances. VMSS Flexible orchestration provides all of these capabilities plus cross-zone spreading for higher SLA (99.99% vs. 99.95%). VMSS Flex also supports mixing VM sizes within the same scale set and allows individual VM management similar to standalone VMs.
Cross-reference the Azure Resource Graph query results with the Azure Activity Log. Query the Activity Log for each empty availability set to find the last time a VM was added or removed. If no VM-related activity has occurred in 90+ days, the set has been orphaned for at least that long. You can also check the lastModifiedDate in the resource metadata, though this may reflect tag changes or other non-VM modifications.
Before
340 orphaned sets
Scattered across 80 tenants, no visibility
After
30% governance review time reduction
Clean inventories, automated detection
Actions taken: Ran cross-tenant Resource Graph query via Azure Lighthouse. Identified 340 empty availability sets. Validated 312 as safe to delete (no DR dependencies, no IaC references). Tagged 28 for review with subscription owners. Cleaned up 312 sets in one batch. Deployed Azure Policy to audit new availability set creation. Monthly governance review time dropped by 30% due to cleaner resource inventories.
Before
200 VMs, 15 availability sets
Legacy availability set architecture
After (6 months)
12 orphaned sets identified and removed
3 retained for phased migration
Actions taken: Migrated 200 VMs from 15 availability sets to VMSS Flex orchestration over 6 months. After migration, 12 availability sets were empty but still referenced in Terraform. Removed Terraform resource blocks and ran terraform state rm for each. Deleted the 12 empty sets from Azure. Retained 3 sets temporarily as the final migration wave was still in progress. Set a calendar reminder to delete the remaining 3 after migration completion.
Before
85 orphaned sets across 30 subscriptions
Accumulated over 12 months in dev/test environments
After
Zero orphaned sets
Automated weekly scan prevents recurrence
Actions taken: Dev/test environments had no DR dependencies and minimal IaC governance, making cleanup straightforward. All 85 orphaned availability sets were validated as safe to delete within a single sprint. Deployed an Azure Automation runbook to scan weekly and notify the platform team of any new orphaned sets. Implemented a tagging policy requiring all new availability sets to have an owner and expiry date.
Before
8 availability sets flagged by auditor
Healthcare org preparing for SOC 2 Type II
After
4 hours investigation saved
Clean finding resolution documented
Actions taken: During SOC 2 Type II audit preparation, the external auditor flagged 8 empty availability sets as unexplained infrastructure components. The security team spent 4 hours investigating each one: checking Activity Logs, cross-referencing IaC, and interviewing resource group owners. All 8 were confirmed as orphaned and deleted. Post-cleanup, the organization deployed Spotto to automatically detect and report orphaned resources before the next audit cycle, eliminating this class of finding entirely.
terraform state rm for Terraform-managed sets.When removing an orphaned availability set from Terraform-managed infrastructure:
# Before: Terraform configuration with availability set
resource "azurerm_availability_set" "legacy_avset" {
name = "avset-legacy-web"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
platform_fault_domain_count = 2
platform_update_domain_count = 5
}
# Step 1: Remove from Terraform state
terraform state rm azurerm_availability_set.legacy_avset
# Step 2: Remove the resource block from your .tf files
# (Delete the resource block shown above)
# Step 3: Verify clean plan
terraform plan
# Expected output: No changes. Your infrastructure matches the configuration.
# Step 4: Delete from Azure
az vm availability-set delete \
--name avset-legacy-web \
--resource-group rg-mainWhen removing an orphaned availability set from Bicep-managed infrastructure:
// Before: Bicep template with availability set
resource legacyAvSet 'Microsoft.Compute/availabilitySets@2023-09-01' = {
name: 'avset-legacy-web'
location: resourceGroup().location
properties: {
platformFaultDomainCount: 2
platformUpdateDomainCount: 5
}
sku: {
name: 'Aligned'
}
}
// After: Remove the resource block entirely
// (Delete the resource block shown above)
// Step 1: Deploy updated template to remove from deployment state
// az deployment group create --resource-group rg-main --template-file main.bicep
// Step 2: Delete from Azure (if not auto-removed by deployment in complete mode)
// az vm availability-set delete --name avset-legacy-web --resource-group rg-mainterraform state rm to remove the stale reference, then update the configuration to remove the resource block. If the resource was accidentally needed, you must recreate the availability set and redeploy VMs into it (VMs cannot be added to availability sets after creation).Availability set deletion is not reversible in the traditional sense. You can recreate an availability set with the same name and configuration, but you cannot add existing VMs to it -- VMs can only be assigned to an availability set at deployment time. This makes prevention (thorough validation before deletion) more important than rollback.
If a deletion was made in error and VMs need to be placed back into an availability set, the rollback path is: (1) recreate the availability set, (2) redeploy affected VMs into the new set during a maintenance window, (3) update all IaC references to point to the new resource ID.
Deploy the following Azure Policy to audit the creation of new availability sets, encouraging teams to use VMSS Flex instead:
{
"mode": "All",
"policyRule": {
"if": {
"field": "type",
"equals": "Microsoft.Compute/availabilitySets"
},
"then": {
"effect": "audit"
}
},
"parameters": {},
"displayName": "Audit creation of availability sets",
"description": "Audits the creation of new availability sets. Microsoft recommends VMSS Flexible orchestration for new deployments. Existing availability sets should be reviewed for migration to VMSS Flex."
}Tip: Use "audit" effect initially to identify teams still creating availability sets. Switch to "deny" after confirming all teams have adopted VMSS Flex for new deployments. This avoids breaking existing CI/CD pipelines that may still deploy availability sets as part of legacy templates.
| Category | Criteria | Action | Timeline |
|---|---|---|---|
| Immediate | Empty, no IaC references, no DR dependency, no owner response in 14 days | Delete | Same day |
| Code-first | Empty, referenced in IaC, no DR dependency | Remove IaC reference, then delete | Within 1 sprint |
| Documented | Empty, owner confirms no longer needed, awaiting change approval | Schedule deletion in change window | Within 30 days |
| DR-reserved | Empty, confirmed DR/failover dependency | Tag with retention reason, review in 90 days | No deletion -- periodic review |
Spotto continuously scans your Azure environment for orphaned availability sets and other governance gaps. Rather than running one-off queries, Spotto provides ongoing visibility across all connected tenants, automatically flagging orphaned resources and tracking remediation progress. This is especially valuable for MSPs managing dozens of tenants, where manual governance reviews cannot scale.
The challenges outlined in this article — orphaned resource detection, governance drift, manual audit overhead — are the same ones our customers work through across their Azure environments. The difference is they use Spotto to surface these issues automatically, rather than running the analysis by hand.
Spotto automatically detects orphaned availability sets and other governance gaps across all your Azure tenants -- so your team can focus on infrastructure that matters instead of investigating resources that should not exist.
Free TrialDisclaimer: This article is provided for informational purposes only and does not constitute professional advice. Azure pricing, features, and service behavior may have changed since publication. Always validate recommendations against your specific environment and consult Microsoft's official documentation before making changes to production infrastructure. Spotto is not liable for any actions taken based on the content of this article.