Azure waste

Public IP prefix with nothing allocated from it

A public IP prefix from which no address has been allocated.
The agent reports this
Why it happens

A public IP prefix reserves a contiguous range of addresses and bills for every address in it, allocated or not. At about $0.006 an address an hour, a /28 costs roughly $70 a month and a /24 about $1,121, whether one address is in use or none at all. A prefix with nothing allocated out of it is paying for a reservation nobody has drawn on, which commonly happens after a migration that was planned and never carried out. Note that a prefix is dearer per address than a standalone static IP, so the range is worth keeping only for the contiguity. Deleting one releases the addresses and they cannot be reclaimed, and public addresses are often written into other organisations allow-lists, so this check reports the cost and leaves the decision to a human.

Paste this into Resource Graph Explorer in the Azure portal.

resources
| where type =~ 'microsoft.network/publicipprefixes'
// Scope below matches CloudFinOpsPro policy DEFAULTS: exclusions.protectTag, environment.tagKeys,
// environment.allowedTagValues, and allowUntagged false so an untagged resource counts as production.
// If your policy.yaml differs, edit the three lines below or this will disagree with your own agent.
| where tolower(tostring(coalesce(tags['cfop:protect'], ''))) !in ('true', 'yes', '1')
| extend cfopEnv = tolower(trim(' ', tostring(coalesce(tags['Environment'], tags['environment'], tags['env'], ''))))
| where cfopEnv in ('dev', 'development', 'test', 'qa', 'sandbox', 'nonprod', 'non-prod', 'staging')
// A MEASURED zero. An absent array means the payload never reported allocation, which is not the
// same as nothing being allocated, so it is excluded rather than treated as unused.
| where isnotnull(properties.publicIPAddresses) and array_length(properties.publicIPAddresses) == 0
// The price follows the TIER, not the sku name: Global costs double Regional.
| extend prefixTier = tostring(coalesce(sku.tier, properties.sku.tier))
| project name, resourceGroup, location, ipPrefix = tostring(properties.ipPrefix),
          prefixLength = toint(properties.prefixLength),
          addressVersion = tostring(properties.publicIPAddressVersion),
          tier = prefixTier, environment = cfopEnv

A public IP prefix reserves a contiguous range and bills for every address in it, allocated or not: about $0.006 an address an hour for a regional prefix and $0.012 for a global one, so a /28 is roughly $70 a month and a /24 about $1,121 whether one address is in use or none. Two things make this easy to price wrongly. A separate product called 'IP Addresses' publishes a similarly-named meter at $0.005, so anchoring on the meter name alone understates a prefix by 20 per cent; and the rate follows the sku TIER (Regional or Global) rather than the sku NAME (Standard or StandardV2), so pricing on the name halves every global prefix. The address count is 2^(32 - prefixLength) for IPv4 only; the check reports an IPv6 prefix without a figure rather than computing 2^64 addresses. This query returns the prefix rather than a cost for the same reason the others do. It is a structural test, so what it returns is what the check reports.

Applies to microsoft.network/publicipprefixes
What the agent does about it

A public IP prefix from which no address has been allocated.

It will not fix this one

It reports this and takes no action. A check only earns the right to change something once the action behind it can re-read live state before it fires and knows the way back.