# Azure Cloud Architecture — Part 4: Networking Foundations: VNets, IP & DNS

> **Series:** Azure Cloud Architecture (4 of 16)
> **Part 1:** `01-fundamentals-and-governance.md` — Fundamentals & Governance
> **Part 2:** `02-identity-and-access.md` — Identity & Access
> **Part 3:** `03-compute-vms-and-scale-sets.md` — Compute: Virtual Machines & Scale Sets
> **Part 4:** This file — Networking Foundations: VNets, IP & DNS
> **Part 5:** `05-networking-hybrid-connectivity.md` — Networking: Hybrid Connectivity
> **Part 6:** `06-networking-application-delivery.md` — Networking: Application Delivery
> **Part 7:** `07-networking-private-access-and-security.md` — Networking: Private Access & Security
> **Part 8:** `08-storage-blob-files-and-disks.md` — Storage: Blob, Files & Disks
> **Part 9:** `09-databases-and-data-services.md` — Databases & Data Services
> **Part 10:** `10-containers-and-serverless.md` — Containers & Serverless
> **Part 11:** `11-application-architecture-and-messaging.md` — Application Architecture & Messaging
> **Part 12:** `12-security-and-compliance.md` — Security & Compliance
> **Part 13:** `13-monitoring-logging-and-observability.md` — Monitoring, Logging & Observability
> **Part 14:** `14-business-continuity-backup-dr-and-migration.md` — Business Continuity: Backup, DR & Migration
> **Part 15:** `15-cicd-and-iac.md` — CI/CD & Infrastructure as Code
> **Part 16:** `16-multi-region-cost-optimization-and-cheatsheet.md` — Multi-Region, Cost Optimization & Cheat Sheet
> **Questions:** `questions.md`

## Table of Contents

1. [Networking in Azure — Why This Gets Four Dedicated Parts](#networking-in-azure--why-this-gets-four-dedicated-parts)
2. [Anatomy of a Virtual Network](#anatomy-of-a-virtual-network)
3. [Subnetting Strategy — Sizing and Azure's Reserved Addresses](#subnetting-strategy--sizing-and-azures-reserved-addresses)
4. [The 2026 Default Change: Private Subnets by Default](#the-2026-default-change-private-subnets-by-default)
5. [NAT Gateway — Predictable Outbound Connectivity](#nat-gateway--predictable-outbound-connectivity)
6. [Public IP Addresses — SKUs and Allocation](#public-ip-addresses--skus-and-allocation)
7. [Public IP Prefixes and Bring-Your-Own-IP](#public-ip-prefixes-and-bring-your-own-ip)
8. [Subnet Delegation — PaaS Services Living Inside a VNet](#subnet-delegation--paas-services-living-inside-a-vnet)
9. [VNet Peering — Connecting Virtual Networks](#vnet-peering--connecting-virtual-networks)
10. [Gateway Transit and Peering Constraints](#gateway-transit-and-peering-constraints)
11. [Cross-Subscription and Cross-Tenant Peering](#cross-subscription-and-cross-tenant-peering)
12. [Hub-and-Spoke Topology](#hub-and-spoke-topology)
13. [Azure Virtual Network Manager — Topology at Scale](#azure-virtual-network-manager--topology-at-scale)
14. [IP Address Management (IPAM) Pools — Preventing Overlapping Address Spaces](#ip-address-management-ipam-pools--preventing-overlapping-address-spaces)
15. [Accelerated Networking and VNet Encryption](#accelerated-networking-and-vnet-encryption)
16. [IPv6 in Azure Virtual Networks](#ipv6-in-azure-virtual-networks)
17. [User-Defined Routes and Route Tables](#user-defined-routes-and-route-tables)
18. [Azure Route Server — Dynamic Routing With Network Appliances](#azure-route-server--dynamic-routing-with-network-appliances)
19. [Forced Tunneling](#forced-tunneling)
20. [Azure DNS — Public DNS Zones](#azure-dns--public-dns-zones)
21. [Azure Private DNS Zones](#azure-private-dns-zones)
22. [DNS Private Resolver — the Hybrid DNS Bridge](#dns-private-resolver--the-hybrid-dns-bridge)
23. [Name Resolution Inside a VNet](#name-resolution-inside-a-vnet)
24. [Network Watcher — First-Look Diagnostics](#network-watcher--first-look-diagnostics)
25. [A Full Worked Network Bootstrap for Meridian Freight](#a-full-worked-network-bootstrap-for-meridian-freight)
26. [Part 4 CLI Cheat Sheet](#part-4-cli-cheat-sheet)
27. [Common Mistakes and Interview Traps](#common-mistakes-and-interview-traps)
28. [Worked Practice Problems](#worked-practice-problems)
29. [Summary and What's Next](#summary-and-whats-next)

## Networking in Azure — Why This Gets Four Dedicated Parts

Azure's networking surface is genuinely broader than AWS's or GCP's equivalent single VPC chapter — the current AZ-700 (Azure Network Engineer) certification alone splits it into five exam domains covering core infrastructure, hybrid connectivity, application delivery, private access, and network security. This series follows that same split across Parts 4-7 rather than compressing it into one chapter, because each area has genuinely distinct concepts, tools, and failure modes worth their own depth.

Coming from the AWS or GCP series, most of this chapter's vocabulary maps directly: a VNet is the Azure equivalent of a VPC/VPC network, a subnet is a subnet, and peering is peering — the genuine differences (the March 2026 private-by-default change, Azure's five reserved IPs per subnet, and NAT Gateway's precedence rules) are called out explicitly as they come up, rather than assumed to work identically just because the vocabulary sounds the same.

```mermaid
graph LR
    P4["Part 4 (this chapter):<br/>VNets, subnets,<br/>IP addressing, DNS"] --> P5["Part 5:<br/>VPN, ExpressRoute,<br/>Virtual WAN"]
    P5 --> P6["Part 6:<br/>Load Balancer, App Gateway,<br/>Front Door, Traffic Manager"]
    P6 --> P7["Part 7:<br/>Private Link, NSGs,<br/>Azure Firewall, WAF"]
```

Meridian Freight's network design threads through all four parts: this chapter establishes the VNet, subnet, and DNS foundation every later part builds on — Part 5 connects it to the outside world, Part 6 puts application delivery in front of it, and Part 7 locks it down.

---

## Anatomy of a Virtual Network

A **Virtual Network (VNet)** is Azure's direct analog of an AWS VPC or GCP VPC network — an isolated network space within a subscription, carved into subnets.

```mermaid
graph TD
    VNet["VNet: vnet-meridian-prod<br/>Address space: 10.0.0.0/16"] --> Subnet1["snet-app<br/>10.0.1.0/24"]
    VNet --> Subnet2["snet-data<br/>10.0.2.0/24"]
    VNet --> Subnet3["snet-gateway<br/>10.0.255.0/27<br/>(reserved name: GatewaySubnet)"]
    VNet --> Subnet4["snet-appgw<br/>10.0.3.0/24"]
```

```bash
az network vnet create --name vnet-meridian-prod --resource-group rg-networking-prod \
  --address-prefix 10.0.0.0/16 --location eastus

az network vnet subnet create --vnet-name vnet-meridian-prod --resource-group rg-networking-prod \
  --name snet-app --address-prefix 10.0.1.0/24

# List every subnet in a VNet at a glance
az network vnet subnet list --vnet-name vnet-meridian-prod --resource-group rg-networking-prod \
  --query "[].{name:name, prefix:addressPrefix}" --output table
```

**Worth stating precisely, since it's a common point of confusion coming from AWS: a VNet's address space is set at creation but CAN be expanded later (adding an additional address range) without recreating the VNet — however, shrinking or removing an already-in-use range requires first removing every subnet and resource using it, which is rarely practical in a live environment.** Sizing the initial address space generously, even if only a fraction is used immediately, avoids a much more disruptive later expansion.

```bash
# Add a second, additional address range to an existing VNet —
# a NON-disruptive expansion, unlike shrinking one
az network vnet update --name vnet-meridian-prod --resource-group rg-networking-prod \
  --address-prefixes 10.0.0.0/16 10.1.0.0/16
```

---

## Subnetting Strategy — Sizing and Azure's Reserved Addresses

A genuinely easy mistake for anyone coming from on-premises networking or another cloud: Azure reserves **five** IP addresses in every subnet, not the more common one or two.

| Reserved address | Purpose |
|---|---|
| `x.x.x.0` | Network address |
| `x.x.x.1` | Reserved for the default gateway |
| `x.x.x.2`, `x.x.x.3` | Reserved for Azure DNS |
| `x.x.x.255` (last address) | Broadcast address |

**Why this matters concretely for subnet sizing: a `/24` subnet (256 addresses) usable capacity is 251, not 254 as many engineers instinctively assume from general networking knowledge — a genuinely easy planning mistake that surfaces as "why can't I fit this many VMs" only after a subnet fills up unexpectedly.** A `/27` (32 addresses) usable capacity is 27; a `/29` (8 addresses, the practical minimum for most delegated subnets) usable capacity is only 3.

A worked example, sizing Meridian Freight's `vnet-shipment-api` spoke:

| Subnet | CIDR | Total addresses | Usable (after 5 reserved) | Purpose |
|---|---|---|---|---|
| `snet-app` | `/24` | 256 | 251 | VMSS instances, headroom for scale-out |
| `snet-data` | `/26` | 64 | 59 | Database VMs, deliberately smaller — a fixed, small tier |
| `snet-appgw` | `/27` | 32 | 27 | Application Gateway (Part 6) — its own docs recommend a dedicated, appropriately sized subnet |
| `snet-private-endpoints` | `/26` | 64 | 59 | Private Link endpoints (Part 7) — one IP per endpoint, plan for growth |
| `GatewaySubnet` | `/27` | 32 | 27 | Reserved exact name for a VPN/ExpressRoute gateway (Part 5) — Microsoft's minimum recommended size |

**Why `GatewaySubnet` specifically needs its exact, case-sensitive reserved name, worth stating as a hard rule rather than a convention: Azure's gateway provisioning explicitly looks for a subnet named exactly `GatewaySubnet` within the VNet — naming it anything else (even `gateway-subnet` or `GatewaySubnets`) causes gateway creation to fail outright**, a real, easy-to-hit mistake for anyone naming subnets by their own team's usual convention instead of checking this specific, non-negotiable exception first.

```bash
# Check current subnet utilization before assuming there's room to grow
az network vnet subnet show --vnet-name vnet-meridian-prod --resource-group rg-networking-prod \
  --name snet-app --query "{addressPrefix: addressPrefix, ipConfigurations: length(ipConfigurations)}"
```

---

## The 2026 Default Change: Private Subnets by Default

A genuinely important, recent platform change worth flagging prominently rather than assuming stale training-data knowledge still applies: **as of March 31, 2026, newly created Azure VNets default to PRIVATE subnets — outbound internet access is no longer provided automatically**, reversing years of prior default behavior where a VM without an explicit public IP still had implicit outbound internet access through a platform-provided default mechanism.

```mermaid
graph TD
    Old["Before March 2026:<br/>implicit default outbound<br/>access, no config needed"] -.->|"CHANGED"| New["From March 2026:<br/>a VNet's subnets are<br/>PRIVATE by default —<br/>explicit outbound method<br/>REQUIRED"]
    New --> Options["NAT Gateway (recommended),<br/>Azure Firewall, a Load Balancer's<br/>outbound rules, or an<br/>instance-level public IP"]
```

```bash
# Explicitly enable outbound access via NAT Gateway — the current
# recommended default for production workloads
az network nat gateway create --name nat-meridian-prod --resource-group rg-networking-prod \
  --public-ip-addresses pip-nat-meridian --location eastus
az network vnet subnet update --vnet-name vnet-meridian-prod --resource-group rg-networking-prod \
  --name snet-app --nat-gateway nat-meridian-prod
```

**Why this is worth treating as a mandatory design step for every new VNet going forward, not an edge case: a VM deployed into a fresh subnet with no explicit outbound method configured will fail to reach the internet at all — package installs, external API calls, and OS update checks all silently fail — and the failure mode looks identical to a misconfigured NSG or firewall rule unless the engineer specifically knows this default changed.** Subnets hosting delegated/managed PaaS services (subnet delegation, covered later in this chapter) are the one exception — the managing service handles its own outbound connectivity regardless of this default.

---

## NAT Gateway — Predictable Outbound Connectivity

**Azure NAT Gateway** is Microsoft's current recommended mechanism for outbound internet access from a private subnet — stable, predictable outbound IP addressing at genuinely high scale, with essentially no configuration beyond attaching it to a subnet.

```bash
az network public-ip create --name pip-nat-meridian --resource-group rg-networking-prod --sku Standard
az network nat gateway create --name nat-meridian-prod --resource-group rg-networking-prod \
  --public-ip-addresses pip-nat-meridian
az network vnet subnet update --vnet-name vnet-meridian-prod --resource-group rg-networking-prod \
  --name snet-app --nat-gateway nat-meridian-prod
```

**A genuinely important precedence rule worth memorizing, a real interview trap: NAT Gateway takes precedence over every other outbound connectivity method on a subnet it's attached to — including a load balancer's outbound rules, instance-level public IPs, and even Azure Firewall.** Attaching a NAT Gateway to a subnet that also has instance-level public IPs configured doesn't combine the two — outbound traffic routes through the NAT Gateway exclusively, which can silently change a workload's observed outbound IP address if this precedence isn't accounted for during design.

| Outbound method | Predictable IP? | Scale | Best fit |
|---|---|---|---|
| NAT Gateway | Yes — one or a few static IPs for the whole subnet | High (up to 50 static IPs, tens of thousands of connections) | Production default recommendation |
| Load Balancer outbound rules | Yes, but shared across backend pool instances | Moderate | Already have a load balancer for inbound traffic |
| Instance-level public IP | No — one IP per instance | Low, doesn't scale cleanly | Rarely the right choice for production |
| Azure Firewall | Yes | High, but adds firewall-level cost and complexity | Already using Azure Firewall for other reasons (Part 7) |

---

## Public IP Addresses — SKUs and Allocation

```bash
az network public-ip create --name pip-appgw-meridian --resource-group rg-networking-prod \
  --sku Standard --allocation-method Static --zone 1 2 3
```

| SKU | Allocation | Zone redundancy | NSG required on the resource? |
|---|---|---|---|
| Basic (legacy, being retired) | Static or Dynamic | No | No |
| Standard | Static only | Yes — can be zone-redundant | Yes — Standard SKU resources are secure-by-default and require an explicit NSG allowing traffic |

```bash
# Confirm a public IP's SKU and allocation method before attaching
# it to a resource that assumes Standard-SKU behavior
az network public-ip show --name pip-appgw-meridian --resource-group rg-networking-prod \
  --query "{sku: sku.name, allocation: publicIPAllocationMethod, zones: zones}"
```

**Worth stating as current, actionable guidance rather than a historical footnote: Basic SKU public IPs are being retired, and Standard SKU is the only sensible choice for any new deployment** — Standard's secure-by-default posture (nothing reaches the resource without an explicit NSG rule) is also a meaningfully better security default than Basic's more permissive behavior.

```bash
# Find any remaining Basic SKU public IPs across a subscription —
# a worthwhile audit before the retirement deadline affects anything live
az network public-ip list --query "[?sku.name=='Basic'].{name:name, resourceGroup:resourceGroup}" --output table
```

---

## Public IP Prefixes and Bring-Your-Own-IP

A **Public IP Prefix** reserves a contiguous, predictable range of public IPs — useful when an external partner (say, a carrier's IT team allow-listing Meridian Freight's outbound IPs) needs a stable, documentable range rather than individual, potentially-changing addresses.

```bash
az network public-ip prefix create --name prefix-meridian-outbound \
  --resource-group rg-networking-prod --length 28
```

For organizations with existing, owned public IP ranges (common for larger enterprises with a pre-existing internet presence), **Custom IP Prefix (bring your own IP)** lets that existing range be brought into Azure directly, preserving IP reputation and existing allow-list relationships rather than starting over with Azure-assigned addresses.

```bash
az network public-ip prefix create --name custom-prefix-meridian \
  --resource-group rg-networking-prod --custom-ip-prefix-parent-id "<owned-range-registration-id>"
```

**Why preserving IP reputation is worth real weight in this decision, not just a technical curiosity: an organization's existing outbound IP range may already be trusted (allow-listed by partner systems, whitelisted against spam/reputation blocklists) after years of legitimate use — starting over with a freshly assigned Azure IP range means re-establishing that trust from zero**, which can mean a genuinely disruptive period of emails landing in spam folders or partner API calls being rate-limited or blocked until the new range earns the same trust the old one had.

---

## Subnet Delegation — PaaS Services Living Inside a VNet

Some Azure PaaS services (App Service with VNet integration, Azure Container Instances, certain database services) need to inject themselves directly into a subnet rather than connecting to it from outside — **subnet delegation** hands a subnet over to a specific service for this purpose.

```bash
az network vnet subnet create --vnet-name vnet-meridian-prod --resource-group rg-networking-prod \
  --name snet-appservice-integration --address-prefix 10.0.4.0/24 \
  --delegations Microsoft.Web/serverFarms
```

**Why a delegated subnet should be used for NOTHING else, worth stating as a hard rule: delegating a subnet to a specific service reserves its address space and behavior for that service exclusively — attempting to also place ordinary VMs in a delegated subnet will fail, and this chapter's earlier "private by default" discussion doesn't apply the same way here, since the delegated service manages its own outbound connectivity regardless of the subnet's own NAT configuration.**

| Delegated to | Common use case |
|---|---|
| `Microsoft.Web/serverFarms` | App Service VNet integration (Part 10) |
| `Microsoft.ContainerInstance/containerGroups` | Azure Container Instances directly inside a VNet |
| `Microsoft.DBforPostgreSQL/flexibleServers` | A flexible-server PostgreSQL instance with private VNet integration (Part 9) |
| `Microsoft.Netapp/volumes` | Azure NetApp Files volumes |

```bash
# List which services support delegation in a given region —
# not every service/region combination supports it
az network vnet list-endpoint-services --location eastus --output table
```

---

## VNet Peering — Connecting Virtual Networks

**VNet peering** connects two VNets so resources in each can communicate using private IP addresses, as if they were in one network — the mechanism behind the hub-and-spoke topology covered later in this chapter.

```bash
az network vnet peering create --name peer-spoke-to-hub \
  --vnet-name vnet-meridian-spoke --resource-group rg-driver-portal-prod \
  --remote-vnet vnet-meridian-hub --allow-vnet-access true

# Peering must be created in BOTH directions
az network vnet peering create --name peer-hub-to-spoke \
  --vnet-name vnet-meridian-hub --resource-group rg-networking-prod \
  --remote-vnet vnet-meridian-spoke --allow-vnet-access true
```

| Peering type | Latency/bandwidth | Cost | Constraint |
|---|---|---|---|
| Regional (same region) | Lowest — traffic stays on Microsoft's regional backbone | Lower | Both VNets in the same region |
| Global (cross-region) | Higher, still stays entirely on Microsoft's private backbone (never traverses the public internet) | Higher, billed per GB in both directions | No same-region requirement |

**Worth stating explicitly since it's a genuinely common point of confusion: peering is NOT transitive.** If VNet A peers with VNet B, and VNet B peers with VNet C, A cannot reach C through B automatically — each pair needing connectivity needs its own explicit peering relationship, or a hub-and-spoke design (next section) where every spoke peers directly with a shared hub.

```bash
# List every peering relationship for a VNet, and confirm its
# provisioning state is fully "Connected" on both sides — a peering
# stuck in "Initiated" on one side but never completed on the other
# behaves as if it doesn't exist at all
az network vnet peering list --vnet-name vnet-meridian-hub --resource-group rg-networking-prod \
  --query "[].{name:name, state:peeringState}" --output table
```

---

## Gateway Transit and Peering Constraints

**Gateway transit** lets a spoke VNet use a VPN or ExpressRoute gateway (Part 5) that lives in a *different*, peered VNet — typically the hub — rather than requiring its own gateway.

```bash
az network vnet peering update --name peer-spoke-to-hub \
  --vnet-name vnet-meridian-spoke --resource-group rg-driver-portal-prod \
  --set allowGatewayTransit=false useRemoteGateways=true
```

**Why this matters concretely for a hub-and-spoke design's cost and complexity, worth stating explicitly: without gateway transit, every spoke needing on-premises connectivity (Part 5) would need its OWN VPN/ExpressRoute gateway — a substantial, unnecessary duplication of cost and management overhead.** Gateway transit lets the hub own exactly one gateway, shared by every spoke that peers into it with `useRemoteGateways` enabled.

A real, worth-knowing constraint: a VNet cannot use both its own local gateway AND a remote gateway via transit at the same time — the two are mutually exclusive per VNet, so a spoke migrating from "its own gateway" to "shared via transit" needs to remove its local gateway first.

---

## Cross-Subscription and Cross-Tenant Peering

Nothing about VNet peering requires both VNets to live in the same subscription — a genuinely important capability for the multi-subscription landing zone Part 1 established, where the hub lives in a dedicated Connectivity subscription and every spoke lives in its own workload subscription.

```bash
# Peering across subscriptions needs the FULL resource ID of the
# remote VNet, not just its name
az network vnet peering create --name peer-spoke-to-hub \
  --vnet-name vnet-shipment-api --resource-group rg-shipment-api-prod \
  --remote-vnet "/subscriptions/<hub-subscription-id>/resourceGroups/rg-networking-prod/providers/Microsoft.Network/virtualNetworks/vnet-meridian-hub" \
  --allow-vnet-access true
```

**Cross-TENANT peering — a genuinely rarer scenario, worth knowing exists rather than assuming it's impossible: two VNets in entirely separate Microsoft Entra tenants (a common post-acquisition scenario, or a joint venture between two independently-run companies) CAN be peered, but it requires the initiating side to have the target tenant's subscription explicitly authorized as a peering partner first** — a deliberate, auditable trust step, not something that happens by simply having the right RBAC permissions alone. This is a genuinely different mechanism from the guest-user cross-tenant collaboration Part 2 covered — that governs identity and application access; this governs network-layer connectivity, and an organization can need one without the other.

---

## Hub-and-Spoke Topology

Bringing this chapter's concepts together into the concrete topology Part 1's landing zone diagram previewed.

```mermaid
graph TD
    Hub["Hub VNet<br/>(Connectivity subscription) —<br/>ExpressRoute/VPN gateway,<br/>Azure Firewall (Part 7),<br/>shared DNS resolver"]
    Hub <-->|"Peering + gateway transit"| Spoke1["Spoke: vnet-shipment-api<br/>(workload subscription)"]
    Hub <-->|"Peering + gateway transit"| Spoke2["Spoke: vnet-driver-portal"]
    Hub <-->|"Peering + gateway transit"| Spoke3["Spoke: vnet-docs-processor"]
    OnPrem["On-premises network<br/>(Part 5)"] --> Hub
```

**Why centralizing shared services (the gateway, the firewall, DNS resolution) in the hub rather than duplicating them per spoke matters concretely, echoing the exact reasoning Part 1 gave for a dedicated Connectivity subscription: every spoke gets consistent security policy and connectivity without each workload team needing to understand or manage networking infrastructure themselves** — a platform team owns the hub; application teams (like the one running `shipment-api`) own only their spoke's application-specific resources.

| Layer | Owned by | Lives in |
|---|---|---|
| Hub VNet, gateway, firewall, DNS resolver | Platform/networking team | Connectivity subscription (Part 1) |
| Spoke VNet, application subnets, NSGs | Application team | The workload's own subscription |
| Peering relationship connecting the two | Established once by the platform team, or automated via AVNM | Spans both subscriptions |

---

## Azure Virtual Network Manager — Topology at Scale

Manually creating and maintaining peering relationships for dozens of spokes doesn't scale — **Azure Virtual Network Manager (AVNM)** manages topology and connectivity declaratively across many VNets at once, including dynamic membership as new spokes are created.

```bash
az network manager create --name avnm-meridian --resource-group rg-networking-prod \
  --network-manager-scopes subscriptions="['<sub-id>']"

az network manager connect-config create --network-manager avnm-meridian \
  --resource-group rg-networking-prod --name hub-spoke-topology \
  --applies-to-groups '[{"networkGroupId": "<spoke-group-id>", "isGlobal": false, "useHubGateway": true}]'
```

**Why this scales meaningfully better than manual peering for an organization with many spokes, worth stating explicitly: connectivity and routing intent are defined ONCE at the network-group level, and AVNM automatically applies it to every VNet that joins the group** — a new spoke VNet added to the group automatically inherits hub connectivity without a platform engineer manually creating a new peering pair for it. For deployments beyond roughly 10 spokes, Microsoft's own current guidance recommends separating spokes into workload-specific subscriptions (Part 1's subscription design models) with a dedicated connectivity subscription hosting the hub — the two recommendations reinforce each other directly.

---

## IP Address Management (IPAM) Pools — Preventing Overlapping Address Spaces

A genuinely common, painful mistake at real organizational scale: two different teams, working independently, each create a VNet using the same or overlapping address range (`10.0.0.0/16` is an obvious, frequently-reused default). Two VNets with overlapping address spaces **cannot be peered at all** — the overlap has to be resolved by recreating one of them, a genuinely disruptive fix if either is already carrying production traffic. Azure Virtual Network Manager's **IPAM pools** solve this at the source, centrally.

```mermaid
graph TD
    Root["Root IPAM Pool<br/>10.0.0.0/8<br/>(the ENTIRE address space<br/>Meridian Freight owns in Azure)"] --> Platform["Child pool: Platform<br/>10.0.0.0/12"]
    Root --> Prod["Child pool: Production landing zones<br/>10.16.0.0/12"]
    Root --> NonProd["Child pool: Non-production landing zones<br/>10.32.0.0/12"]
    Prod --> VNet1["vnet-shipment-api requests<br/>a /16 — IPAM auto-assigns<br/>10.16.0.0/16, guaranteed<br/>non-overlapping"]
```

```bash
# Create a root pool representing the organization's entire address space
az network manager ipam-pool create --network-manager avnm-meridian \
  --resource-group rg-networking-prod --name pool-root --address-prefixes 10.0.0.0/8

# Create a child pool for production landing zones, up to 7 levels deep
az network manager ipam-pool create --network-manager avnm-meridian \
  --resource-group rg-networking-prod --name pool-production \
  --parent-pool-name pool-root --address-prefixes 10.16.0.0/12

# A new VNet requests an allocation from the pool rather than a
# team picking an address range by hand
az network vnet create --name vnet-new-workload --resource-group rg-new-workload-prod \
  --ipam-pool-id "<pool-production-resource-id>" --number-of-ip-addresses 65536
```

**Why centralized IPAM matters concretely at scale, worth stating plainly: it makes an overlapping-address-space mistake structurally impossible rather than relying on every team remembering to check a shared spreadsheet before creating a VNet** — the pool itself tracks what's already allocated and refuses to hand out a range that overlaps an existing allocation, the same shift from "process discipline" to "structural guarantee" this series has recommended repeatedly for tagging (Part 1) and other easy-to-skip conventions. A 2026 update worth knowing about specifically: **cross-region IPAM pool association** is now generally available, letting one pool apply consistent CIDR allocation policy across multiple regions rather than requiring a separate pool per region.

---

## Accelerated Networking and VNet Encryption

Two lower-level performance and security features worth knowing precisely, since both are easy to leave disabled without realizing a real capability was left on the table.

**Accelerated Networking** bypasses the host's software-based network switch entirely for a VM's traffic, using SR-IOV to give the VM more direct access to the underlying network hardware — substantially lower latency and higher throughput, at no additional cost.

```bash
az vm create --name vm-driver-portal-01 --resource-group rg-driver-portal-prod \
  --image Ubuntu2404 --size Standard_D2s_v5 --accelerated-networking true
```

**Why this is worth treating as a near-default rather than an optional tuning knob, worth stating plainly: Accelerated Networking is FREE and supported on the large majority of current-generation VM sizes, so leaving it disabled on a size that supports it is leaving real, no-cost performance on the table** — the main reason it's ever off is an older VM size that predates support, or a VM created before Accelerated Networking support existed for its size and never redeployed since.

**Virtual Network encryption** is a separate, newer capability: hardware-accelerated encryption of traffic between VMs *within the same VNet*, transparent to the application layer — no code changes, no certificate management.

```bash
az network vnet encryption update --name vnet-meridian-prod --resource-group rg-networking-prod \
  --enforcement AllowUnencrypted --enable true
```

**Worth distinguishing precisely from TLS at the application layer: VNet encryption protects against a very specific threat — traffic eavesdropping at the physical network layer, between two VMs that may not otherwise be running any application-level encryption at all** — it's a defense-in-depth layer underneath application-level TLS, not a substitute for it. A design still needs application-layer TLS for anything crossing outside the VNet (Part 7's Private Link and Part 12's Key Vault-managed certificates cover that), but VNet encryption closes the specific gap of unencrypted intra-VNet traffic between two VMs that happen to not be using TLS between themselves.

---

## IPv6 in Azure Virtual Networks

Azure VNets support **dual-stack** configuration — a VNet must always have an IPv4 address space, and can additionally have an IPv6 range alongside it; VMs in a dual-stack subnet receive both an IPv4 and an IPv6 address.

```bash
az network vnet update --name vnet-meridian-prod --resource-group rg-networking-prod \
  --address-prefixes 10.0.0.0/16 2001:db8:1234::/48

az network vnet subnet create --vnet-name vnet-meridian-prod --resource-group rg-networking-prod \
  --name snet-dualstack --address-prefixes 10.0.5.0/24 2001:db8:1234:5::/64
```

**A real, worth-knowing current limitation: Azure Firewall (Part 7) can be deployed into a dual-stack VNet, but its own dedicated subnet must remain IPv4-only, and the firewall itself only filters IPv4 traffic** — a design planning IPv6 support end-to-end needs to account for this gap explicitly rather than assuming firewall inspection covers IPv6 traffic the same way it covers IPv4. IPv6 support across VNet peering (dual-stack to dual-stack) and ExpressRoute exists, and Private Link over IPv6 is in preview as of 2026 — worth checking current availability before committing a design to IPv6-dependent Private Link connectivity specifically, since preview features carry different support and SLA guarantees than generally available ones.

---

## User-Defined Routes and Route Tables

By default, Azure routes traffic using its own system routes (direct VNet-to-VNet, VNet-to-internet, and so on). A **User-Defined Route (UDR)**, attached via a **route table**, overrides that default — most commonly to force traffic through a network virtual appliance like Azure Firewall (Part 7) instead of routing directly.

```bash
az network route-table create --name rt-force-firewall --resource-group rg-networking-prod

az network route-table route create --route-table-name rt-force-firewall \
  --resource-group rg-networking-prod --name route-to-firewall \
  --address-prefix 0.0.0.0/0 --next-hop-type VirtualAppliance \
  --next-hop-ip-address 10.0.100.4

az network vnet subnet update --vnet-name vnet-meridian-spoke --resource-group rg-driver-portal-prod \
  --name snet-app --route-table rt-force-firewall
```

**Why a `0.0.0.0/0` UDR pointing at Azure Firewall's private IP is the standard hub-and-spoke security pattern, worth stating explicitly: it forces EVERY outbound packet from the spoke subnet — not just internet-bound traffic — through the firewall's inspection, closing the gap a design relying purely on NSGs (Part 7) would leave for traffic between spokes or to on-premises networks.**

```bash
# Confirm a route table is actually associated with the intended
# subnet(s) — a genuinely common gap after creating the route table
# but forgetting the association step
az network vnet subnet list --vnet-name vnet-meridian-spoke --resource-group rg-driver-portal-prod \
  --query "[].{name:name, routeTable:routeTable.id}" --output table
```

---

## Azure Route Server — Dynamic Routing With Network Appliances

For a more complex topology involving a third-party network virtual appliance (an SD-WAN device, a partner's own router appliance) that needs to exchange routes dynamically via BGP rather than through static UDRs, **Azure Route Server** provides a managed BGP peering point inside a VNet.

```bash
az network routeserver create --name routeserver-meridian --resource-group rg-networking-prod \
  --hosted-subnet snet-routeserver --vnet vnet-meridian-hub
```

**Worth stating precisely why this matters over hand-maintained UDRs: a network appliance can advertise route changes to Azure Route Server dynamically via BGP, and Route Server propagates them automatically — a static UDR would instead require manual updates every time the appliance's routing topology changes**, a meaningful operational difference for a network with a genuinely dynamic third-party routing component.

```bash
# Peer a network virtual appliance with Route Server via BGP
az network routeserver peering create --routeserver routeserver-meridian \
  --resource-group rg-networking-prod --name peer-nva \
  --peer-ip "10.0.100.20" --peer-asn 65001
```

---

## Forced Tunneling

**Forced tunneling** routes ALL of a VNet's internet-bound traffic back through an on-premises connection (Part 5) rather than directly out to the internet from Azure — typically for compliance reasons requiring all traffic to pass through an organization's existing, audited on-premises security stack.

```bash
az network route-table route create --route-table-name rt-forced-tunnel \
  --resource-group rg-networking-prod --name route-internet-onprem \
  --address-prefix 0.0.0.0/0 --next-hop-type VirtualNetworkGateway
```

**Worth flagging as a real tradeoff, not a free security upgrade: forced tunneling adds real latency to every internet-bound request (routing through on-premises and back) and creates a hard dependency on that on-premises connection's own availability** — worth adopting only when the compliance requirement genuinely demands it, not as a default hardening measure.

| Design | Internet traffic path | Added latency | Dependency risk |
|---|---|---|---|
| Direct outbound (NAT Gateway) | Azure directly to internet | None | None beyond Azure's own network |
| Forced tunneling | Azure → on-premises → internet → back | Real, proportional to the on-premises round trip | Full dependency on the on-premises link's availability |

---

## Azure DNS — Public DNS Zones

**Azure DNS** hosts public DNS zones — the direct analog of AWS Route 53's public hosted zones or GCP Cloud DNS's public zones.

```bash
az network dns zone create --name meridianfreight.com --resource-group rg-networking-prod

az network dns record-set a add-record --zone-name meridianfreight.com \
  --resource-group rg-networking-prod --record-set-name shipment-api --ipv4-address 20.1.2.3
```

### Alias Records — Pointing DNS at an Azure Resource That Can Change IPs

An **alias record** is Azure DNS's answer to a real limitation of standard DNS records: a standard `A` record points at a fixed IP address, which breaks the moment that IP changes (a Public IP resource gets deleted and recreated, a Front Door endpoint's underlying IP shifts). An alias record instead points at the *Azure resource itself*, and automatically tracks its current IP.

```bash
az network dns record-set a create --zone-name meridianfreight.com \
  --resource-group rg-networking-prod --name www --target-resource "<public-ip-resource-id>"
```

**Why this matters concretely: a standard `A` record pointing at a Public IP that later gets deleted and recreated (a real, non-obvious way an IP can change even without anyone editing DNS directly) silently goes stale, sending traffic to an address nobody owns anymore** — an alias record eliminates this entire failure class by resolving against the live resource at query time rather than a snapshot IP frozen into the record.

### Delegating a Domain to Azure DNS

Creating a zone in Azure DNS doesn't automatically make it authoritative for the internet at large — the domain's registrar (wherever `meridianfreight.com` was originally purchased) needs to be told to delegate authority to Azure DNS's name servers.

```bash
# Azure assigns four name servers to a new zone — retrieve them
az network dns zone show --name meridianfreight.com --resource-group rg-networking-prod \
  --query nameServers

# These four values are then entered as NS records at the REGISTRAR,
# outside Azure entirely — a manual, one-time step
```

**Why this step is easy to forget and produces a confusing symptom when skipped, worth stating explicitly: a zone can be fully configured in Azure DNS with correct records, and still resolve nothing for the public internet, because the registrar is still pointing queries at its own default name servers instead of Azure's** — the fix is entirely outside Azure (updating NS records at the registrar), which is why "the DNS records look correct in the portal" doesn't rule out this specific, easy-to-overlook cause.

---

## Azure Private DNS Zones

A **Private DNS zone** resolves names only within linked VNets, never exposing records to the public internet — the natural fit for internal service names.

```bash
az network private-dns zone create --name internal.meridianfreight.com --resource-group rg-networking-prod

az network private-dns link vnet create --zone-name internal.meridianfreight.com \
  --resource-group rg-networking-prod --name link-hub --virtual-network vnet-meridian-hub \
  --registration-enabled true
```

**Why `--registration-enabled` matters concretely: it lets VMs in the linked VNet automatically register their own DNS records in the zone on creation**, rather than requiring a platform team to manually create an A record for every new VM — directly reducing the same kind of manual toil the SRE Fundamentals series flags as worth automating away.

### Split-Horizon DNS — the Same Name, Different Answers Inside and Outside

A genuinely common enterprise pattern worth naming explicitly: **split-horizon DNS** (also called split-brain DNS) resolves the exact same domain name to a DIFFERENT answer depending on whether the query originates inside the VNet or from the public internet — typically routing internal traffic directly to a private endpoint while external traffic goes through a public-facing load balancer or Front Door.

```mermaid
graph TD
    Name["shipment-api.meridianfreight.com"] --> Internal["Query from INSIDE the VNet"]
    Name --> External["Query from the PUBLIC internet"]
    Internal --> PrivateZone["Resolves via the PRIVATE DNS zone —\nprivate IP, direct path,\nnever touches the public internet"]
    External --> PublicZone["Resolves via the PUBLIC DNS zone —\npublic IP, through Front Door/\nApplication Gateway (Part 6)"]
```

**Why this is worth deliberately designing rather than treating as a coincidence of having both zone types configured: internal service-to-service traffic that resolves to a private IP and stays entirely within the VNet is both faster (no round trip through a public-facing load balancer) and more secure (never exposed to the public internet path at all) than routing internal calls out and back through the same public endpoint external users hit.** Meridian Freight's `shipment-api` uses exactly this pattern — the `driver-portal` backend calls it via its private DNS name and private IP, while external carrier-partner integrations reach the same logical service through its public DNS name and Front Door endpoint, with both names sharing the human-readable `shipment-api.meridianfreight.com` label but resolving through entirely different zones and paths.

---

## DNS Private Resolver — the Hybrid DNS Bridge

For genuinely hybrid DNS — an on-premises server needing to resolve an Azure Private DNS zone's names, or an Azure VNet needing to resolve an on-premises domain — **Azure DNS Private Resolver** provides managed inbound and outbound DNS endpoints inside a VNet, eliminating the older pattern of running and patching custom DNS forwarder VMs.

```mermaid
graph TD
    OnPrem["On-premises DNS server"] -->|"conditional forwarder for<br/>internal.meridianfreight.com"| Inbound["DNS Private Resolver<br/>INBOUND endpoint<br/>(in the hub VNet)"]
    Inbound --> PrivateZone["Azure Private DNS Zone"]
    VNetClient["A VM in an Azure VNet"] --> Outbound["DNS Private Resolver<br/>OUTBOUND endpoint"]
    Outbound -->|"ruleset forwards<br/>onprem.meridianfreight.local<br/>queries"| OnPremDNS["On-premises DNS server"]
```

```bash
az dns-resolver create --name resolver-meridian-hub --resource-group rg-networking-prod \
  --vnet vnet-meridian-hub --location eastus

az dns-resolver inbound-endpoint create --name inbound-endpoint \
  --dns-resolver-name resolver-meridian-hub --resource-group rg-networking-prod \
  --subnet snet-dns-inbound
```

**Worth stating clearly why this replaced the older custom-DNS-forwarder-VM pattern: DNS Private Resolver is zone-redundant by default in regions supporting Availability Zones, with no separate high-availability design required from the platform team** — a meaningful operational simplification over the previous pattern of manually deploying and keeping two or more forwarder VMs patched and load-balanced for the same purpose.

---

## Name Resolution Inside a VNet

By default, every VM in a VNet uses **Azure-provided DNS**, which automatically resolves other resources in the same VNet by name (when using Private DNS zones) and forwards external queries to the public internet.

```bash
# Override a VNet's DNS servers to use custom/on-premises resolvers instead
az network vnet update --name vnet-meridian-hub --resource-group rg-networking-prod \
  --dns-servers 10.0.100.10 10.0.100.11
```

**A real, worth-knowing gotcha: changing a VNet's DNS server setting does NOT automatically apply to already-running VMs — each affected VM typically needs a restart (or its network interface to be reprocessed) before it picks up the new DNS configuration**, a detail that has caused real confusion when a DNS change appears to have "not worked" immediately after being applied.

---

## Network Watcher — First-Look Diagnostics

**Network Watcher** is Azure's built-in network diagnostics toolkit, worth introducing here as the first stop for any connectivity question this chapter's concepts raise — Part 13 covers its monitoring/alerting integration in full depth.

```bash
# Verify whether a specific flow (source/destination/port) would be
# allowed or denied by current NSG rules, WITHOUT sending real traffic
az network watcher test-ip-flow --resource-group rg-driver-portal-prod \
  --vm vm-driver-portal-01 --direction Outbound --protocol TCP \
  --local 10.0.1.4:0 --remote 8.8.8.8:443

# Trace the actual next-hop a packet would take from a given VM —
# genuinely useful for diagnosing an unexpected UDR
az network watcher show-next-hop --resource-group rg-driver-portal-prod \
  --vm vm-driver-portal-01 --source-ip 10.0.1.4 --dest-ip 10.0.2.4
```

**Why `test-ip-flow` is worth reaching for before manually re-reading every NSG rule by hand: it evaluates the EFFECTIVE result of every applicable rule at once (including inherited rules from associated NSGs at both the subnet and NIC level) and states plainly whether a specific flow is allowed or denied** — dramatically faster than manually tracing rule precedence across multiple NSGs.

### Connection Monitor and Topology View

Two more Network Watcher capabilities worth knowing exist, beyond the one-shot diagnostic commands above. **Connection Monitor** runs CONTINUOUS, ongoing connectivity checks between two endpoints (a VM to another VM, a VM to an external URL, cross-region), rather than a single point-in-time test — genuinely useful for catching an intermittent connectivity issue that a one-time check would simply miss by not happening to run during the failure window.

```bash
az network watcher connection-monitor create --name monitor-shipment-to-driver-portal \
  --resource-group rg-driver-portal-prod \
  --endpoint-source-resource-id "<shipment-api-vm-resource-id>" \
  --endpoint-dest-resource-id "<driver-portal-vm-resource-id>" \
  --test-frequency 60
```

**Topology view** renders the actual, current resource-level topology of a VNet — subnets, NICs, NSGs, route tables, and how they connect — genuinely useful for confirming a design matches what's actually deployed, especially after several iterative changes have accumulated over time.

```bash
az network watcher show-topology --resource-group rg-driver-portal-prod
```

**Why Connection Monitor's CONTINUOUS checking matters concretely, worth stating the underlying reasoning: an intermittent failure — a route flapping, a brief NSG misconfiguration window during a deployment — is often gone by the time an engineer runs a one-shot `test-ip-flow` check to investigate a reported issue, making the one-shot tool look like it found nothing wrong even though a real problem occurred.** A Connection Monitor already running BEFORE the issue happens captures the actual failure window in its history, turning "it seems fine now, must have been transient" into "here's exactly when and for how long the check failed."

---

## A Full Worked Network Bootstrap for Meridian Freight

```bash
# 1. Create the hub VNet in the connectivity subscription
az network vnet create --name vnet-meridian-hub --resource-group rg-networking-prod \
  --address-prefix 10.0.0.0/16

# 2. Create a spoke VNet for shipment-api
az network vnet create --name vnet-shipment-api --resource-group rg-shipment-api-prod \
  --address-prefix 10.1.0.0/16

# 3. Peer hub and spoke in both directions, with gateway transit
az network vnet peering create --name peer-spoke-to-hub --vnet-name vnet-shipment-api \
  --resource-group rg-shipment-api-prod --remote-vnet vnet-meridian-hub \
  --allow-vnet-access true --use-remote-gateways true
az network vnet peering create --name peer-hub-to-spoke --vnet-name vnet-meridian-hub \
  --resource-group rg-networking-prod --remote-vnet vnet-shipment-api \
  --allow-vnet-access true --allow-gateway-transit true

# 4. Attach NAT Gateway to the spoke's app subnet for outbound access
#    (mandatory since March 2026's private-by-default change)
az network nat gateway create --name nat-shipment-api --resource-group rg-shipment-api-prod \
  --public-ip-addresses pip-nat-shipment-api
az network vnet subnet update --vnet-name vnet-shipment-api --resource-group rg-shipment-api-prod \
  --name snet-app --nat-gateway nat-shipment-api

# 5. Create a Private DNS zone for internal service names, linked to the hub
az network private-dns zone create --name internal.meridianfreight.com --resource-group rg-networking-prod
az network private-dns link vnet create --zone-name internal.meridianfreight.com \
  --resource-group rg-networking-prod --name link-hub --virtual-network vnet-meridian-hub \
  --registration-enabled true

# 6. Force spoke traffic through the future Azure Firewall (Part 7) via UDR
az network route-table create --name rt-force-firewall --resource-group rg-shipment-api-prod
az network route-table route create --route-table-name rt-force-firewall \
  --resource-group rg-shipment-api-prod --name route-to-firewall \
  --address-prefix 0.0.0.0/0 --next-hop-type VirtualAppliance --next-hop-ip-address 10.0.100.4
```

---

## Part 4 CLI Cheat Sheet

| Area | Command | Purpose |
|---|---|---|
| VNets | `az network vnet create` | Create a virtual network |
| Subnets | `az network vnet subnet create` | Create a subnet |
| NAT | `az network nat gateway create` | Create a NAT Gateway for outbound access |
| Public IP | `az network public-ip create --sku Standard` | Create a Standard SKU public IP |
| Peering | `az network vnet peering create` | Peer two VNets (create in both directions) |
| AVNM | `az network manager create` | Create a Virtual Network Manager instance |
| Routing | `az network route-table create` / `route create` | Create a UDR forcing traffic through an appliance |
| Route Server | `az network routeserver create` | Create a managed BGP peering point |
| Public DNS | `az network dns zone create` | Create a public DNS zone |
| Public DNS | `az network dns record-set a create --target-resource` | Create an alias record tracking a resource's live IP |
| Public DNS | `az network dns zone show --query nameServers` | Retrieve name servers to configure at the domain registrar |
| Diagnostics | `az network watcher connection-monitor create` | Set up continuous connectivity monitoring |
| Diagnostics | `az network watcher show-topology` | Render a VNet's actual current resource topology |
| IPAM | `az network manager ipam-pool create` | Create a root or child IPAM pool |
| IPAM | `az network vnet create --ipam-pool-id` | Allocate a VNet's address space from a pool |
| Private DNS | `az network private-dns zone create` | Create a private DNS zone |
| DNS Resolver | `az dns-resolver create` | Create a hybrid DNS bridge |
| Diagnostics | `az network watcher test-ip-flow` | Check whether a specific flow is allowed |
| Diagnostics | `az network watcher show-next-hop` | Trace a packet's actual routing next-hop |
| Performance | `az vm create --accelerated-networking true` | Enable SR-IOV-based low-latency networking |
| Encryption | `az network vnet encryption update` | Enable intra-VNet traffic encryption |
| Cross-sub peering | `az network vnet peering create --remote-vnet <full-resource-id>` | Peer VNets across subscriptions |

---

## Common Mistakes and Interview Traps

| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Assuming a `/24` subnet fits 254 usable hosts | Azure reserves 5 addresses per subnet, leaving 251 usable in a `/24` | Account for the 5 reserved addresses when sizing any subnet |
| Deploying a new VNet after March 2026 and assuming implicit outbound internet access | Subnets are private by default as of this platform change — no automatic outbound path exists | Explicitly attach a NAT Gateway (or another outbound method) to every subnet needing internet access |
| Assuming VNet peering is transitive | A peers B, B peers C does NOT let A reach C automatically | Peer every pair needing connectivity directly, or use a hub-and-spoke design |
| Giving every spoke its own VPN/ExpressRoute gateway | Unnecessary cost and management duplication | Use gateway transit so spokes share the hub's single gateway |
| Assuming a DNS server change on a VNet applies to already-running VMs immediately | Existing VMs typically need a restart/NIC reprocess to pick up new DNS settings | Restart affected VMs (or plan the change during a maintenance window) after updating VNet DNS settings |
| Placing ordinary VMs in a subnet already delegated to a PaaS service | Delegated subnets are reserved exclusively for the delegated service | Use a separate, non-delegated subnet for ordinary VM workloads |
| Manually re-reading every NSG rule to answer "is this traffic allowed" | Slow and error-prone when multiple NSGs and inherited rules are involved | Use `az network watcher test-ip-flow` for the effective, authoritative answer |
| Adopting forced tunneling as a default hardening measure | Adds real latency and a hard dependency on the on-premises connection's availability | Reserve forced tunneling for a genuine compliance requirement, not blanket hardening |
| Leaving Accelerated Networking disabled on a supported VM size | Free, no-cost performance improvement left unused for no reason | Enable it by default for any VM size that supports it |
| Assuming VNet encryption replaces the need for application-layer TLS | It only protects the physical network layer between VMs in the same VNet, not traffic leaving the VNet or the application layer itself | Treat VNet encryption as a defense-in-depth layer underneath TLS, not a substitute for it |
| Assuming Azure Firewall inspects IPv6 traffic in a dual-stack VNet | Azure Firewall's dedicated subnet must remain IPv4-only and it only filters IPv4 | Plan a separate strategy for IPv6 traffic inspection if a design genuinely needs end-to-end IPv6 |
| Naming a VPN/ExpressRoute gateway's subnet anything other than the exact string `GatewaySubnet` | Azure's gateway provisioning specifically looks for this exact, case-sensitive name — any other name fails gateway creation outright | Always name the dedicated gateway subnet exactly `GatewaySubnet` |
| Letting different teams pick VNet address ranges independently without a shared allocation source | Overlapping address spaces make peering between the affected VNets impossible without disruptive recreation | Use centralized IPAM pools so every VNet's range is allocated from a single, conflict-free source |
| Using a standard `A` record for a resource whose underlying IP can change (e.g., a recreated Public IP) | The record silently goes stale, routing traffic to an address nobody owns anymore | Use an alias record that tracks the Azure resource itself, not a frozen IP snapshot |
| Configuring an Azure DNS zone correctly but never updating the registrar's NS records | The zone is never actually authoritative for the public internet, no matter how correct its records look in the portal | Retrieve the zone's assigned name servers and configure them at the domain registrar as a required, separate step |
| Relying only on one-shot diagnostic checks to investigate a reported intermittent connectivity issue | The issue is often already gone by the time a one-shot check runs, making it look like nothing is wrong | Use Connection Monitor for continuous checking on any link with a history of intermittent failures |

---

## Worked Practice Problems

**Problem 1:** A team provisions a new VNet and subnet in September 2026, deploys a VM with no public IP into it, and finds the VM cannot reach the public internet at all — not even basic OS update checks succeed. The team suspects a misconfigured NSG. What's the more likely cause, and how would you confirm it?

*Answer:* The more likely cause is the March 2026 platform default change making new subnets private by default, with no automatic outbound internet path — this failure mode looks identical to an NSG blocking outbound traffic, which is exactly why it's worth checking first rather than assuming NSG misconfiguration. Confirm with `az network watcher test-ip-flow` testing outbound traffic to an external destination: if NSG rules show the traffic as allowed but it still fails, the cause is the missing outbound path (no NAT Gateway or equivalent attached to the subnet), not NSG rules. The fix is attaching a NAT Gateway to the subnet, which is now a required, explicit step for any subnet needing outbound internet access rather than something that happens automatically.

**Problem 2:** Meridian Freight has a hub VNet, and two spoke VNets (`vnet-shipment-api` and `vnet-driver-portal`), each peered directly to the hub. A developer asks why a VM in `vnet-shipment-api` cannot reach a VM in `vnet-driver-portal` by private IP, despite both spokes being connected to the same hub. What's the underlying cause?

*Answer:* VNet peering is not transitive — each spoke's peering relationship is only with the hub, not with each other, so connectivity between the two spokes does not exist automatically just because both are peered to the same third VNet. If direct spoke-to-spoke connectivity is genuinely required, it needs its own explicit peering relationship between the two spokes directly (or, if using Azure Virtual Network Manager, enabling direct connectivity within the same spoke network group) — simply being connected to a common hub does not transitively bridge them.

**Problem 3:** A platform team designs Meridian Freight's hub-and-spoke topology and configures every spoke with its own dedicated VPN gateway for on-premises connectivity, reasoning that "each workload team should own their full stack independently." Six months later, a cost review flags this as a significant, avoidable expense. What's the architectural fix, and is the team's original reasoning about independence entirely wrong?

*Answer:* The architectural fix is gateway transit: consolidate to a single VPN (or ExpressRoute) gateway in the hub VNet, with every spoke peered using `useRemoteGateways`, letting all spokes share that one gateway rather than each provisioning and paying for their own. The team's underlying instinct toward workload team independence isn't entirely wrong — it's a reasonable goal for APPLICATION-level resources — but a networking gateway is exactly the kind of shared, foundational infrastructure the Part 1 landing zone pattern deliberately centralizes in a platform-owned Connectivity subscription, precisely because duplicating it per team adds real cost and management overhead without a corresponding benefit; workload team independence is better served by giving each team ownership of their own spoke's application resources, not by duplicating shared network infrastructure per team.

**Problem 4:** An engineer needs a subnet to host an App Service with VNet integration and also wants to place two general-purpose VMs in the same subnet to save on subnet count. The deployment of the VMs fails. Why, and what's the correct design?

*Answer:* The subnet was delegated to `Microsoft.Web/serverFarms` for the App Service integration, and a delegated subnet is reserved exclusively for the delegated service — it cannot also host ordinary VM resources, which is exactly why the VM deployment failed. The correct design uses two separate subnets: one delegated subnet for the App Service integration, and a separate, non-delegated subnet for the general-purpose VMs. Subnet count is rarely a meaningfully scarce resource within a reasonably sized VNet address space, so consolidating for that reason alone isn't worth the delegation conflict it creates.

**Problem 5:** Meridian Freight's on-premises network needs to resolve names in an Azure Private DNS zone, and an Azure VNet separately needs to resolve names in the on-premises domain. A team proposes deploying and manually maintaining two custom DNS forwarder VMs (one for each direction), citing "this is how hybrid DNS has always been done." What would you recommend instead, and why?

*Answer:* Azure DNS Private Resolver is the current, purpose-built answer to exactly this bidirectional hybrid DNS requirement — it provides managed inbound and outbound DNS endpoints inside a VNet, handling both directions (on-premises resolving Azure private zones via the inbound endpoint, and Azure resolving on-premises domains via the outbound endpoint and a forwarding ruleset) without deploying or patching any DNS forwarder VMs at all. It's also zone-redundant by default in regions with Availability Zone support, removing the operational burden the manual VM approach would require to achieve equivalent high availability. The team's instinct reflects how hybrid DNS was historically done before this managed service existed, but recommending the older, more operationally expensive pattern when a fully managed, purpose-built alternative now exists isn't the right call for a design being built today.

**Problem 6:** After a corporate acquisition, Meridian Freight needs to peer its own hub VNet with a newly acquired carrier company's existing VNet, which lives in a completely separate Microsoft Entra tenant with its own independent Azure environment. An engineer attempts the peering using the same command used for same-tenant cross-subscription peering and it fails with an authorization error. What's missing, and why is this a genuinely different mechanism from the Entra External ID guest access covered in Part 2?

*Answer:* Cross-tenant VNet peering requires an explicit, one-time authorization step where the initiating tenant's subscription is granted permission to peer with the target tenant's subscription — this is a deliberate trust boundary at the Azure Resource Manager/networking layer, separate from any identity-level trust. This is a genuinely different mechanism from Part 2's Entra External ID guest access: guest access governs whether a PERSON from one tenant can sign in and use applications/resources in another tenant's directory, while cross-tenant peering governs whether NETWORK TRAFFIC can flow between two tenants' VNets at the infrastructure layer — an organization can have one without the other, and setting up guest access for the acquired company's staff does nothing to enable the network-level peering this scenario needs.

**Problem 7:** A cost review of Meridian Freight's Azure networking spend finds meaningfully higher-than-expected data transfer charges between the hub VNet (in `eastus`) and one specific spoke VNet the platform team recently created in `westus2` to support a new regional office. What's the likely cause, and is moving the spoke back to `eastus` the only fix?

*Answer:* The likely cause is global VNet peering's higher per-GB data transfer cost compared to regional peering — connecting a spoke in a different region from the hub necessarily uses global peering, which costs more than same-region regional peering, even though both stay entirely on Microsoft's private backbone rather than the public internet. Moving the spoke to `eastus` is one fix, but not the only one, and not necessarily the right one if the regional office's actual latency-sensitive traffic benefits from a `westus2`-local presence — the better first question is whether the specific traffic pattern driving the high transfer cost (e.g., a chatty application making many small cross-region calls rather than occasional bulk transfers) could be redesigned to reduce cross-region chattiness, before assuming the region placement itself was the mistake.

**Problem 8:** Two independent teams at Meridian Freight each create a new VNet for a separate project without coordinating beforehand, and both happen to use `10.0.0.0/16` as their address space, following the same commonly-copied example from Azure documentation. Months later, the two teams need to peer their VNets to share a database connection, and the peering request fails. What's the underlying cause, and how would centralized IPAM have prevented this from happening in the first place?

*Answer:* VNets with overlapping address spaces cannot be peered at all — `10.0.0.0/16` used identically by both VNets means Azure cannot establish a routable peering relationship between them, since the same IP address would be ambiguous about which VNet it belongs to. The only fix at this point is recreating one of the two VNets with a non-overlapping address range, which is genuinely disruptive if either is already running production workloads. Centralized IPAM pools prevent this class of mistake structurally: if both teams had requested their VNet's address space from a shared IPAM pool instead of picking a range by hand, the pool would have allocated two guaranteed non-overlapping ranges automatically, making this specific failure impossible rather than merely discouraged by a process teams might forget to follow.

**Problem 9:** Meridian Freight's `shipment-api` needs to be reachable both by external carrier partners (over the public internet, through Front Door) and by the internal `driver-portal` backend (which should never route through the public internet for security and latency reasons), using the same human-readable domain name for both. What DNS design achieves this, and why not simply give the two callers different domain names instead?

*Answer:* Split-horizon DNS is the right design — configuring both a public DNS zone (resolving `shipment-api.meridianfreight.com` to the Front Door endpoint for external callers) and a private DNS zone linked to the internal VNet (resolving the identical name to the service's private IP for internal callers), so the same name yields different, path-appropriate answers depending on where the query originates. Using different domain names for the two audiences would work functionally, but it fragments the service's identity across two names for what is conceptually one logical service, complicates certificate management (Part 12) and documentation, and makes it harder to later change which caller uses which path without a coordinated rename — split-horizon DNS keeps the one meaningful name while still routing each caller down the appropriate, path-optimized route transparently.

**Problem 10:** Meridian Freight's platform team creates a new Azure DNS zone for `meridianfreight.com`, adds all the correct A and alias records through the portal, and confirms every record looks correct there. External users report the domain simply doesn't resolve at all, as if it doesn't exist. Internal review of the Azure DNS zone finds nothing wrong. What's the most likely cause outside Azure entirely, and how would you confirm it?

*Answer:* The most likely cause is that the domain's registrar was never updated to delegate authority to Azure DNS's assigned name servers — the zone can be perfectly configured inside Azure and still be completely invisible to the public internet, because DNS resolvers follow the registrar's NS records to find the authoritative name servers, and if those still point at the registrar's own default servers (or a previous DNS provider), Azure's zone is never actually queried at all. Confirming it is straightforward: run `az network dns zone show --query nameServers` to get Azure's assigned name servers, then check the domain's current NS records at the registrar (via a public DNS lookup tool or the registrar's own control panel) — a mismatch between the two confirms the registrar was never updated, which is a one-time, outside-Azure fix rather than anything wrong with the zone configuration itself.

---

## Summary and What's Next

- Azure reserves **five IP addresses per subnet**, not one or two — a `/24` subnet has 251 usable addresses, a detail that changes real subnet-sizing math.
- **As of March 2026, new subnets are private by default** — outbound internet access requires an explicit method (NAT Gateway is the current recommendation), a genuinely important recent platform change.
- **VNet peering is not transitive** — a hub-and-spoke topology, with gateway transit letting spokes share the hub's gateway, is the standard pattern for connecting many VNets without duplicating shared infrastructure.
- **Azure Virtual Network Manager** manages topology and connectivity declaratively at scale, recommended once an organization has more than roughly 10 spokes.
- **User-Defined Routes force traffic through a network appliance** (typically Azure Firewall, Part 7) — the standard hub-and-spoke security pattern uses a `0.0.0.0/0` UDR pointing at the firewall's private IP.
- **Azure DNS Private Resolver replaced the older custom-DNS-forwarder-VM pattern** for hybrid DNS, providing managed, zone-redundant inbound/outbound endpoints with no VM patching required.
- **Network Watcher's `test-ip-flow` gives an authoritative, effective answer** to "is this traffic allowed" faster than manually tracing NSG rule precedence.
- **Accelerated Networking is free and near-default for any supported VM size** — VNet encryption adds a complementary, defense-in-depth layer against physical-network eavesdropping, neither replacing application-layer TLS.
- **Cross-subscription peering needs the remote VNet's full resource ID; cross-tenant peering additionally needs an explicit, one-time authorization step** — a separate trust mechanism from Entra External ID's identity-level guest access.
- **Dual-stack IPv6 support exists across VNets, peering, and ExpressRoute, but Azure Firewall currently only filters IPv4** — a real gap to plan around for any design needing genuine end-to-end IPv6 traffic inspection.
- **Centralized IPAM pools make overlapping address spaces structurally impossible** rather than relying on every team remembering to check a shared spreadsheet before creating a VNet.
- **Alias records track a live Azure resource rather than a frozen IP snapshot**, closing a real staleness gap standard `A` records have whenever the underlying resource's IP can change.
- **Split-horizon DNS lets one human-readable name resolve differently for internal and external callers** — the standard pattern for routing internal traffic privately while external traffic goes through a public-facing endpoint.

**Continue to Part 5** (`05-networking-hybrid-connectivity.md`) for the VPN, ExpressRoute, and Virtual WAN mechanisms that connect this chapter's VNets back to Meridian Freight's on-premises network.
