You can upgrade a Basic SKU public IP to Standard and keep the same address because the upgrade changes the SKU of the existing resource rather than allocating a new one. The address must be static and the IP must be disassociated during the upgrade, so for a VM you set the IP to static, detach it, change the SKU to Standard and reattach it, which Microsoft's AzureVMPublicIPUpgrade PowerShell module does for you in a minute or two per VM. Basic load balancers and VPN gateways have their own migration tools that also keep their front-end addresses.
Who this is for and what you will have at the end
This guide is for Azure administrators who still have Basic SKU public IPs on VMs, availability sets, load balancers or gateways after the 30 September 2025 retirement, and need to move them without telling partners, DNS providers or firewall owners about a new address.
At the end you will have:
- An inventory of Basic public IPs and the resources that use them.
- Network security groups in place so nothing that worked before is blocked afterwards.
- Every Basic public IP upgraded to Standard with the same address, using the right tool for each resource type.
- A verification step that confirms the SKU and the address.
Why the upgrade matters, and what changes
Basic public IPs keep working after retirement, but Microsoft treats them as unsupported and outside SLA. They also block other work: a NAT gateway can't be used in a subnet that contains Basic resources, which matters now that new subnets are private by default (see migrating VMs off default outbound access).
| Aspect | Basic SKU | Standard SKU |
|---|---|---|
| Allocation | Dynamic or static (IPv4); dynamic (IPv6) | Static only |
| Inbound security | Open by default | Secure by default; an NSG must allow traffic |
| Availability zones | Not supported | Non-zonal, zonal or zone-redundant |
| Routing preference | Not supported | Supported |
| Standard load balancer, NAT gateway, Azure Firewall | Not supported | Supported |
Two behaviours catch people out:
- NSGs become mandatory. Traffic that reached a VM through a Basic IP with no NSG is blocked after the upgrade until an NSG allows it.
- Zones. An upgraded IP doesn't show zones in its properties. Microsoft's documentation describes upgraded IPs as zone-redundant in most cases in one place and as having no zones in another, so if you need an IP you can attach to a zonal or zone-redundant resource, the guidance is to create a new Standard IP rather than upgrade. A Basic IP that already had a specific zone keeps it.
Prerequisites
- Network Contributor or a custom role with
Microsoft.Network/publicIPAddresses/read,/writeand/join/action, plus rights on the NICs and VMs involved. - The latest Az PowerShell module. Microsoft recommends PowerShell 7 or later for the load balancer upgrade module, although Windows PowerShell 5.1 is supported.
- Permission in your organization to install and run modules from the PowerShell Gallery.
- No resource locks on load balancers, their resource groups or related resources if you're upgrading a load balancer.
- A maintenance window. A VM is unreachable at its public IP for the minutes it takes to detach, upgrade and reattach.
Step 1: Inventory Basic public IPs
List every Basic public IP you can read, with its allocation method:
resources
| where type =~ 'microsoft.network/publicipaddresses'
| where sku.name =~ 'Basic'
| project
ResourceId = tolower(id),
ResourceName = name,
AllocationMethod = tostring(properties.publicIPAllocationMethod),
Region = location,
ResourceGroupName = resourceGroup,
SubscriptionId = subscriptionIdThen list the VMs that have Basic IPs attached. This is Microsoft's query; run it in Resource Graph Explorer or wrap it in Search-AzGraph -Query:
Resources
| where type =~ 'microsoft.compute/virtualmachines'
| project vmId = tolower(id), vmNics = properties.networkProfile.networkInterfaces
| join (
Resources |
where type =~ 'microsoft.network/networkinterfaces' |
project nicVMId = tolower(tostring(properties.virtualMachine.id)), allVMNicID = tolower(id), nicIPConfigs = properties.ipConfigurations)
on $left.vmId == $right.nicVMId
| join (
Resources
| where type =~ 'microsoft.network/publicipaddresses' and isnotnull(properties.ipConfiguration.id)
| where sku.name == 'Basic'
| project pipId = id, pipSku = sku.name, pipAssociatedNicId = tolower(tostring(split(properties.ipConfiguration.id, '/ipConfigurations/')[0])))
on $left.allVMNicID == $right.pipAssociatedNicId
| project vmId, pipId, pipSkuAnd the Basic load balancers:
Resources
| where type == 'microsoft.network/loadbalancers' and sku.name == 'Basic'Step 2: Pick the upgrade path for each resource
| Resource using the Basic IP | Path | Address kept? |
|---|---|---|
| Standalone VM | AzureVMPublicIPUpgrade module | Yes |
| VMs in an availability set | AzureAvSetBasicPublicIPUpgrade module | Yes |
| VM whose NIC is in a Basic load balancer pool | AzureBasicLoadBalancerUpgrade module (upgrades the LB and the IPs together) | Yes for VMs and LB front ends |
| Disassociated Basic IP | Portal, az network public-ip update or Set-AzPublicIpAddress | Yes |
| Uniform scale set with per-instance public IPs | Not a public IP resource; replace with Standard IP configurations | No, new addresses |
| VPN gateway (VpnGw1-5 or legacy SKU) | VPN Gateway Basic IP migration tool | Yes |
| Basic SKU VPN gateway | Remove the Basic public IP reference | Yes |
| ExpressRoute gateway | ExpressRoute gateway migration experience | Not covered here; see Microsoft's ExpressRoute gateway migration guidance |
| Application Gateway v1 | Migrate to v2 (v1 retired 28 April 2026) | Not by this process |
VPN gateways are covered separately in the VPN Gateway SKU migration guide, because the IP upgrade also changes the gateway SKU.
Step 3: Put NSGs in place first
The VM and availability set scripts refuse to upgrade a VM unless an NSG is associated with either the NIC or the subnet of every IP configuration that has a public IP. That requirement exists because Standard IPs drop unsolicited inbound traffic.
For each VM in scope:
- Record the inbound ports that clients use today, such as 443 for a web server or a management port reached only from a jump host.
- Associate an NSG with the NIC or subnet and add allow rules for exactly that traffic, scoped to known source ranges where you can.
- Test that the rules don't break anything while the IP is still Basic.
Step 4: Upgrade VM public IPs with the module
Install the module and connect to the right tenant and subscription:
Install-Module -Name AzureVMPublicIPUpgrade -Scope CurrentUser -Repository PSGallery -Force
Connect-AzAccount -Tenant <TenantId> -Subscription <SubscriptionId>Check that a VM is supported without changing anything:
Start-VMPublicIPUpgrade -VMName 'vm-web-01' -ResourceGroupName 'rg-web' -WhatIfThen upgrade it:
Start-VMPublicIPUpgrade -VMName 'vm-web-01' -ResourceGroupName 'rg-web'To upgrade every VM in a resource group and skip any VM without an NSG:
Get-AzVM -ResourceGroupName 'rg-web' | Start-VMPublicIPUpgrade -skipVMMissingNSGThe module sets each IP to static, confirms it's static, disassociates it, changes the SKU to Standard and reassociates it. Microsoft's testing shows 1 to 2 minutes for a VM with one NIC and one public IP, about another minute per extra NIC, and a few seconds per extra public IP. Activity is logged to PublicIPUpgrade.log in the working directory.
VMs in an availability set
Use the availability set module, which processes all VMs in the set:
Install-Module -Name AzureAvSetBasicPublicIPUpgrade -Scope CurrentUser -Repository PSGallery -Force
Start-AzAvSetPublicIPUpgrade -availabilitySetName 'avset-web' -resourceGroupName 'rg-web' -WhatIf
Start-AzAvSetPublicIPUpgrade -availabilitySetName 'avset-web' -resourceGroupName 'rg-web'Its log file is AvSetPublicIPUpgrade.log.
Step 5: Upgrade an IP manually
For a Basic IP that isn't attached to anything, or when you prefer to control each step, the upgrade needs a static, disassociated IP.
Set the allocation method to static. This step doesn't change the address of an IP that's already in use:
az network public-ip update --resource-group rg-web --name pip-web-01 --allocation-method StaticDissociate it from the NIC's IP configuration:
az network nic ip-config update \
--name ipconfig1 \
--resource-group rg-web \
--nic-name nic-web-01 \
--public-ip-address nullUpgrade the SKU, then confirm it:
az network public-ip update --resource-group rg-web --name pip-web-01 --sku Standard
az network public-ip show --resource-group rg-web --name pip-web-01 --query sku --output tsvReattach it to the same IP configuration:
az network nic ip-config update \
--name ipconfig1 \
--resource-group rg-web \
--nic-name nic-web-01 \
--public-ip-address pip-web-01The PowerShell equivalent of the static and SKU changes uses Set-AzPublicIpAddress:
$pubIP = Get-AzPublicIpAddress -Name 'pip-web-01' -ResourceGroupName 'rg-web'
$pubIP.PublicIpAllocationMethod = 'Static'
Set-AzPublicIpAddress -PublicIpAddress $pubIP
# after the IP has been dissociated from the NIC
$pubIP = Get-AzPublicIpAddress -Name 'pip-web-01' -ResourceGroupName 'rg-web'
$pubIP.Sku.Name = 'Standard'
Set-AzPublicIpAddress -PublicIpAddress $pubIPIn the portal, open the disassociated IP, select the upgrade banner on Overview, select I acknowledge and then Upgrade.
Step 6: Upgrade Basic load balancers and their IPs
A VM's public IP and its load balancer must use the same SKU, so VMs behind a Basic load balancer are upgraded with the load balancer module. It removes the Basic load balancer before it creates the Standard one, so plan downtime.
Install-Module -Name AzureBasicLoadBalancerUpgrade -Scope CurrentUser -Repository PSGallery -Force
Select-AzSubscription -Subscription <SubscriptionId>
Start-AzBasicLoadBalancerUpgrade -ResourceGroupName 'rg-web' -BasicLoadBalancerName 'lb-web' -validateScenarioOnly:$true
Start-AzBasicLoadBalancerUpgrade -ResourceGroupName 'rg-web' -BasicLoadBalancerName 'lb-web' -RecoveryBackupPath C:\BasicLBRecoveryWhat the module keeps and changes:
- Public front-end IPs are converted to static and upgraded to Standard, so the address is kept. Internal front ends try to reuse the same private IP.
- Health probes, load-balancing rules, inbound NAT rules and backend pools are migrated. NAT pools become NAT rules unless you pass
-skipUpgradeNATPoolsToNATRules. - Instance-level public IPs on VMs keep their addresses. Uniform scale set instance IPs change, because they can only be replaced.
- For public load balancers it creates an outbound rule (when there is one backend pool) and an NSG if none exists.
- Internal load balancers get no outbound path. Plan a NAT gateway, an NVA or a secondary outbound-only load balancer before you migrate.
Migrate load balancers that share backend members, such as an internal and external pair, together with -MultiLBConfig.
Verify the upgrade
- Confirm the SKU:
az network public-ip show ... --query sku --output tsvreturnsStandard, or the portal Overview shows Standard. - Confirm the address is unchanged: compare
az network public-ip show ... --query ipAddresswith your inventory. - Test inbound application traffic from outside Azure and outbound traffic from the VM.
- Re-run the Basic public IP query; the result set should shrink to the items you deliberately left for other tools.
- For load balancers, run the module with
-validateCompletedMigrationand the state file it created.
Troubleshooting
Warning that the IP can't be upgraded. The IP is dynamic. Change it to static first; Standard IPs don't support dynamic allocation.
The upgrade is blocked because the IP is associated. Only disassociated IPs can be upgraded manually. Detach it or use the VM module, which does the detach for you.
The script skips a VM. The VM has no NSG on its NIC or subnet, or its NIC is in a load balancer backend or NAT pool. Add an NSG, or use the load balancer module.
Inbound traffic stopped after the upgrade. The NSG doesn't allow the traffic. Add the allow rule; Standard IPs are closed by default.
The VM dropped out of an Application Gateway backend pool. The VM module removes the NIC from the pool. Add it back, or define the backend by private IP address before you run the script.
The VM upgrade failed part-way. Rerun with the recovery file: Start-VMPublicIPUpgrade -RecoverFromFile ./PublicIPUpgrade_Recovery_<timestamp>.csv -VMName 'vm-web-01' -ResourceGroupName 'rg-web'. The address isn't lost because it was made static first.
The load balancer migration failed. Fix the cause in Start-AzBasicLoadBalancerUpgrade.log, then rerun with -FailedMigrationRetryFilePathLB (and -FailedMigrationRetryFilePathVMSS for scale sets). The approach is fail forward; the upgraded IP can't go back to a Basic load balancer.
An internal load balancer upgrade fails on the private IP. The module tries to reassign the private front-end IP released when the Basic load balancer is deleted, and fails if that address isn't available. Resolve the conflict, then retry with the backup state file.
Checklist
- Basic public IPs, attached VMs and Basic load balancers inventoried.
- NSGs associated and tested on every VM that receives inbound traffic.
- Dynamic IPs switched to static before any detach.
- Standalone VMs upgraded with
AzureVMPublicIPUpgrade, availability sets withAzureAvSetBasicPublicIPUpgrade. - Basic load balancers upgraded with
AzureBasicLoadBalancerUpgrade, with outbound connectivity planned for internal ones. - VPN gateways moved with the VPN Gateway migration tool.
- SKU and address verified for every IP; Basic query returns nothing unexpected.
References
- Upgrade Basic public IP address to Standard SKU in Azure
- Upgrade a public IP address
- Upgrade public IP addresses attached to a VM from Basic to Standard
- Upgrade public IP addresses attached to VMs in an availability set
- Upgrade a Basic load balancer to Standard with PowerShell
- Dissociate a public IP address from an Azure VM
- Create, change, or delete an Azure public IP address
- az network public-ip reference
- Set-AzPublicIpAddress
- FinOps best practices for Networking