Symptoms
A VM built in a new virtual network has no outbound internet access, while an identical VM in an older VNet works fine. Typical signs:
curlorInvoke-WebRequestto public sites times out. DNS resolves fine.- Windows activation fails and Windows Update can't reach its endpoints.
apt,yumordnfhang on mirrors. Extensions that download packages fail to provision.- Calls to public Microsoft endpoints (Entra ID sign-in, Key Vault without Private Link, container registries) time out.
- Nothing in the NSG denies the traffic.
The usual cause is that the subnet is private: its defaultOutboundAccess property is false, so Azure no longer gives the VM an implicit outbound public IP. Since the API versions released after 31 March 2026, subnets in new VNets are private by default, and the portal had already made them private by default before that. Existing VNets were not changed. That is why the old VNet works and the new one doesn't.
Quick check: is the subnet private, and does it have explicit egress?
RG=rg-app-prod
VNET=vnet-app-prod
az network vnet subnet list -g $RG --vnet-name $VNET \
--query "[].{subnet:name, defaultOutbound:defaultOutboundAccess, natGateway:natGateway.id, routeTable:routeTable.id}" \
-o table
How to read it:
defaultOutbound | NAT gateway / route table | What the VM gets |
|---|---|---|
False | none | No internet egress at all. This is your outage. |
False | NAT gateway attached | Egress through the NAT gateway |
False | route table with 0.0.0.0/0 to a firewall/NVA | Egress through the firewall, if the firewall allows it |
empty (null) or True | none | Default outbound access (implicit, Microsoft-owned IP that can change) |
A VM can also have explicit egress at NIC level, through an instance public IP or a Standard load balancer with outbound rules. Check both:
VM=vm-app-01
NIC_ID=$(az vm show -g $RG -n $VM --query "networkProfile.networkInterfaces[0].id" -o tsv)
az network nic show --ids $NIC_ID \
--query "{publicIp:ipConfigurations[0].publicIPAddress.id, lbPools:ipConfigurations[0].loadBalancerBackendAddressPools[].id}" \
-o json
Prove it from inside the VM without logging in
Run Command works through the VM agent and doesn't need RDP or SSH:
# Linux
az vm run-command invoke -g $RG -n $VM --command-id RunShellScript \
--scripts "curl -s -m 5 -o /dev/null -w '%{http_code}\n' https://www.microsoft.com || echo FAILED"
# Windows
az vm run-command invoke -g $RG -n $VM --command-id RunPowerShellScript \
--scripts "Test-NetConnection www.microsoft.com -Port 443 | Select-Object TcpTestSucceeded"
Ping is not a valid test here. Default outbound access IPs never supported ICMP, so ping failing tells you nothing.
Check the effective routes
If the subnet has a route table, look at what the NIC actually uses (the VM must be running):
az network nic show-effective-route-table --ids $NIC_ID -o table
Watch for one trap. User-defined routes with next hop type Internet, often used to send service-tag traffic around the firewall, stop working in a private subnet unless the subnet also has explicit egress. The route still points at Internet, but there is no outbound IP behind it. Service endpoints (VirtualNetworkServiceEndpoint) are not affected.
Symptom → cause → fix
| Symptom | Likely cause | Fix |
|---|---|---|
| New VNet, no egress at all, NSG clean | Private subnet, no explicit outbound method | Attach a NAT gateway to the subnet |
| Only service-tag bypass routes fail | UDR next hop Internet in a private subnet | Add a NAT gateway, or send that traffic through the firewall |
| Egress works but the source IP keeps changing | Still on default outbound access | Move to a NAT gateway and make the subnet private |
| Third-party allow-list rejects you | Default outbound IP not owned by you | NAT gateway with a static public IP or prefix |
| Flexible VM Scale Set instances have no egress | Flexible orchestration never assigns default outbound IPs | Explicit egress on the subnet or load balancer |
| Load balancer backend pool by IP still uses default outbound | Known issue documented by Microsoft | Attach a NAT gateway to the backend subnet |
Fix: add a NAT gateway (the recommended default)
A NAT gateway is the recommended explicit method for most subnets. It applies to the whole subnet and gives you a static, known source IP.
LOC=westeurope
az network public-ip create -g $RG -n pip-natgw-app --sku Standard \
--allocation-method Static --location $LOC
az network nat gateway create -g $RG -n natgw-app --location $LOC \
--public-ip-addresses pip-natgw-app --idle-timeout 4
az network vnet subnet update -g $RG --vnet-name $VNET -n snet-app \
--nat-gateway natgw-app
Egress through the NAT gateway starts working for running VMs. No restart is needed. Run the Run Command test again to confirm. Check NAT gateway pricing and zone options for your region before you roll it out to every subnet.
Tip
If the subnet already sends
0.0.0.0/0to Azure Firewall or an NVA, you have explicit egress. Fix the firewall rules (application or network rules for the destinations) rather than adding a NAT gateway to the spoke.
Other explicit options
- Instance public IP: fine for a single jump box. It doesn't scale and it exposes the VM.
- Standard load balancer outbound rules: useful when the VMs are already behind a public Standard load balancer.
- Firewall or NVA via UDR: the usual choice in hub-and-spoke.
Should you flip the subnet back to non-private?
You can set defaultOutboundAccess back to true, but that is a stopgap:
az network vnet subnet update -g $RG --vnet-name $VNET -n snet-app --default-outbound true
Warning
Changing a subnet between private and non-private only takes effect on existing VMs after they are stopped (deallocated) and started. A deallocate means downtime and can change any dynamic public IP. Plan it as a maintenance window. Adding a NAT gateway needs no deallocate, so it is usually the faster fix.
Clearing the "default outbound access" warning on old VNets
The same property explains the portal banner and the Azure Advisor recommendation (Operational Excellence: "Add explicit outbound method to disable default outbound") on VMs in older VNets. To list NICs that still have a default outbound IP:
az graph query -q "
Resources
| where type =~ 'microsoft.network/networkinterfaces'
| where tobool(properties.defaultOutboundConnectivityEnabled) == true
| project nic=name, resourceGroup, subscriptionId, vm=tostring(properties.virtualMachine.id)
" --first 1000 -o table
To clear it safely, in this order:
- Add explicit egress to the subnet (NAT gateway or a UDR to a firewall) and test it.
- Make the subnet private:
az network vnet subnet update ... --default-outbound false. - Deallocate and start each VM in a maintenance window so the NIC drops its default outbound IP.
Caution
Never do step 2 before step 1 on a production subnet. Without explicit egress, VMs lose Windows activation, patching and every public endpoint dependency after their next deallocate, which may be weeks later and hard to trace back to this change.
Prevent it next time
- Put explicit egress in the network template. In Bicep, set
defaultOutboundAccess: falseand attachnatGatewayon the subnet in the same template. In Terraform azurerm, setdefault_outbound_access_enabled = falseand add a NAT gateway association. Pin the property explicitly rather than relying on the API version default, because older API versions and providers still leave it null. - Add egress to the VM build checklist. Before handover, run the Run Command HTTPS test and confirm the source IP matches the NAT gateway's public IP.
- Watch Advisor. Review the default outbound recommendations every month until the count reaches zero.
- Audit with Resource Graph. Schedule the subnet query across subscriptions to find private subnets with no NAT gateway and no route table. Those are the next outage.
- Document the egress IPs. Third parties that allow-list your traffic need the NAT gateway public IP or prefix. Default outbound IPs can change without notice.