Azure Networking Is a Lie: What a VNet Actually Is
An Azure VNet is metadata and policy enforced at each NIC. No Layer 2, no router, no isolating subnet boundary. Your segmentation is the NSG, and your security is the layering on top of it. What Azure networking actually does, and how to design for it.
Azure Networking Is a Lie: What a VNet Actually Is
I read Azure network designs for a living. Most of them are drawn by someone who still thinks a VNet is a switch with a router bolted onto the side.
It isn’t.
An Azure VNet is metadata. Policy that tells the Azure fabric which network interface cards (NICs) are allowed to route to each other, and nothing more. No shared wire. No Layer 2 segment. No broadcast domain. Packets get encapsulated and delivered NIC to NIC by the host’s filtering platform, and every control you think you’re configuring (segmentation, routing, NAT) is enforced as policy at each individual NIC, not by some device sitting in the path between two machines.
That sounds academic right up until you design against it. Carry the switch-and-router model into Azure and you make decisions that are subtly wrong in ways nobody catches at design review. Subnets look like isolation boundaries. MAC addresses look meaningful. The default gateway looks like a hop.
None of it is true.
If you’ve ever drawn a VNet as a rectangle with a little router icon in the middle, this one is for you. (I have drawn that icon. More than once. It was wrong then too.) What follows swaps the diagram for the control plane that’s actually running underneath it.
A VNet is a policy scope
Create a VNet with an address space and you haven’t allocated a wire or a slice of switch backplane. You’ve declared a policy scope. The fabric then decides, per packet, whether a given source NIC may reach a given destination NIC.
That’s also why peering behaves the way it does. It doesn’t lay a pipe between two VNets. It merges two sets of NICs into one routable scope. No cable, no tunnel device, no gateway in the middle by default. Just an updated policy saying these NICs may now reach those NICs.
There’s a real control plane under all of it. Azure Resource Manager, down through the Network Controller, to a Host Agent on every Hyper-V host. Your az network vnet create bottoms out as a set of flow rules pushed to hosts.
That’s the whole trick.
No Layer 2, and why that’s deliberate
There is no broadcast in Azure. No multicast, no GRE, no IP-in-IP. Nobody forgot to add them. It’s a deliberate property of a network built for roughly sixteen million virtual networks (a 24-bit VXLAN identifier) instead of the 4,096 a traditional VLAN gives you.
ARP is the tell. On a real Layer 2 segment a host broadcasts an ARP request and whoever owns the IP answers with its MAC. In Azure there’s nobody to broadcast to. So Azure intercepts the query and answers it itself, returning one placeholder MAC for every known destination IP on the subnet: 12:34:56:78:9a:bc, the conceptual Azure Virtual Router. Same answer, every host, every VNet, every destination.
Which means MAC addresses are meaningless inside Azure. Don’t filter on them. Don’t expect MAC-based licensing or clustering to survive the move, and if you’re migrating an appliance that does either, find that out now rather than during the cutover window.
A specific set of technologies leans on Layer 2 to work. None of them do here:
| Technology | Why it depends on Layer 2 | Azure-native answer |
|---|---|---|
| VRRP / HSRP gateway redundancy | Relies on multicast / gratuitous ARP | Azure Load Balancer |
| Windows Server failover clustering | Multicast heartbeats | Azure Load Balancer health probes |
| SQL Server Always On listener | Multicast / broadcast discovery | Azure Load Balancer (ILB listener) |
| Network Load Balancing (NLB) multicast mode | Multicast | Azure Load Balancer |
| DHCP relay / custom DHCP | Broadcast | Host-provided DHCP (do not disable it) |
DHCP still works. Not the way you’d guess, though. With no broadcast, the request gets intercepted and answered by the host platform. The address doing that work is 168.63.129.16, a reserved virtual public IP that Azure uses for core platform services: DNS, DHCP, load balancer health probes, and VM agent communication.
Two things it is not. It is not your default gateway, whatever the guest’s routing table implies. And it is not the Instance Metadata Service, which lives at 169.254.169.254. Both are host-virtualised addresses owned by Microsoft, and by default that platform traffic is not subject to your NSGs at all. If you want to filter it you have to name it explicitly, with the AzurePlatformDNS, AzurePlatformIMDS and AzurePlatformLKM service tags. Never disable DHCP in the guest. Never hard-code IPs there either. If you need a static address, set it in ARM so the fabric and the guest agree on what it is.
How a packet gets between two VMs
There’s no central router performing a recursive lookup. Routing happens at the NIC, and the packet travels wormhole style: VXLAN-encapsulated at the source, surfacing at the destination, nothing in between.
When a VM sends, the VFP on the source host wraps the packet in a VXLAN envelope. Outer IP is the host’s physical (provider) address, transport is UDP port 4789, and a 24-bit VNI identifies the virtual network. That overhead is why the internal MTU is 1400, not the 1500 you’d assume. It’s the kind of detail that silently fragments or drops traffic for anything hard-coding 1500 or setting the “don’t fragment” bit. The encapsulated packet leaves the source NIC and appears at the destination NIC. No hops.
Which is why traceroute tells you nothing here, and why neither your subnet’s gateway address nor 168.63.129.16 will answer a ping. By design.
Put both VMs on the same physical host and there’s no physical network involved at all. The vSwitch performs a memory copy from one NIC to the other, at memory speed rather than network speed. The “network” between two co-located VMs is a memcpy.
The engine behind all of this, the VFP, is a match-action flow machine structured as Port to Layer to Group to Rule. First packet of a flow takes the slow path through full rule evaluation. The result gets cached as a Unified Flow, and every packet after that is handled in microseconds. On hosts with Accelerated Networking the policy offloads to SR-IOV and FPGA-based SmartNICs, with a failsafe path back to the synthetic NIC if the hardware path drops.
NSGs and routes are the same thing
Both are flow rules in the VFP, evaluated at each NIC. That single fact is why NSGs are stateful, and why there’s no central router to point at when a path breaks. Understand it and Azure networking stops being mysterious.
NSGs are stateful, unlike the stateless NACLs some architects drag over from other clouds. Allow an inbound flow and the return traffic is permitted automatically. You don’t write the reverse rule. The VFP tracks connections on a 5-tuple.
Mostly stateful, anyway. Microsoft’s own documentation says that because of current platform limitations, non-default security rules affecting inbound TCP flows are implemented in a stateless way. The fix is a virtual network property, FlowTimeoutInMinutes, which is null unless somebody set it. Set it to 4 and you get the stateful behaviour you assumed you already had. You can take it to 30 for long-running connections.
Go and check whether yours is set. I’d put money on null.
Here’s the other thing that catches people out. Rule changes only apply to new connections. Pull the rule that allowed an established SSH session and the session stays up: it’s the next connection attempt that gets refused. That is a genuinely uncomfortable few minutes mid-incident, when you believe you’ve just cut something off and you haven’t. Evaluation order is specific too: inbound runs Subnet-NSG then NIC-NSG, outbound the reverse.
Routing is decided at the NIC as well, from an effective route table the fabric composes for that interface. No central router, no recursive lookups. Selection follows a strict priority: User-Defined Routes beat BGP-learned routes, which beat system routes, and within a tier longest-prefix-match wins. The sharp edge is written into Microsoft’s routing documentation and almost nobody reads it: system routes for the virtual network, for VNet peerings, and for service endpoints are preferred over BGP routes even when the BGP route is more specific. Service endpoint routes can’t be overridden at all, route table or not. That blindsides teams running forced tunnelling back to on-premises.
So when a path misbehaves, don’t theorise. Read the Effective Routes on the NIC. Confirm the destination with Network Watcher’s Next Hop. Check NSG Flow Logs for what got allowed or denied. Test a specific 5-tuple with IP Flow Verify.
The fabric will tell you what it’s doing. Ask it.
The DMZ that isn’t there
I still find DMZs in Azure designs. Not from beginners, either. Experienced architects, people who have built real networks that carried real traffic, still hand me a design with a screened subnet, a router doing the isolating, and tiers separated by wires.
There are no wires.
It is a competent design for a datacentre. It just describes a machine that isn’t running. The router isolates nothing, because there is no router. The subnet boundary the whole pattern leans on enforces nothing by itself.
So here is the argument I keep making in review rooms, mostly to prove a point. You could drop every workload onto one big flat network, enforce the lot with per-NIC NSGs and ASGs, and the enforcement would be real in a way that screened subnet never was.
Every NIC enforces. There is no choke point to misconfigure. No UDR that forgot to send a flow at the firewall. No east-west path slipping past inspection because two machines happen to share a VNet. The policy lands in the one place a packet cannot route around: the interface it leaves from.
It is a thought experiment, not a design. I am not telling you to go flat.
Because one layer of enforcement, however well placed, is still one layer.
Security is like an onion. It has layers. Just like ogres.
What makes an estate secure is the layering: the NSG at the NIC, the firewall in the hub, the policy that stops the resource being created in the wrong shape at all, the identity boundary underneath the lot. Any one of them fails and the others still hold. That is the real case for hub-and-spoke, and it is a much better one than the wires.
The flat network is also an administrative nightmare. That is the second reason, and it is the one people reach for first.
What it changes is what you think the VNet is doing. It is a unit of administration and a place to hang a layer. It is not the thing stopping the traffic.
Design around policy, not wires
Stop designing around physical-network assumptions. Five things fall out of that directly.
Subnets aren’t isolation. A subnet is a policy container: route tables, NSGs, service endpoints, delegations. It is not a Layer 2 broadcast domain and it is not a DMZ. Two VMs in different subnets of the same VNet talk by default. Isolation comes from NSGs and ASGs, never from the subnet boundary.
This is usually where someone says: “Sure, but we run hub-and-spoke with a firewall in the hub, everything goes through it, so we’re segmented.”
You’re segmented for the traffic you actually forced through it. A firewall in the hub inspects what a UDR sends there and nothing else. Two VMs sitting in the same VNet, or in peered VNets with no route pushing them at the hub, talk NIC to NIC and your firewall never sees the flow. It isn’t in the path. There is no path. That is the entire point of this article, and it’s the assumption I find wrong most often.
NSGs plus ASGs are your segmentation. Default to deny-all, allow only what’s needed, and express intent with Application Security Groups so a rule reads “web tier may reach app tier” instead of some brittle IP range. That’s micro-segmentation enforced at every NIC at once, with no single choke point to misconfigure or forget.
UDRs are your cabling. No default-gateway hop, routing per NIC: a User-Defined Route is how you lay a path, say forcing east-west traffic through that hub firewall. Your route tables are the wiring diagram. Treat them like one.
Small VNets, peered to a hub. Peering is set-merging and isolation comes from policy, so the workable pattern is one workload per VNet, each peered to a hub, inspection enforced by a firewall in the hub. Not because that boundary enforces anything on its own, but because it gives you somewhere to hang another layer, and a unit small enough to reason about and hand to a team.
Secure and compliant by design, not by hand. When segmentation, routing, and inspection are all policy, the right posture is something you declare and enforce, in templates and policy-as-code. Not something you reconstruct by hand the week before an audit.
That last one is where the diagram and the running control plane drift apart. The diagram shows tidy subnets and a firewall. The control plane shows the effective routes, the NSG flow records, and the peering sets actually in force on each NIC. Over time those two pictures diverge, quietly, and nobody is assigned to notice.
Doing that comparison by hand, NIC by NIC, for the third time in a month, is what annoyed me into building Platform Architecture Authority. It reads what the fabric is really doing and holds it against the standard you said you’d keep, so the gap shows up as a finding instead of as an outage. If that gap is nagging at you, get in touch.
For more on the same problem from other angles: the Azure landing zone compliance review, the running-state question in Azure configuration drift, and the control mapping in NIS2 Article 21 Azure controls.
Five things to design around
- A VNet is metadata, not a network. It defines which NICs may route to each other. Peering merges two sets of NICs, it doesn’t lay a pipe.
- There’s no Layer 2. No broadcast, no multicast, no GRE, no IP-in-IP. ARP is intercepted and synthesised, and every host returns the same fake MAC,
12:34:56:78:9a:bc. MAC addresses are meaningless; never design around them. - Packets travel wormhole style. The customer packet is VXLAN-encapsulated (UDP 4789, 24-bit VNI, internal MTU 1400) and appears at the destination NIC with no hops. Same-host VMs are a pure memory copy. Traceroute is pointless.
- The Virtual Filtering Platform on the Hyper-V vSwitch enforces everything at the NIC: NSGs, routing, NAT. NSGs are stateful, so return traffic is auto-allowed.
- Design accordingly. Subnets are policy containers, not isolation. NSGs plus Application Security Groups are your segmentation. User-Defined Routes are your cabling. Use smaller VNets peered to a hub firewall.
The point worth keeping
Azure networking is not the network you drew. It’s a per-NIC policy plane wearing the costume of switches and routers. Stop designing around wires, broadcast domains, and MAC addresses. Start designing around NSGs, ASGs, UDRs, and small VNets peered to a hub, and the model turns consistent and, more to the point, enforceable.
The one real risk left is the gap between the diagram you believe and the control plane actually running. Close it deliberately.
It won’t close itself.
Frequently asked questions
Is an Azure VNet a real network? No. An Azure VNet is metadata that tells the Azure fabric which NICs are allowed to route to each other. There is no shared wire, no Layer 2 segment, and no broadcast domain. Every control (segmentation, routing, NAT) is enforced as policy at each individual NIC by the host’s Virtual Filtering Platform, not by a device sitting between machines.
Why does Azure have no broadcast or multicast?
Azure is a policy-routed overlay, not a switched Layer 2 segment, so the broadcast and multicast primitives do not exist. ARP is intercepted and synthesised, and every host returns the same fake MAC, 12:34:56:78:9a:bc. This breaks VRRP/HSRP, Windows failover clustering heartbeats, SQL Server Always On multicast listeners, and NLB multicast mode. Use Azure Load Balancer instead.
Are Azure NSGs stateful?
Yes, with one documented exception. Azure NSGs are stateful: when you allow an inbound flow, the return traffic is automatically permitted without a reverse rule, tracked on a 5-tuple. The exception is that non-default rules affecting inbound TCP flows are implemented statelessly because of a platform limitation; you restore stateful behaviour by setting FlowTimeoutInMinutes on the virtual network, which is null by default and accepts 4 to 30 minutes. Separately, rule changes apply only to new connections, so removing an allow rule does not terminate an established session.
Why doesn’t traceroute work in Azure?
Because packets travel “wormhole” style: VXLAN-encapsulated at the source NIC and delivered directly to the destination NIC with no intermediate hops. There is no central router and no chain of devices to report TTL expiry. Neither your subnet’s gateway address nor the platform services address 168.63.129.16 answers ping, by design. Use Effective Routes, Network Watcher Next Hop, and IP Flow Verify instead.