Chapter 3
Virtual networks: subnets, peering, IPs, routing and DNS
The network is the foundation that VMs, private endpoints and App Service integration plug into. In this chapter you build a small hub-and-spoke network that the rest of the guide uses.
Theory
Virtual networks and subnets
A virtual network (VNet) is your private network in Azure. It lives in one region and one subscription, and has one or more address spaces in CIDR notation, like 10.20.0.0/16. You split it into subnets, and resources like VM network interfaces get an IP address from a subnet.
Planning the address space is the part people get wrong in real life and in the exam:
- Use private ranges (RFC 1918:
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16). - Address spaces must not overlap with VNets you want to peer with, or with on-premises networks you want to connect via VPN or ExpressRoute. Overlap can't be fixed later without re-addressing.
- You can add or resize address spaces and subnets later, but a subnet can only shrink if no IP addresses are in use outside the new range.
Five addresses are reserved in every subnet
Azure reserves five IP addresses in each subnet, so a /24 has 251 usable addresses, not 254. The smallest subnet for IPv4 is a /29 (8 addresses, 3 usable).
Special subnets
Some Azure services need a subnet with an exact name and a minimum size. Expect at least one question on this:
| Subnet name | For | Minimum size |
|---|---|---|
GatewaySubnet | VPN gateway and ExpressRoute gateway | /29 (use /27) |
AzureBastionSubnet | Azure Bastion Basic/Standard/Premium | /26 |
AzureFirewallSubnet | Azure Firewall | /26 |
RouteServerSubnet | Azure Route Server | /26 (use /27) |
Other subnets can be delegated to a service, which then injects its own resources there. Examples: Microsoft.Web/serverFarms for App Service VNet integration (chapter 8) or Microsoft.ContainerInstance/containerGroups for ACI in a VNet. A delegated subnet can only be used by that service.
Private subnets and outbound internet access
Until recently, any VM without a public IP still got default outbound access to the internet through a hidden Microsoft IP. That's being retired: new VNets now create private subnets by default. In a private subnet a VM can only reach the internet through an explicit outbound method. In order of precedence:
- NAT Gateway on the subnet (recommended for production).
- A public IP on the VM's NIC.
- A Standard Load Balancer with outbound rules (chapter 6).
In the lab you'll turn private-subnet mode off for snet-web so your VMs can install a web server in chapter 5. In chapter 6 you then add a proper outbound rule.
IP addresses
Private IP addresses
Every NIC gets a private IP from its subnet, either dynamic (the next free address; it's kept as long as the NIC exists, even across reboots) or static (you pick an address from the subnet). Always set static IPs in Azure, never inside the guest OS. If you hardcode an IP in Windows or Linux and it doesn't match, the VM loses connectivity.
Public IP addresses
A public IP address is a separate resource that you associate with a VM NIC, a public load balancer, an Application Gateway, a VPN gateway, a NAT Gateway, Azure Bastion or Azure Firewall. The choices when you create one:
- SKU: Standard. The Basic SKU was retired on 30 September 2025. Standard IPs are always static and secure by default: inbound traffic is blocked unless an NSG allows it.
- Availability zone: zone-redundant (survives a zone failure), zonal (pinned to zone 1, 2 or 3), or no zone. A zonal resource needs a zonal or zone-redundant IP in the same zone.
- Tier: Regional (normal) or Global (only for the cross-region load balancer).
- Routing preference: Microsoft network (traffic enters Microsoft's backbone close to the user) or Internet (cheaper, uses ISP networks longer).
- IP version: IPv4 or IPv6.
A public IP prefix reserves a contiguous block of public IPs (like a /28), which is handy when partners need to allow-list your addresses.
"You add a Standard public IP to a VM but can't reach it on port 80." Standard SKU IPs are closed by default. You need an NSG rule that allows port 80 on the NIC or subnet.
Virtual network peering
Peering connects two VNets so their resources talk to each other with private IPs over the Microsoft backbone, with low latency and no gateway in between. Regional peering connects VNets in the same region, global peering connects VNets in different regions. Peering also works across subscriptions and tenants.
The rules you must know:
- Address spaces must not overlap.
- A peering is two links, one in each VNet. The portal creates both for you. When only one side exists, the status is Initiated; when both exist, Connected. If one side is deleted, the other becomes Disconnected and must be recreated.
- Peering is not transitive. If Spoke A peers with Hub and Spoke B peers with Hub, A can't reach B through the hub by default.
- Data transfer over peering is billed (inbound and outbound), and global peering costs more.
Peering settings
Each side of a peering has four settings. The portal words them as "allow" questions:
- Allow access to the remote VNet: the normal case. Turn it off to block traffic without deleting the peering.
- Allow forwarded traffic: accept traffic that didn't originate in the peered VNet, for example traffic that a firewall in the hub forwards from Spoke B. Needed for hub-and-spoke routing.
- Allow gateway transit (on the hub side): let peered VNets use this VNet's VPN/ExpressRoute gateway.
- Use remote gateways (on the spoke side): use the hub's gateway instead of having one. The spoke can't have its own gateway then.
"Spokes must reach on-premises through the hub's VPN gateway." On the hub peering: Allow gateway transit. On each spoke peering: Use remote gateways. Not "allow forwarded traffic", which is about NVAs.
Routing
Azure routes traffic between subnets, peered VNets, on-premises and the internet automatically using system routes. Every subnet starts with these:
| Address prefix | Next hop |
|---|---|
| The VNet's own address space | Virtual network |
0.0.0.0/0 | Internet |
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10 | None (dropped), unless they're in your VNet |
Azure adds more system routes when you add peering (next hop VNet peering), a VPN/ExpressRoute gateway (routes learned via BGP) or service endpoints.
User-defined routes (UDRs)
To override the defaults, you create a route table with routes and associate it with subnets. The classic use is sending all traffic through a firewall (forced tunnelling): a route 0.0.0.0/0 → Virtual appliance 10.10.2.4.
The next hop types for a UDR are:
- Virtual appliance: an IP address of a firewall or NVA. The NVA's NIC must have IP forwarding enabled, and the guest OS must forward packets too.
- Virtual network gateway: send to the VPN gateway (e.g. to force internet traffic to on-premises).
- Virtual network: keep traffic inside the VNet.
- Internet: send directly out.
- None: drop the traffic (a "black hole").
Rules for route tables:
- A subnet can have at most one route table; one route table can be linked to many subnets.
- The route table must be in the same region and subscription as the VNet.
- Propagate gateway routes (on by default) lets BGP routes from your VPN/ExpressRoute gateway appear in the subnet. Turn it off to make sure your UDR to the firewall wins for on-premises traffic.
How Azure picks a route
When several routes match a destination, Azure decides in two steps:
- Longest prefix match wins: a route for
10.20.1.0/24beats10.20.0.0/16, which beats0.0.0.0/0. - If the prefixes are equally long: user-defined route > BGP route > system route.
To see what actually applies, open a VM's NIC → Effective routes, or use Network Watcher → Next hop (chapter 9). Both show the combined result of system routes, BGP and UDRs.
"After adding a route 0.0.0.0/0 → Virtual appliance, VMs can't reach anything." Check that IP forwarding is enabled on the NVA's NIC and that the NVA is running. UDRs don't check whether the next hop works.
Name resolution and Azure DNS
By default VMs use Azure-provided DNS at the virtual IP 168.63.129.16. It resolves public names and the VM names within the same VNet, but not across VNets. For anything more, there are three options:
- Custom DNS servers set on the VNet (e.g. your domain controllers). VMs pick up the change after a restart or DHCP renewal.
- Azure Private DNS zones: DNS zones that only VNets you link can resolve.
- Azure DNS Private Resolver: lets on-premises servers resolve private zones and vice versa, without running DNS VMs.
Public DNS zones
An Azure DNS zone hosts the records of a public domain like contoso.com. Azure DNS doesn't sell domains. You buy the domain at a registrar and delegate it by setting the four Azure NS name servers at the registrar. For a subdomain (like dev.contoso.com), you create a child zone and add an NS record in the parent zone.
Record types you should recognise: A and AAAA (IPv4/IPv6 address), CNAME (alias to another name, not allowed at the zone apex), MX (mail), TXT (verification, SPF), SRV, CAA and NS/SOA. An alias record set points to an Azure resource (a public IP, Traffic Manager profile or Front Door) and updates automatically when its IP changes. Alias records can be used at the apex (contoso.com itself), which a CNAME can't.
Private DNS zones
A private DNS zone (like az104.lab or privatelink.file.core.windows.net) is resolvable only from VNets that have a virtual network link to it. A link can have auto-registration turned on:
- Registration link (auto-registration on): VMs in that VNet automatically get an A record, which is updated and removed with the VM. A VNet can be a registration VNet for only one private zone.
- Resolution link (auto-registration off): the VNet can look up records but doesn't add its own. A VNet can have resolution links to many zones.
az104.lab; VMs in the hub can resolve those names.Troubleshooting connectivity
When two things can't talk, work through the layers in this order. The tools are covered in depth in chapter 9:
- Is the target running and listening? (service started, OS firewall)
- Is there a route? NIC → Effective routes, or Network Watcher Next hop. Look for UDRs to dead NVAs, missing peering, or "None".
- Does an NSG block it? NIC → Effective security rules, or Network Watcher IP flow verify (chapter 6).
- Does the name resolve correctly?
nslookupfrom the VM: does it return the private or the public IP? - End-to-end test: Network Watcher Connection troubleshoot checks all of the above in one go.
AZ-305 asks you to choose between hub-and-spoke with peering and Azure Virtual WAN (managed hub, transitive routing built in, better for many branches), and to design IP address plans that leave room for growth and never overlap with on-premises. "Plan the address space first" is always part of the right answer.
Self-check
1. How many VMs can you place in a subnet with prefix /27?
C. 32 addresses − 5 reserved = 27.
2. VNet A is peered with B, and B is peered with C. A VM in A can't reach a VM in C. What's the simplest fix?
B. Peering isn't transitive. Gateway transit is for VPN/ExpressRoute gateways, not for VM-to-VM traffic.
3. A subnet has system route 0.0.0.0/0 → Internet and a UDR 0.0.0.0/0 → Virtual appliance 10.10.2.4. Traffic to 10.20.5.8 goes where, if 10.20.0.0/16 is the VNet?
D. Longest prefix match comes first. UDR beats system only when prefixes are equally long.
4. You want contoso.com (the apex) to point at the public IP of a load balancer, and stay correct if the IP changes. What do you create?
A. CNAMEs aren't allowed at the apex; alias records are and follow the resource.
5. You need to deploy Azure Bastion Standard. Which subnet do you need?
C. The name is exact and the minimum is /26.
Lab: build the hub-and-spoke network
You build the network that every later chapter uses. Paste the variables first:
RG=rg-az104-lab; LOC=westeurope; LOC2=northeurope; INIT=xx
| VNet | Address space | Subnets |
|---|---|---|
vnet-hub | 10.10.0.0/16 | snet-shared 10.10.1.0/24 |
vnet-spoke | 10.20.0.0/16 | snet-web 10.20.1.0/24 · snet-data 10.20.2.0/24 ( snet-app 10.20.3.0/24 is added with Bicep in chapter 4) |
-
Task
Create the hub VNet
Create
vnet-hubwith the address space and subnet from the table above, in your lab region, inrg-az104-lab. Don't add Bastion, Firewall or DDoS protection.Done whenvnet-hubexists with one subnetsnet-shared.- Its tags show
envandcourse. Who added them?
Solution · Portal
- Create a resource → Virtual network. Resource group
rg-az104-lab, namevnet-hub, your region. - Security tab: leave everything off.
- IP addresses tab: change the address space to
10.10.0.0/16, edit the default subnet to namesnet-shared, range10.10.1.0/24. - Review + create. The tags come from your chapter 2 Modify policy.
Solution · Azure CLI
az network vnet create -g $RG -n vnet-hub -l $LOC \ --address-prefixes 10.10.0.0/16 --subnet-name snet-shared --subnet-prefixes 10.10.1.0/24 az network vnet show -g $RG -n vnet-hub --query tags -
Task
Create the spoke VNet
Create
vnet-spokewithsnet-webandsnet-data. One requirement: VMs insnet-webmust be able to reach the internet without a public IP or NAT gateway for now (they download a web server in chapter 5).snet-datastays a private subnet.Done when- Both subnets exist with the right ranges.
snet-webhas default outbound access enabled,snet-datadoesn't.- You can say how many IPs are usable in
snet-data.
Hint
When you edit a subnet, look for the checkbox Enable private subnet (no default outbound access).
Solution · Portal
- Create a resource → Virtual network: name
vnet-spoke, address space10.20.0.0/16. - Edit the default subnet →
snet-web,10.20.1.0/24, untick Enable private subnet. - Add a subnet →
snet-data,10.20.2.0/24, keep Enable private subnet ticked. - Review + create. Usable IPs in a /24: 251.
Solution · Azure CLI
az network vnet create -g $RG -n vnet-spoke -l $LOC --address-prefixes 10.20.0.0/16 az network vnet subnet create -g $RG --vnet-name vnet-spoke -n snet-web \ --address-prefixes 10.20.1.0/24 --default-outbound-access true az network vnet subnet create -g $RG --vnet-name vnet-spoke -n snet-data \ --address-prefixes 10.20.2.0/24 --default-outbound-access false az network vnet subnet list -g $RG --vnet-name vnet-spoke \ --query "[].{name:name, prefix:addressPrefix, defaultOutbound:defaultOutboundAccess}" -o table -
Task
Peer the hub and the spoke
Connect
vnet-hubandvnet-spokeso resources in both can reach each other with private IPs. There's no gateway or firewall, so leave gateway transit off. Name the linkspeer-hub-to-spokeandpeer-spoke-to-hub.Done when- Both VNets show the peering with status Connected.
- You know what the status would be if you'd created only one side.
Solution · Portal
- Open
vnet-hub→ Settings → Peerings → Add. - Remote virtual network summary: peering link name
peer-spoke-to-hub, virtual networkvnet-spoke, keep Allow vnet-spoke to access vnet-hub ticked. - Local virtual network summary: peering link name
peer-hub-to-spoke, keep access allowed. Add. - With one side only, the status would be Initiated.
Solution · Azure CLI
az network vnet peering create -g $RG -n peer-hub-to-spoke --vnet-name vnet-hub \ --remote-vnet vnet-spoke --allow-vnet-access az network vnet peering create -g $RG -n peer-spoke-to-hub --vnet-name vnet-spoke \ --remote-vnet vnet-hub --allow-vnet-access az network vnet peering list -g $RG --vnet-name vnet-hub --query "[].{name:name, state:peeringState}" -o table -
Task
Give your VMs DNS names
Create a private DNS zone
az104.lab. VMs in the spoke must register themselves automatically; VMs in the hub must be able to resolve those names but not register their own.Done when- The zone has two virtual network links, with auto-registration only on the spoke link.
- (You'll see the VM records appear in chapter 5.)
Solution · Portal
- Create a resource → Private DNS zone: name
az104.lab, resource grouprg-az104-lab. - Open the zone → DNS Management → Virtual Network Links → Add: name
link-spoke, VNetvnet-spoke, tick Enable auto registration. - Add another:
link-hub, VNetvnet-hub, auto registration off.
Solution · Azure CLI
az network private-dns zone create -g $RG -n az104.lab az network private-dns link vnet create -g $RG -z az104.lab -n link-spoke -v vnet-spoke -e true az network private-dns link vnet create -g $RG -z az104.lab -n link-hub -v vnet-hub -e false -
Task
Add a user-defined route
Create a route table
rt-spokeand attach it tosnet-web. Add one route namedblackhole-testnetthat drops all traffic to the documentation range203.0.113.0/24. You'll see this route again in chapter 9 with Network Watcher.Done whenrt-spokeis associated withsnet-weband has the route with next hop type None.
Solution · Portal
- Create a resource → Route table: name
rt-spoke, your region, Propagate gateway routes = Yes. - Open it → Routes → Add: name
blackhole-testnet, destination type IP Addresses, prefix203.0.113.0/24, next hop None. - Subnets → Associate:
vnet-spoke/snet-web.
Solution · Azure CLI
az network route-table create -g $RG -n rt-spoke -l $LOC az network route-table route create -g $RG --route-table-name rt-spoke -n blackhole-testnet \ --address-prefix 203.0.113.0/24 --next-hop-type None az network vnet subnet update -g $RG --vnet-name vnet-spoke -n snet-web --route-table rt-spoke -
Task
Reserve a public IP for the load balancer
Create a public IP
pip-lbe-webfor the load balancer of chapter 6. It must survive the failure of one availability zone and keep the same address forever.Done when- The IP shows SKU Standard, assignment Static and zones 1, 2, 3. Write the address down.
Solution · Portal
Create a resource → Public IP address: name
pip-lbe-web, IPv4, SKU Standard, tier Regional, availability zone Zone-redundant, routing preference Microsoft network. Standard is always static.Solution · Azure CLI
az network public-ip create -g $RG -n pip-lbe-web -l $LOC --sku Standard \ --allocation-method Static --zone 1 2 3 --query "publicIp.ipAddress" -o tsvCostA Standard public IP costs a few euros per month. VNets, peering (without traffic), route tables and private DNS zones cost almost nothing.
-
Task · optional, needs your own domain
Host a subdomain in Azure DNS
Make Azure DNS authoritative for the subdomain
lab.yourdomain.com, while your registrar keeps hosting the main domain. Then create the nameweb.lab.yourdomain.compointing atpip-lbe-webin a way that keeps working if the IP ever changes. (It serves a website after chapter 6.)Don't touch the main domainOnly add records for
labat your registrar. Never change the name servers of the domain itself, or your existing email and websites stop working.Done when- A public lookup of the NS records of
lab.yourdomain.comreturns four Azure name servers (ns1-xx.azure-dns.cometc.). web.lab.yourdomain.comresolves to the address ofpip-lbe-web.- You can explain why you used an alias record instead of a normal A record.
Hint
Delegating a subdomain = NS records with the name
labin the parent zone (at your registrar), pointing at the name servers Azure gives your new zone. Delegation can take from a few minutes up to the parent's NS TTL to be visible.Solution · Portal
- Create a resource → DNS zone: resource group
rg-az104-lab, namelab.yourdomain.com. (Leave "This zone is a child of an existing zone hosted in Azure DNS" unticked: the parent is at your registrar.) - Open the zone → Overview: copy the four Name servers.
- At your registrar's DNS management for
yourdomain.com: add four NS records with host/namelab, each with one Azure name server as value (keep the trailing dot if the registrar wants it). - Back in the zone: + Record set → name
web, type A, Alias record set: Yes, alias type Azure resource →pip-lbe-web. - Test from your Linux VM:
nslookup -type=NS lab.yourdomain.comandnslookup web.lab.yourdomain.com.
An alias record follows the Azure resource: if the IP changes or the resource is deleted, DNS updates (or empties) automatically, so you never get a dangling record pointing at an IP someone else might get.
Solution · Azure CLI
ZONE=lab.yourdomain.com az network dns zone create -g $RG -n $ZONE az network dns zone show -g $RG -n $ZONE --query nameServers -o tsv # → add these four as NS records named "lab" at your registrar PIPID=$(az network public-ip show -g $RG -n pip-lbe-web --query id -o tsv) az network dns record-set a create -g $RG -z $ZONE -n web --target-resource $PIPID nslookup -type=NS $ZONE # after the delegation is live nslookup web.$ZONECostA public DNS zone costs about €0.50 per month plus a tiny amount per million queries.
- A public lookup of the NS records of
-
Task
Protect the hub from deletion
Make sure nobody can delete
vnet-hubby accident, while still allowing changes to it. Then try to delete it to prove it works.Done when- Deleting
vnet-hubfails with a lock error, and the VNet still exists.
Solution · Portal
vnet-hub→ Settings → Locks → Add: namelock-hub-nodelete, type Delete (CanNotDelete). Then Overview → Delete → it fails.Solution · Azure CLI
az lock create -n lock-hub-nodelete -g $RG --resource-name vnet-hub \ --resource-type Microsoft.Network/virtualNetworks --lock-type CanNotDelete az network vnet delete -g $RG -n vnet-hub # fails: ScopeLockedRemember this lock: chapter 10's cleanup has to remove it first.
- Deleting
vnet-hub(locked) andvnet-spoke, peered.snet-web(default outbound on, route tablert-spoke) andsnet-data(private).- Private DNS zone
az104.lablinked to both VNets. - Public IP
pip-lbe-web, unused until chapter 6. - Optional: public DNS zone
lab.yourdomain.com, delegated from your registrar, with alias recordweb→pip-lbe-web.