Load balancing & Bastion
Load Balancer, App Gateway/WAF, Bastion, DNS.
Every big office used to run a mail room, and two very different people worked in it. One of them never opened anything. She read the outside of the envelope, building, floor, department number, and flung each letter into the right bin at astonishing speed. The other opened every letter, read what it actually asked for, sent invoices to accounting and complaints to legal, and shredded anything that smelled like a threat before it reached a desk. Azure hires both. Azure Load Balancer is the envelope reader. It works at *Layer 4* (the level of the network stack that knows only IP addresses, port numbers and the protocol, TCP or UDP), so it never looks inside. Application Gateway is the letter opener. It works at *Layer 7* (the application level, where URLs, headers and cookies live), and it can carry a WAF (web application firewall), the shredder for malicious requests. Half of this lesson is choosing the right one. The other half is building them with real commands.
Two questions pick the service for you
Every load-balancing question on the AZ-104 exam collapses into two questions of your own. First: what does the service need to see before it can route? If "push this TCP or UDP flow to a backend that is still alive" covers it, Layer 4 is plenty. If the decision depends on the request itself, sending /api/* to one pool, terminating TLS (transport layer security, the encryption behind HTTPS), blocking a SQL injection attempt, you need Layer 7. Second: where are your users? Regional services spread traffic inside one Azure region. Global services steer users between regions. Cross those two axes and you get four boxes. Load Balancer is regional Layer 4. Application Gateway is regional Layer 7 with a WAF. Azure Front Door is global Layer 7, with caching (a CDN, or content delivery network) and a WAF running in Microsoft's edge sites around the world. Traffic Manager is global too, but it works through DNS (domain name system, the internet's phone book). It answers the client's lookup with the name of the healthiest endpoint and then steps out of the traffic path completely. That is why it copes with any protocol at all, and also why its failover is only as fast as DNS caching allows, since every answer carries a TTL (time to live) that clients obey. Front Door sits *in* the traffic path using anycast routing, so it speaks HTTP and HTTPS only, but it fails over in seconds. Learn the grid by its two axes. Four unrelated product names will not stick.
Build a Standard Load Balancer
A load balancer is four objects bolted together. The frontend IP is the address clients connect to. The backend pool is a list of NICs (network interface cards, the virtual network adapters attached to your VMs or scale set). The health probe is how it decides who is alive. The rule ties a frontend port to a backend port. Inside, Azure Load Balancer is *not* a proxy. It runs a five-tuple hash over source IP, source port, destination IP, destination port and protocol, picks a backend from the result, rewrites the packet's destination and forwards it on. Your backend still sees the client's real source address. One date matters for the exam: the Basic SKU was retired on 30 September 2025 (SKU is Microsoft's word for a feature and pricing tier). Standard is the only realistic choice now, leaving aside the niche Gateway SKU used for chaining virtual appliances. Standard behaves differently in three ways worth remembering. It can spread across availability zones. It carries a 99.99% SLA (service level agreement, the uptime Microsoft commits to in writing). And it is *closed by default*: every inbound packet is dropped until an NSG (network security group, the packet filter you attach to a subnet or a NIC) explicitly permits it, exactly as you set up in the previous lesson.
# 1. Public frontend IP (Standard SKU, zone-redundant)az network public-ip create -g rg-web -n pip-lb-web \--sku Standard --zone 1 2 3# 2. The load balancer with a frontend and an empty backend poolaz network lb create -g rg-web -n lb-web --sku Standard \--public-ip-address pip-lb-web \--frontend-ip-name fe-web --backend-pool-name bp-web# 3. Health probe: poll /healthz every 5 secondsaz network lb probe create -g rg-web --lb-name lb-web -n probe-http \--protocol Http --port 80 --path /healthz --interval 5# 4. Rule: frontend :80 -> backend :80, gated by the probeaz network lb rule create -g rg-web --lb-name lb-web -n rule-http \--protocol Tcp --frontend-port 80 --backend-port 80 \--frontend-ip-name fe-web --backend-pool-name bp-web \--probe-name probe-http --idle-timeout 15# 5. Put each VM's NIC into the poolaz network nic ip-config address-pool add -g rg-web \--nic-name vm-web-1VMNic --ip-config-name ipconfig1 \--lb-name lb-web --address-pool bp-web# Output of step 2 (trimmed):# {# "loadBalancer": {# "name": "lb-web",# "provisioningState": "Succeeded",# "sku": { "name": "Standard" }# }# }
Grab the frontend address and hit it a few times. Traffic is spread by hash, not by strict round-robin. Each new TCP connection leaves from a fresh source port, so the hash changes and the backend usually changes with it. Do not expect a tidy A, B, A, B pattern:
$ az network public-ip show -g rg-web -n pip-lb-web \--query ipAddress -o tsv20.242.150.87$ for i in 1 2 3 4; do curl -s http://20.242.150.87; doneHello from vm-web-1Hello from vm-web-2Hello from vm-web-2Hello from vm-web-1
When a probe fails several times in a row, that instance drops out of rotation within seconds, and connections already in flight are allowed to finish rather than being cut off mid-sentence. Point the probe at /healthz, an endpoint that actually checks the app's real dependencies, rather than at /. A bare / tells you the web server answered the door. It says nothing about whether the application behind it can still reach its database, and you will cheerfully keep feeding customers to a dead app.
Layer 7: Application Gateway and the WAF
Application Gateway *is* a proxy. It answers the client's TCP connection itself, unwraps the TLS, reads the whole HTTP request, then opens a brand-new connection to a backend. That extra hop is what buys you path-based routing (/api/* and /images/* going to different backend pools), routing by hostname, cookie-based session affinity and TLS offload. The WAF_v2 SKU adds the web application firewall, driven by managed rule sets. Deploy Microsoft_DefaultRuleSet 2.1, which stacks Microsoft's threat intelligence on top of the OWASP Core Rule Set (OWASP is the Open Worldwide Application Security Project, a nonprofit whose published rules catch the classic web attacks) to stop SQL injection, cross-site scripting and known CVE exploits (a CVE, or common vulnerabilities and exposures entry, is a publicly catalogued software flaw with an ID number). A WAF policy runs in one of two modes. Detection logs what it would have blocked and blocks nothing, which is where you start, so you can watch for false positives on genuine customer traffic. Prevention actually enforces. Want the same shield in every region at once? Front Door Premium runs a WAF out at the edge instead. Two practical facts round this out. The gateway insists on a *dedicated subnet* of its own, and v2 can autoscale, though the example below pins two fixed instances. At a baseline of roughly $300 a month before you serve a single request, it is the priciest regional component here, so small internal apps often go without.
# WAF policy with the modern managed rule set, then enforce itaz network application-gateway waf-policy create -g rg-web \-n wafpol-web --type Microsoft_DefaultRuleSet --version 2.1az network application-gateway waf-policy policy-setting update \-g rg-web --policy-name wafpol-web \--mode Prevention --state Enabled# App Gateway WAF_v2 in its own subnet, policy attachedaz network application-gateway create -g rg-web -n agw-web \--sku WAF_v2 --capacity 2 --priority 100 \--vnet-name vnet-prod --subnet snet-agw \--public-ip-address pip-agw --waf-policy wafpol-web \--servers 10.0.1.4 10.0.1.5# Deployment runs ~6-8 minutes, then:# "provisioningState": "Succeeded",# "operationalState": "Running"
Bastion: reach VMs that have no public address
Not one VM in this design has a *public IP*. So how do you get a shell on one? The old answer was a jump box: a hardened VM with a public address that you log into first and hop inward from. But a jump box is one more machine to patch, and the minute it exists, its open port 22 (SSH, or secure shell, the standard remote login for Linux) or port 3389 (RDP, or remote desktop protocol, the Windows equivalent) starts getting scanned by strangers. Azure Bastion is Microsoft's managed version of that jump host. You reach Bastion over TLS on port 443, from the portal or the CLI (command line interface), and Bastion opens the RDP or SSH session to the VM's private address from inside the VNet (virtual network). No public IPs on your VMs, no inbound management ports in your NSGs, nothing for a scanner to find. Two hard requirements catch people out, on the exam and in production: the subnet must be named exactly AzureBastionSubnet, and it must be at least a /26.
# Bastion demands a dedicated subnet with this EXACT name, >= /26az network vnet subnet create -g rg-web --vnet-name vnet-prod \-n AzureBastionSubnet --address-prefixes 10.0.255.0/26az network public-ip create -g rg-web -n pip-bastion --sku Standard# Standard SKU + tunneling = native ssh/rdp from your terminal# (deployment takes ~10 minutes)az network bastion create -g rg-web -n bas-prod \--vnet-name vnet-prod --public-ip-address pip-bastion \--sku Standard --enable-tunneling true# Connect with your own SSH key -- no public IP, no open port 22az network bastion ssh -g rg-web -n bas-prod \--target-resource-id $(az vm show -g rg-web -n vm-web-1 \--query id -o tsv) \--auth-type ssh-key --username azureuser \--ssh-key ~/.ssh/id_ed25519# Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-1030-azure x86_64)# azureuser@vm-web-1:~$
Learn the SKU ladder. Developer is free, limited to dev and test, allows one connection at a time and needs no dedicated subnet. Basic gives you browser-based sessions. Standard adds native-client tunneling, file transfer, and scaling up to 50 instances. Premium adds session recording and a private-only deployment. Bastion bills by the hour whether anyone is connected or not, around $140 a month even at Basic, so tear it down when a lab session ends or stay on the Developer SKU. Production teams pay the bill without blinking. It is what deleting every public management port in the estate costs.
DNS, and where this design goes next
Azure DNS hosts your public zones on Microsoft's nameservers, and you manage them with the same az commands and the same RBAC (role-based access control, the permission system) as everything else (az network dns record-set a add-record ...). The detail worth carrying into the exam is the alias record. The DNS standard forbids a CNAME at the apex of a zone, meaning the bare contoso.com with nothing in front of it. An alias record is Azure's way around that. It points straight at an Azure public IP, a Traffic Manager profile or a Front Door endpoint, and it *follows the resource*. If your load balancer's address ever changes, the record updates itself. Its private sibling, the private DNS zone, resolves names inside a VNet with no exposure to the internet whatsoever.
Notice which way traffic has been flowing all lesson. Inward, from the internet, filtered by a WAF, spread by a hash, administered over TLS, and never landing on a VM with a public address. The next lesson turns the arrow around. Your VMs also call *outward* to Azure PaaS services (platform as a service: the storage accounts, databases and key vaults that Microsoft runs on your behalf), and by default those calls travel to public endpoints over the internet. Private Endpoints pull those services into your VNet as ordinary private IP addresses, and the private DNS zones you met a paragraph ago are precisely what makes their names resolve to those addresses. That is the last piece of a network in which nothing important is public.
Pick the balancer by how deep it has to look. If all you need is port 443 delivered to a pool of NICs with a health check in front, Standard Load Balancer does the job and costs little. If you need host headers, path routing, TLS termination or OWASP rules, that is Application Gateway, or Front Door when the audience is worldwide. Confusing the two is one of the most reliable traps on the exam.
Bastion is the operational half of the same idea: a managed jump host living inside your VNet, so the network security groups on your VMs can deny SSH and RDP from the internet outright. When you still need a temporary management opening, pair it with Just-In-Time VM access in Microsoft Defender for Cloud, which opens a port for one named person for a fixed window and then closes it again. Azure DNS, public zones and private ones, keeps your names steady while the frontends behind them come and go.
Try this
Take this to a lab. Create a Standard public Load Balancer, or point at an App Gateway you already run. At the very least, list the SKUs and frontend IPs so you can see the Layer 4 and Layer 7 products sitting side by side. Only create Bastion in a live lab if you are happy to pay for every hour it exists.
RG=rg-lab-lbaz group create -n $RG -l eastusaz network public-ip create -g $RG -n pip-lb --sku Standard --allocation-method Staticaz network lb create -g $RG -n lb-lab --sku Standard --public-ip-address pip-lb --frontend-ip-name fe --backend-pool-name beaz network lb show -g $RG -n lb-lab --query "{sku:sku.name,fe:frontendIpConfigurations[0].name}" -o jsonaz network lb list -g $RG -o table
$ az network lb show -g rg-lab-lb -n lb-lab --query "{sku:sku.name,fe:frontendIpConfigurations[0].name}" -o json{"sku": "Standard","fe": "fe"}# Sample output — Azure Load Balancer = L4; Application Gateway = L7 HTTP + optional WAF.
Takeaway
Three lines to carry into the exam room. Load Balancer moves TCP and UDP flows at Layer 4. Application Gateway reads HTTP at Layer 7 and can run a WAF. Bastion gives you RDP and SSH sessions without a single public management port on any VM.
Your next move: put Bastion in a dedicated AzureBastionSubnet of /26 or larger, prove you can reach a VM through it, then strip the public IPs off the old jump boxes.