FinOps best practices for Networking

This article outlines proven FinOps practices for networking services. They focus on cost optimization, efficiency improvements, and resource insights.


Azure Firewall

The following sections provide Azure Resource Graph (ARG) queries for Azure Firewall. These queries help you gain insights into your Azure firewall resources and ensure they're configured with the appropriate settings. By analyzing usage patterns and surfacing recommendations from Azure Advisor, you can optimize your Azure firewall configurations for cost efficiency.

Query: Azure firewall and firewall policies analysis

This ARG query analyzes Azure firewalls and their associated firewall policies within your Azure environment. It specifically targets firewalls with a premium SKU tier and verifies that the configurations in their associated firewall policies are utilizing the premium features.

Category

Optimization

Query

resources
| where type =~ 'Microsoft.Network/azureFirewalls' and properties.sku.tier=="Premium"
| project FWID=id, firewallName=name, SkuTier=tostring(properties.sku.tier), resourceGroup, location
| join kind=inner (
    resources
    | where type =~ 'microsoft.network/firewallpolicies'
    | mv-expand properties.firewalls
    | extend intrusionDetection = tostring(properties.intrusionDetection contains "Alert"
        or properties.intrusionDetection contains "Deny")
    | extend transportSecurity = tostring(properties.transportSecurity contains "keyVaultSecretId")
    | extend FWID = tostring(properties_firewalls.id)
    | where intrusionDetection == "False"
        and transportSecurity == "False"
    | project
        PolicyName = name,
        PolicySKU = tostring(properties.sku.tier),
        intrusionDetection,
        transportSecurity,
        FWID
) on FWID

Query: Azure Firewall and associated subnets analysis

This ARG query analyzes Azure firewalls and their associated subnets within your Azure environment. It provides insights into which subnets are associated with each Azure firewall instance. Optimize the use of Azure firewall by having a central instance of Azure firewall in the hub virtual network or Virtual WAN secure hub. Then share the same firewall across many spoke virtual networks that are connected to the same hub from the same region.

Category

Optimization

Query

resources
| where type =~ 'Microsoft.Network/azureFirewalls' and properties.sku.tier=="Premium"
| project
    FWID=id,
    firewallName=name,
    SkuTier=tostring(properties.sku.tier),
    resourceGroup,
    location
| join kind=inner (
    resources
    | where type =~ 'microsoft.network/firewallpolicies'
    | mv-expand properties.firewalls
    | extend intrusionDetection = tostring(properties.intrusionDetection contains "Alert"
        or properties.intrusionDetection contains "Deny")
    | extend transportSecurity = tostring(properties.transportSecurity contains "keyVaultSecretId")
    | extend FWID=tostring(properties_firewalls.id)
    | where intrusionDetection == "False"
        and transportSecurity == "False"
    | project
        PolicyName = name,
        PolicySKU = tostring(properties.sku.tier),
        intrusionDetection,
        transportSecurity,
        FWID
) on FWID

Application Gateway

Azure Application Gateway is a web traffic load balancer that enables you to manage traffic to your web applications. It provides application-level routing and load balancing services that let you build a scalable and highly available web front end in Azure.

Related resources:

Remove idle application gateways

Recommendation: Remove application gateways that don't have any backend pools to avoid unnecessary costs.

About idle application gateways

Application gateways without any backend pool targets aren't actively routing traffic and may represent unused resources. These idle gateways continue to incur costs even though they serve no function.

Note

FinOps hubs can automatically identify idle application gateways. Learn more.

Identify idle application gateways

Use the following ARG query to identify application gateways with empty backend pools.

resources
| where type =~ 'Microsoft.Network/applicationGateways'
| extend
    backendPoolsCount = array_length(properties.backendAddressPools),
    SKUName = tostring(properties.sku.name),
    SKUTier = tostring(properties.sku.tier),
    SKUCapacity = properties.sku.capacity,
    backendPools = properties.backendAddressPools,
    resourceGroup = strcat('/subscriptions/',subscriptionId,'/resourceGroups/',resourceGroup)
| project id, name, SKUName, SKUTier, SKUCapacity, resourceGroup, subscriptionId
| join (
    resources
    | where type =~ 'Microsoft.Network/applicationGateways'
    | mvexpand backendPools = properties.backendAddressPools
    | extend backendIPCount = array_length(backendPools.properties.backendIPConfigurations)
    | extend backendAddressesCount = array_length(backendPools.properties.backendAddresses)
    | extend backendPoolName = backendPools.properties.backendAddressPools.name
    | summarize
        backendIPCount = sum(backendIPCount),
        backendAddressesCount = sum(backendAddressesCount)
        by id
) on id
| project-away id1
| where (backendIPCount == 0 or isempty(backendIPCount))
    and (backendAddressesCount==0 or isempty(backendAddressesCount))
| order by id asc

Upgrade classic application gateways

Recommendation: Upgrade Application Gateway v1 SKU to v2 before the v1 retirement date to maintain support and access improved features.

About classic application gateways

Application Gateway v1 SKU (Standard and WAF) is being retired. The v2 SKU offers autoscaling, zone redundancy, and improved performance. Migrating to v2 ensures continued support and may reduce costs through autoscaling, which automatically adjusts the number of instances based on traffic.

Note

FinOps hubs can automatically identify classic application gateways using v1 SKU. Learn more.

Identify classic application gateways

Use the following ARG query to identify application gateways still using the v1 SKU.

resources
| where type =~ 'microsoft.network/applicationgateways'
| where properties.sku.tier in ('Standard', 'WAF')
| project
    ResourceId = tolower(id),
    ResourceName = name,
    SKUTier = tostring(properties.sku.tier),
    Region = location,
    ResourceGroupName = resourceGroup,
    SubscriptionId = subscriptionId

DDoS Protection

Azure DDoS Protection provides countermeasures against the most sophisticated DDoS threats. It provides enhanced DDoS mitigation capabilities for your application and resources deployed in your virtual networks.

Related resources:

Remove unassociated DDoS protection plans

Recommendation: Remove DDoS protection plans that aren't associated with any virtual network to avoid unnecessary costs.

About unassociated DDoS protection plans

DDoS protection plans incur a fixed monthly charge. Plans that aren't associated with any virtual network provide no protection but still generate costs. Removing unused plans eliminates unnecessary spending.

Note

FinOps hubs can automatically identify unassociated DDoS protection plans. Learn more.

Identify unassociated DDoS protection plans

Use the following ARG query to identify DDoS protection plans that aren't associated with any virtual network.

resources
| where type =~ 'microsoft.network/ddosprotectionplans'
| where isnull(properties.virtualNetworks) or array_length(properties.virtualNetworks) == 0
| project
    ResourceId = tolower(id),
    ResourceName = name,
    Region = location,
    ResourceGroupName = resourceGroup,
    SubscriptionId = subscriptionId

ExpressRoute

Azure ExpressRoute lets you extend your on-premises networks into the Microsoft cloud over a private connection. ExpressRoute circuits incur monthly charges based on the SKU and bandwidth provisioned.

Related resources:

Remove unprovisioned ExpressRoute circuits

Recommendation: Delete or provision ExpressRoute circuits that are in a not-provisioned state to avoid unnecessary charges.

About unprovisioned ExpressRoute circuits

ExpressRoute circuits that remain in a "NotProvisioned" state aren't actively carrying traffic but still incur monthly charges. These circuits may have been created but never completed with the service provider. Identifying and removing them eliminates unnecessary costs.

Note

FinOps hubs can automatically identify unprovisioned ExpressRoute circuits. Learn more.

Identify unprovisioned ExpressRoute circuits

Use the following ARG query to identify ExpressRoute circuits in a not-provisioned state.

resources
| where type =~ 'Microsoft.Network/expressRouteCircuits'
    and properties.serviceProviderProvisioningState == "NotProvisioned"
| extend
    ServiceLocation = tostring(properties.serviceProviderProperties.peeringLocation),
    ServiceProvider = tostring(properties.serviceProviderProperties.serviceProviderName),
    BandwidthInMbps = tostring(properties.serviceProviderProperties.bandwidthInMbps)
| project
    ERId = id,
    ERName = name,
    ERRG = resourceGroup,
    SKUName = tostring(sku.name),
    SKUTier = tostring(sku.tier),
    SKUFamily = tostring(sku.family),
    ERLocation = location,
    ServiceLocation,
    ServiceProvider,
    BandwidthInMbps

Load Balancer

Azure Load Balancer operates at layer 4 of the OSI model and distributes inbound traffic across healthy backend pool instances. It provides high availability by monitoring the health of backend instances and automatically rerouting traffic away from unhealthy ones.

Related resources:

Remove idle load balancers

Recommendation: Remove load balancers that don't have any backend pools to avoid unnecessary costs.

About idle load balancers

Load balancers without backend pool targets aren't actively distributing traffic and may represent unused resources. Standard SKU load balancers incur costs even when idle, so removing unused instances can reduce unnecessary spending.

Note

FinOps hubs can automatically identify idle load balancers. Learn more.

Identify idle load balancers

Use the following ARG query to identify Standard SKU load balancers with empty backend pools.

resources
| extend resourceGroup = strcat('/subscriptions/', subscriptionId, '/resourceGroups/', resourceGroup)
| extend SKUName = tostring(sku.name)
| extend SKUTier = tostring(sku.tier)
| extend location,backendAddressPools = properties.backendAddressPools
| where type =~ 'microsoft.network/loadbalancers'
    and array_length(backendAddressPools) == 0
    and sku.name!='Basic'
| order by id asc
| project
    id,
    name,
    SKUName,
    SKUTier,
    backendAddressPools,
    location,
    resourceGroup,
    subscriptionId

Upgrade Basic load balancers

Recommendation: Upgrade load balancers using the retired Basic SKU to Standard for better performance, security, and continued support.

About Basic load balancers

The Basic SKU for Azure Load Balancer was retired on September 30, 2025. Basic load balancers don't provide an SLA, lack availability zone support, and have limited diagnostic capabilities. Upgrading to Standard SKU provides improved reliability, performance, and security features.

Note

FinOps hubs can automatically identify Basic load balancers. Learn more.

Identify Basic load balancers

Use the following ARG query to identify load balancers using the Basic SKU.

resources
| where type =~ 'microsoft.network/loadbalancers'
| where sku.name =~ 'Basic'
| project
    ResourceId = tolower(id),
    ResourceName = name,
    SKUName = tostring(sku.name),
    Region = location,
    ResourceGroupName = resourceGroup,
    SubscriptionId = subscriptionId

NAT Gateway

Azure NAT Gateway provides outbound internet connectivity for virtual networks. NAT gateways simplify outbound-only internet connectivity by providing a managed, highly available SNAT service.

Related resources:

Remove orphaned NAT gateways

Recommendation: Remove NAT gateways that aren't associated with any subnet to avoid unnecessary charges.

About orphaned NAT gateways

NAT gateways incur hourly charges and data processing costs. Gateways that aren't associated with any subnet aren't providing outbound connectivity and represent wasted spend. These orphaned gateways may remain after a subnet or virtual network is reconfigured.

Note

FinOps hubs can automatically identify orphaned NAT gateways. Learn more.

Identify orphaned NAT gateways

Use the following ARG query to identify NAT gateways not associated with any subnet.

resources
| where type == "microsoft.network/natgateways"
| where isnull(properties.subnets) or array_length(properties.subnets) == 0
| project
    id,
    GWName = name,
    SKUName = tostring(sku.name),
    SKUTier = tostring(sku.tier),
    Location = location,
    resourceGroup = tostring(strcat('/subscriptions/', subscriptionId, '/resourceGroups/', resourceGroup)),
    subnets = properties.subnets,
    subscriptionId

Network Interface

Azure network interfaces (NICs) enable Azure VMs to communicate with internet, Azure, and on-premises resources. NICs don't incur direct charges, but orphaned NICs can indicate missed cleanup opportunities and complicate resource management.

Remove unattached network interfaces

Recommendation: Remove network interfaces that aren't attached to any virtual machine or private endpoint to keep your environment clean and reduce management overhead.

About unattached network interfaces

When a VM is deleted, its associated network interfaces may not be cleaned up automatically. These orphaned NICs can accumulate over time, cluttering your environment and potentially retaining associated resources like public IPs. While NICs don't incur direct charges, cleaning them up simplifies resource management and may reveal other orphaned resources.

Note

FinOps hubs can automatically identify unattached network interfaces. Learn more.

Identify unattached network interfaces

Use the following ARG query to identify network interfaces not attached to any VM or private endpoint.

resources
| where type =~ 'microsoft.network/networkinterfaces'
| where isnull(properties.virtualMachine) and isnull(properties.privateEndpoint)
| project
    ResourceId = tolower(id),
    ResourceName = name,
    PrivateIP = tostring(properties.ipConfigurations[0].properties.privateIPAddress),
    Region = location,
    ResourceGroupName = resourceGroup,
    SubscriptionId = subscriptionId

Network Security Group

Network security groups (NSGs) filter network traffic to and from Azure resources in a virtual network. NSGs contain security rules that allow or deny inbound and outbound network traffic.

Remove empty network security groups

Recommendation: Remove network security groups that aren't associated with any network interface or subnet to simplify your environment and reduce management overhead.

About empty network security groups

NSGs that aren't associated with any network interface or subnet aren't actively filtering traffic. These unused resources can accumulate during infrastructure changes, adding clutter and complicating security audits. Removing them simplifies network management and helps maintain a clean environment.

Note

FinOps hubs can automatically identify empty network security groups. Learn more.

Identify empty network security groups

Use the following ARG query to identify NSGs not associated with any network interface or subnet.

resources
| where type =~ 'microsoft.network/networksecuritygroups'
| where isnull(properties.networkInterfaces) and isnull(properties.subnets)
| project
    ResourceId = tolower(id),
    ResourceName = name,
    Region = location,
    ResourceGroupName = resourceGroup,
    SubscriptionId = subscriptionId

Private DNS

The following section provides an ARG query for Private DNS. It helps you gain insights into your Private DNS resources and ensure they're configured with the appropriate settings.

Query: Private DNS

This ARG query analyzes Private DNS zones within your Azure environment to identify any without Virtual Network Links.

Category

Optimization

Query

resources
| where type == "microsoft.network/privatednszones"
    and properties.numberOfVirtualNetworkLinks == 0
| project id, PrivateDNSName=name,
    NumberOfRecordSets = tostring(properties.numberOfRecordSets),
    resourceGroup = tostring(strcat('/subscriptions/', subscriptionId, '/resourceGroups/', resourceGroup)),
    vNets = tostring(properties.properties.numberOfVirtualNetworkLinks),
    subscriptionId

Public IP address

Azure public IP addresses enable Azure resources to communicate with the internet and other public-facing Azure services. Public IP addresses are assigned to resources such as virtual machines, load balancers, and application gateways. Static public IP addresses incur costs whether or not they're associated with a resource.

Related resources:

Upgrade Basic public IPs

Recommendation: Upgrade public IP addresses using the retired Basic SKU to Standard for better security and continued support.

About Basic public IPs

The Basic SKU for Azure public IP addresses was retired on September 30, 2025. Basic public IPs lack zone redundancy, don't support routing preference, and are open to inbound traffic by default. Upgrading to Standard SKU provides zone redundancy, secure-by-default behavior (closed to inbound traffic), and support for routing preferences.

Note

FinOps hubs can automatically identify Basic public IPs. Learn more.

Identify Basic public IPs

Use the following ARG query to identify public IP addresses using the Basic SKU.

resources
| where type =~ 'microsoft.network/publicipaddresses'
| where sku.name =~ 'Basic'
| project
    ResourceId = tolower(id),
    ResourceName = name,
    SKUName = tostring(sku.name),
    AllocationMethod = tostring(properties.publicIPAllocationMethod),
    Region = location,
    ResourceGroupName = resourceGroup,
    SubscriptionId = subscriptionId

Remove idle public IP addresses

Recommendation: Remove unattached static public IP addresses to avoid unnecessary networking costs.

About idle public IP addresses

Static public IP addresses incur costs regardless of whether they're associated with a resource. Unattached public IPs can accumulate over time as resources are deleted but their associated public IPs are left behind. Identifying and removing these orphaned resources can reduce unnecessary costs.

Note

FinOps hubs can automatically identify unattached public IP addresses. Learn more.

Identify idle public IP addresses

Use the following ARG query to identify unattached static public IP addresses, including those associated with unattached network interfaces.

resources
| where type =~ 'Microsoft.Network/publicIPAddresses'
    and isempty(properties.ipConfiguration)
    and isempty(properties.natGateway)
    and properties.publicIPAllocationMethod =~ 'Static'
| extend
    PublicIpId = id,
    IPName = name,
    AllocationMethod = tostring(properties.publicIPAllocationMethod),
    SKUName = sku.name,
    Location = location,
    resourceGroup = strcat('/subscriptions/', subscriptionId, '/resourceGroups/', resourceGroup)
| project PublicIpId, IPName, SKUName, resourceGroup, Location, AllocationMethod, subscriptionId
| union (
    Resources
    | where type =~ 'microsoft.network/networkinterfaces'
        and isempty(properties.virtualMachine)
        and isnull(properties.privateEndpoint)
        and isnotempty(properties.ipConfigurations)
    | extend IPconfig = properties.ipConfigurations
    | mv-expand IPconfig
    | extend PublicIpId= tostring(IPconfig.properties.publicIPAddress.id)
    | project PublicIpId
    | join (
        resource
        | where type =~ 'Microsoft.Network/publicIPAddresses'
        | extend
            PublicIpId = id,
            IPName = name,
            AllocationMethod = tostring(properties.publicIPAllocationMethod),
            SKUName = sku.name,
            resourceGroup,
            Location = location
    ) on PublicIpId
    | project
        PublicIpId,
        IPName,
        SKUName,
        resourceGroup,
        Location,
        AllocationMethod,
        subscriptionId
)

Query: Identify public IP addresses routing method

This ARG query analyzes public IP addresses and identifies the routing method, allocation method, and SKU. It also analyzes other details of public IP addresses that are associated with an IP configuration.

Category

Optimization

Query

resources
| where type =~ 'Microsoft.Network/publicIPAddresses'
    and isnotempty(properties.ipConfiguration)
| where tostring(properties.ipTags) == "[]"
| extend
    PublicIpId = id,
    RoutingMethod = id,
    IPName = name,
    AllocationMethod = tostring(properties.publicIPAllocationMethod),
    SKUName = sku.name,
    Location = location,
    resourceGroup = strcat('/subscriptions/', subscriptionId, '/resourceGroups/', resourceGroup)
| project
    PublicIpId,
    IPName,
    RoutingMethod,SKUName,
    resourceGroup,
    Location,
    AllocationMethod,
    subscriptionId

Query: Check public IP addresses' DDoS protection policy

If you need to protect fewer than 15 public IP resources, the IP protection tier is the more cost-effective option. However, if you have more than 15 public IP resources to protect, then the network protection tier becomes more cost-effective.

Category

Optimization

Query

resources
| where type == "microsoft.network/publicipaddresses"
| project ddosProtection = tostring(properties.ddosSettings), name
| where ddosProtection has "Enabled"
| count
| project TotalIpsProtected = Count
| extend CheckIpsProtected = iff(TotalIpsProtected >= 15, "Enable Network Protection tier", "Enable PIP DDoS Protection")

Virtual Network Gateway

Azure Virtual Network Gateways provide cross-premises connectivity between your Azure virtual networks and on-premises infrastructure. Gateways incur hourly charges based on their SKU.

Related resources:

Remove idle VNet gateways

Recommendation: Remove virtual network gateways that don't have any active connections to avoid unnecessary charges.

About idle VNet gateways

Virtual network gateways incur hourly costs based on their SKU tier, regardless of whether they're actively used. Gateways without any connections aren't providing cross-premises connectivity and represent wasted spend. These idle gateways may remain after a migration or when connectivity requirements change.

Note

FinOps hubs can automatically identify idle VNet gateways. Learn more.

Identify idle VNet gateways

Use the following ARG query to identify virtual network gateways without any active connections.

resources
| where type == "microsoft.network/virtualnetworkgateways"
| extend resourceGroup = strcat('/subscriptions/', subscriptionId, '/resourceGroups/', resourceGroup)
| project id, GWName=name, resourceGroup, location, subscriptionId
| join kind = leftouter(
    resources
    | where type == "microsoft.network/connections"
    | extend id = tostring(properties.virtualNetworkGateway1.id)
    | project id
) on id
| where isempty(id1)
| project
    id,
    GWName,
    resourceGroup,
    location,
    subscriptionId,
    status=id

Give feedback

Let us know how we're doing with a quick review. We use these reviews to improve and expand FinOps tools and resources.

If you're looking for something specific, vote for an existing or create a new idea. Share ideas with others to get more votes. We focus on ideas with the most votes.


Related resources:

Related solutions: