Part 8: Load Balance Traffic in Azure
Route web traffic across two Azure VMs and test health-based failover.
Introduction
30 Second Summary
A public website can feel reliable until the only machine serving it stops responding. With no second machine ready, every visitor hits the same failure.
In this project, you will build a public two-server web service that distributes new connections with Azure Load Balancer. You will use health checks to keep new traffic away from a failed server.
What You'll Build
A Windows PowerShell test against one public IP will show responses from both server names before a deliberate failure leaves only the healthy server answering.
By the end of this project, you'll have:
- A single public endpoint that displays the hostname of the private web server handling each new connection.
- A two-server backend pool that distributes new TCP flows across both healthy servers.
- Visible failure and recovery proof from an HTTP health probe that removes the failed server from new traffic before restoring it after repair.
- Secret Mission: Graph the backend health drop and recovery over time with Azure Monitor.
Are there any prerequisites?
You need an active Azure subscription with permission to create and delete resource groups. You also need a Windows device with Azure portal access.
Billable Azure resources remain active until the dedicated resource group is deleted.
Before We Start
Before you begin building, take a moment to lock in the goal. You will create one public Azure Load Balancer endpoint that distributes new TCP port 80 flows across two healthy private IIS backends so the service keeps working when one application instance fails.
Prepare Two Private Web Servers
An earlier networking lab may still contain billable resources. Its unknown state makes reuse risky.
This step uses the Azure portal to inspect the old lab safely. You will deploy an isolated baseline for Azure Load Balancer with an Azure Resource Manager template.
In this step, get ready to:
- Inspect the earlier networking resource group without deleting unfamiliar resources.
- Deploy two private Azure Virtual Machines with outbound access through Azure NAT Gateway.
- Install IIS on both servers.
Inspect the earlier resource group safely
A separate resource group protects anything unfamiliar in the earlier lab. Your inspection determines whether the old group stays untouched.
- Sign in to the Azure portal from your browser.
- Search for Resource groups in the portal search bar.
- Select Resource groups from the search results.
- Look for NextWork-Networking in the resource group list.
- Choose the outcome below that matches what you see.
✔️ The resource group is absent
The earlier resource group has already been removed. Your new baseline can remain fully isolated.
ⓧ The group contains unfamiliar resources
An unfamiliar resource may belong to another project. Leaving the group unchanged protects work you have not confirmed as disposable.
- Select NextWork-Networking to inspect its resource list.
- Leave the resource group unchanged if any resource is unfamiliar.
Its virtual machines or disks may continue to incur charges. Its networking resources may also continue to incur charges.
ⓧ Every resource is disposable
Deleting the group is irreversible. Proceed only after you have confirmed that every listed resource belongs to the disposable earlier lab.
- Select NextWork-Networking from the resource group list.
- Select Delete resource group.
- Enter NextWork-Networking when Azure requests the resource group name.
- Confirm the deletion.
Deploy the dedicated baseline
The dedicated baseline keeps Part 8 independent from the earlier lab. One resource group also gives you a clear cleanup boundary.
Before you deploy
This deployment creates billable compute resources. It also creates billable disk resources.
The deployment includes billable networking resources. You will remove the entire dedicated resource group in the cleanup section if you do not want to keep them.
- Return to Resource groups in the Azure portal.
- Select Create.
- Enter NextWork-LoadBalancer as the resource group name.
- Select East US as the region.
The review now shows the dedicated name with its Azure location.
- Select Review + create.
- Select Create after validation passes.
- Select Refresh in the resource group list.
You will see NextWork-LoadBalancer in the list. That confirms the isolated cleanup boundary now exists.
- Select Create a resource from the Azure portal.
- Search for template.
- Select Template deployment from the results.
- Select Create.
- Select Build your own template in editor.
- Replace the editor contents with azuredeploy.json by pasting this code:
{"$schema":"https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#","contentVersion":"1.0.0.0","parameters":{"location":{"type":"string","defaultValue":"[resourceGroup().location]"},"adminUsername":{"type":"string","defaultValue":"azureuser"},"adminPassword":{"type":"secureString","minLength":12},"vmSize":{"type":"string","defaultValue":"Standard_B2s"}},"variables":{"vnetName":"vnet-lb-lab","subnetName":"snet-backend","nsgName":"nsg-lb-web","natGatewayName":"nat-lb-lab","natPublicIpName":"pip-nat-lb","vm1Name":"vm-lb-1","vm2Name":"vm-lb-2","nic1Name":"nic-vm-lb-1","nic2Name":"nic-vm-lb-2"},"resources":[{"type":"Microsoft.Network/publicIPAddresses","apiVersion":"2024-01-01","name":"[variables('natPublicIpName')]","location":"[parameters('location')]","sku":{"name":"Standard"},"properties":{"publicIPAddressVersion":"IPv4","publicIPAllocationMethod":"Static","idleTimeoutInMinutes":4}},{"type":"Microsoft.Network/natGateways","apiVersion":"2024-01-01","name":"[variables('natGatewayName')]","location":"[parameters('location')]","sku":{"name":"Standard"},"properties":{"idleTimeoutInMinutes":4,"publicIpAddresses":[{"id":"[resourceId('Microsoft.Network/publicIPAddresses', variables('natPublicIpName'))]"}]},"dependsOn":["[resourceId('Microsoft.Network/publicIPAddresses', variables('natPublicIpName'))]"]},{"type":"Microsoft.Network/networkSecurityGroups","apiVersion":"2024-01-01","name":"[variables('nsgName')]","location":"[parameters('location')]","properties":{"securityRules":[{"name":"allow-http","properties":{"priority":100,"access":"Allow","direction":"Inbound","protocol":"Tcp","sourcePortRange":"*","destinationPortRange":"80","sourceAddressPrefix":"Internet","destinationAddressPrefix":"*"}}]}},{"type":"Microsoft.Network/virtualNetworks","apiVersion":"2024-01-01","name":"[variables('vnetName')]","location":"[parameters('location')]","properties":{"addressSpace":{"addressPrefixes":["10.40.0.0/16"]},"subnets":[{"name":"[variables('subnetName')]","properties":{"addressPrefix":"10.40.1.0/24","defaultOutboundAccess":false,"networkSecurityGroup":{"id":"[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))]"},"natGateway":{"id":"[resourceId('Microsoft.Network/natGateways', variables('natGatewayName'))]"}}}]},"dependsOn":["[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))]","[resourceId('Microsoft.Network/natGateways', variables('natGatewayName'))]"]},{"type":"Microsoft.Network/networkInterfaces","apiVersion":"2024-01-01","name":"[variables('nic1Name')]","location":"[parameters('location')]","properties":{"enableAcceleratedNetworking":false,"enableIPForwarding":false,"ipConfigurations":[{"name":"ipconfig1","properties":{"primary":true,"privateIPAddressVersion":"IPv4","privateIPAllocationMethod":"Dynamic","subnet":{"id":"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), variables('subnetName'))]"}}}]},"dependsOn":["[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"]},{"type":"Microsoft.Network/networkInterfaces","apiVersion":"2024-01-01","name":"[variables('nic2Name')]","location":"[parameters('location')]","properties":{"enableAcceleratedNetworking":false,"enableIPForwarding":false,"ipConfigurations":[{"name":"ipconfig1","properties":{"primary":true,"privateIPAddressVersion":"IPv4","privateIPAllocationMethod":"Dynamic","subnet":{"id":"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), variables('subnetName'))]"}}}]},"dependsOn":["[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"]},{"type":"Microsoft.Compute/virtualMachines","apiVersion":"2023-09-01","name":"[variables('vm1Name')]","location":"[parameters('location')]","properties":{"hardwareProfile":{"vmSize":"[parameters('vmSize')]"},"osProfile":{"computerName":"[variables('vm1Name')]","adminUsername":"[parameters('adminUsername')]","adminPassword":"[parameters('adminPassword')]"},"storageProfile":{"imageReference":{"publisher":"MicrosoftWindowsServer","offer":"WindowsServer","sku":"2022-datacenter-azure-edition","version":"latest"},"osDisk":{"createOption":"FromImage","deleteOption":"Delete","managedDisk":{"storageAccountType":"StandardSSD_LRS"}}},"networkProfile":{"networkInterfaces":[{"id":"[resourceId('Microsoft.Network/networkInterfaces', variables('nic1Name'))]","properties":{"primary":true,"deleteOption":"Delete"}}]},"diagnosticsProfile":{"bootDiagnostics":{"enabled":true}}},"dependsOn":["[resourceId('Microsoft.Network/networkInterfaces', variables('nic1Name'))]"]},{"type":"Microsoft.Compute/virtualMachines","apiVersion":"2023-09-01","name":"[variables('vm2Name')]","location":"[parameters('location')]","properties":{"hardwareProfile":{"vmSize":"[parameters('vmSize')]"},"osProfile":{"computerName":"[variables('vm2Name')]","adminUsername":"[parameters('adminUsername')]","adminPassword":"[parameters('adminPassword')]"},"storageProfile":{"imageReference":{"publisher":"MicrosoftWindowsServer","offer":"WindowsServer","sku":"2022-datacenter-azure-edition","version":"latest"},"osDisk":{"createOption":"FromImage","deleteOption":"Delete","managedDisk":{"storageAccountType":"StandardSSD_LRS"}}},"networkProfile":{"networkInterfaces":[{"id":"[resourceId('Microsoft.Network/networkInterfaces', variables('nic2Name'))]","properties":{"primary":true,"deleteOption":"Delete"}}]},"diagnosticsProfile":{"bootDiagnostics":{"enabled":true}}},"dependsOn":["[resourceId('Microsoft.Network/networkInterfaces', variables('nic2Name'))]"}]}],"outputs":{"backendOne":{"type":"string","value":"[variables('vm1Name')]"},"backendTwo":{"type":"string","value":"[variables('vm2Name')]"},"virtualNetwork":{"type":"string","value":"[variables('vnetName')]"}}}
What does this template create?
- The parameters collect the deployment location.
- The parameters also collect the administrator credentials.
- The virtual machine size starts as Standard_B2s.
- The private subnet disables default outbound access.
- The NAT Gateway supplies explicit outbound connectivity.
- The network security group permits inbound TCP traffic on port 80.
- Each private network interface connects one server to snet-backend.
- Select Save in the template editor.
- Select your Azure subscription on the deployment form.
- Select NextWork-LoadBalancer as the destination resource group.
The deployment form now targets the isolated resource group.
- Keep Standard_B2s as the virtual machine size when it is available.
- Enter a unique administrator username.
The form now shows your selected virtual machine size. It also shows the administrator username.
How is the password handled?
Azure masks the administrator password field. The secure template parameter prevents a literal password from being stored in azuredeploy.json.
- Enter an administrator password containing at least 12 characters.
- Change the virtual machine size parameter if Standard_B2s is unavailable in East US.
- Keep every resource name unchanged if you select a replacement size.
- Keep every networking value unchanged if you select a replacement size.
- Select Purchase to submit the template deployment.
Expect the deployment to take several minutes while Azure creates the network resources. Azure also creates both virtual machines during this time.
Deployment not completing?
If Azure reports that the selected virtual machine size is unavailable, choose another available size. Keep the resource names unchanged.
If a resource fails, inspect the failed deployment operation. Its details identify the resource Azure could not create.
Help me diagnose the failed deployment.
- Return to Resource groups after the deployment completes.
- Select NextWork-LoadBalancer.
- Confirm that vm-lb-1 is running.
- Confirm that vm-lb-2 is running.
- Confirm that vnet-lb-lab appears in the resource list.
- Confirm that nat-lb-lab appears in the resource list.
- Confirm that nsg-lb-web appears in the resource list.
That is the baseline in place. Both private servers now have the network foundation needed for their web pages.
✔️ Awesome, I've got everything!
Great. Your template deployment is complete inside NextWork-LoadBalancer.
ⓧ I'd like to double check the full code
Compare your complete azuredeploy.json with this reference:
{"$schema":"https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#","contentVersion":"1.0.0.0","parameters":{"location":{"type":"string","defaultValue":"[resourceGroup().location]"},"adminUsername":{"type":"string","defaultValue":"azureuser"},"adminPassword":{"type":"secureString","minLength":12},"vmSize":{"type":"string","defaultValue":"Standard_B2s"}},"variables":{"vnetName":"vnet-lb-lab","subnetName":"snet-backend","nsgName":"nsg-lb-web","natGatewayName":"nat-lb-lab","natPublicIpName":"pip-nat-lb","vm1Name":"vm-lb-1","vm2Name":"vm-lb-2","nic1Name":"nic-vm-lb-1","nic2Name":"nic-vm-lb-2"},"resources":[{"type":"Microsoft.Network/publicIPAddresses","apiVersion":"2024-01-01","name":"[variables('natPublicIpName')]","location":"[parameters('location')]","sku":{"name":"Standard"},"properties":{"publicIPAddressVersion":"IPv4","publicIPAllocationMethod":"Static","idleTimeoutInMinutes":4}},{"type":"Microsoft.Network/natGateways","apiVersion":"2024-01-01","name":"[variables('natGatewayName')]","location":"[parameters('location')]","sku":{"name":"Standard"},"properties":{"idleTimeoutInMinutes":4,"publicIpAddresses":[{"id":"[resourceId('Microsoft.Network/publicIPAddresses', variables('natPublicIpName'))]"}]},"dependsOn":["[resourceId('Microsoft.Network/publicIPAddresses', variables('natPublicIpName'))]"]},{"type":"Microsoft.Network/networkSecurityGroups","apiVersion":"2024-01-01","name":"[variables('nsgName')]","location":"[parameters('location')]","properties":{"securityRules":[{"name":"allow-http","properties":{"priority":100,"access":"Allow","direction":"Inbound","protocol":"Tcp","sourcePortRange":"*","destinationPortRange":"80","sourceAddressPrefix":"Internet","destinationAddressPrefix":"*"}}]}},{"type":"Microsoft.Network/virtualNetworks","apiVersion":"2024-01-01","name":"[variables('vnetName')]","location":"[parameters('location')]","properties":{"addressSpace":{"addressPrefixes":["10.40.0.0/16"]},"subnets":[{"name":"[variables('subnetName')]","properties":{"addressPrefix":"10.40.1.0/24","defaultOutboundAccess":false,"networkSecurityGroup":{"id":"[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))]"},"natGateway":{"id":"[resourceId('Microsoft.Network/natGateways', variables('natGatewayName'))]"}}}]},"dependsOn":["[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))]","[resourceId('Microsoft.Network/natGateways', variables('natGatewayName'))]"]},{"type":"Microsoft.Network/networkInterfaces","apiVersion":"2024-01-01","name":"[variables('nic1Name')]","location":"[parameters('location')]","properties":{"enableAcceleratedNetworking":false,"enableIPForwarding":false,"ipConfigurations":[{"name":"ipconfig1","properties":{"primary":true,"privateIPAddressVersion":"IPv4","privateIPAllocationMethod":"Dynamic","subnet":{"id":"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), variables('subnetName'))]"}}}]},"dependsOn":["[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"]},{"type":"Microsoft.Network/networkInterfaces","apiVersion":"2024-01-01","name":"[variables('nic2Name')]","location":"[parameters('location')]","properties":{"enableAcceleratedNetworking":false,"enableIPForwarding":false,"ipConfigurations":[{"name":"ipconfig1","properties":{"primary":true,"privateIPAddressVersion":"IPv4","privateIPAllocationMethod":"Dynamic","subnet":{"id":"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), variables('subnetName'))]"}}}]},"dependsOn":["[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"]},{"type":"Microsoft.Compute/virtualMachines","apiVersion":"2023-09-01","name":"[variables('vm1Name')]","location":"[parameters('location')]","properties":{"hardwareProfile":{"vmSize":"[parameters('vmSize')]"},"osProfile":{"computerName":"[variables('vm1Name')]","adminUsername":"[parameters('adminUsername')]","adminPassword":"[parameters('adminPassword')]"},"storageProfile":{"imageReference":{"publisher":"MicrosoftWindowsServer","offer":"WindowsServer","sku":"2022-datacenter-azure-edition","version":"latest"},"osDisk":{"createOption":"FromImage","deleteOption":"Delete","managedDisk":{"storageAccountType":"StandardSSD_LRS"}}},"networkProfile":{"networkInterfaces":[{"id":"[resourceId('Microsoft.Network/networkInterfaces', variables('nic1Name'))]","properties":{"primary":true,"deleteOption":"Delete"}}]},"diagnosticsProfile":{"bootDiagnostics":{"enabled":true}}},"dependsOn":["[resourceId('Microsoft.Network/networkInterfaces', variables('nic1Name'))]"]},{"type":"Microsoft.Compute/virtualMachines","apiVersion":"2023-09-01","name":"[variables('vm2Name')]","location":"[parameters('location')]","properties":{"hardwareProfile":{"vmSize":"[parameters('vmSize')]"},"osProfile":{"computerName":"[variables('vm2Name')]","adminUsername":"[parameters('adminUsername')]","adminPassword":"[parameters('adminPassword')]"},"storageProfile":{"imageReference":{"publisher":"MicrosoftWindowsServer","offer":"WindowsServer","sku":"2022-datacenter-azure-edition","version":"latest"},"osDisk":{"createOption":"FromImage","deleteOption":"Delete","managedDisk":{"storageAccountType":"StandardSSD_LRS"}}},"networkProfile":{"networkInterfaces":[{"id":"[resourceId('Microsoft.Network/networkInterfaces', variables('nic2Name'))]","properties":{"primary":true,"deleteOption":"Delete"}}]},"diagnosticsProfile":{"bootDiagnostics":{"enabled":true}}},"dependsOn":["[resourceId('Microsoft.Network/networkInterfaces', variables('nic2Name'))]"}]}],"outputs":{"backendOne":{"type":"string","value":"[variables('vm1Name')]"},"backendTwo":{"type":"string","value":"[variables('vm2Name')]"},"virtualNetwork":{"type":"string","value":"[variables('vnetName')]"}}}
Configure and check both web servers
Each private server needs a web page that identifies the machine handling a request. The hostname makes traffic distribution visible in later steps.
- Copy install-iis.ps1 for use with both virtual machines:
# Install IIS server role
Install-WindowsFeature -name Web-Server -IncludeManagementTools
# Remove default htm file
Remove-Item C:\inetpub\wwwroot\iisstart.htm
# Add a new htm file that displays server name
Add-Content -Path "C:\inetpub\wwwroot\iisstart.htm" -Value $("Hello World from " + $env:computername)
# Verify the local website
Invoke-RestMethod -Uri "http://localhost" -UseBasicParsing
What does this script do?
- The first section installs the IIS server role.
- The second section removes the default IIS page.
- The third section creates a page containing the server hostname.
- The final section requests the local page to confirm that IIS responds.
- Return to the NextWork-LoadBalancer resource group.
- Select vm-lb-1.
- Select Run command under Operations.
- Select RunPowerShellScript.
- Paste install-iis.ps1 into the script field.
Before you select Run, what hostname do you expect the local request to return?
- Select Run.
The completed output ends with Hello World from VM-LB-1. That proves the first private web server is responding.
- Return to the NextWork-LoadBalancer resource group.
- Select vm-lb-2.
- Select Run command under Operations.
- Select RunPowerShellScript.
- Paste install-iis.ps1 into the script field.
Before you select Run, do you expect this server to return the same hostname as the first one?
- Select Run.
The completed output ends with Hello World from VM-LB-2. You now have two distinct private web backends.
Run Command not returning the hostname?
Confirm that the selected virtual machine is running. Its VM agent must also be ready.
Confirm that nat-lb-lab is connected to snet-backend. Run Command needs outbound connectivity on port 443 to return results.
Help me troubleshoot the IIS Run Command result.
✔️ Awesome, I've got everything!
Excellent. Both Run Command results show a working hostname page.
ⓧ I'd like to double check the full code
Compare your complete install-iis.ps1 with this reference:
# Install IIS server role
Install-WindowsFeature -name Web-Server -IncludeManagementTools
# Remove default htm file
Remove-Item C:\inetpub\wwwroot\iisstart.htm
# Add a new htm file that displays server name
Add-Content -Path "C:\inetpub\wwwroot\iisstart.htm" -Value $("Hello World from " + $env:computername)
# Verify the local website
Invoke-RestMethod -Uri "http://localhost" -UseBasicParsing
- Press the Windows key to open the Start menu.
- Type Windows PowerShell.
- Select Windows PowerShell from the search results.
You will see a local PowerShell prompt. The later public endpoint test needs no Azure sign-in or additional modules.
Your two private IIS servers are running inside an isolated Azure network. Next up, you will place the first backend behind one public load balancer address.
Publish the First Backend
Your two private IIS servers can now return their hostname pages inside Azure. They still have no shared public address for visitors.
A public Azure Load Balancer gives the service one stable entry point. Its public frontend IP routes the first healthy flow to vm-lb-1.
In this step, get ready to:
- Create a Standard public load balancer with a public frontend.
- Connect vm-lb-1 through a NIC-based backend pool.
- Publish TCP port 80 through a healthy HTTP rule.
Create the public load balancer
A public load balancer maps an internet-facing address to private servers in a backend pool. The Standard SKU supports this public traffic path.
Plan for Azure Charges
This step creates a Standard public IPv4 address. The configured load-balancing rule can also incur hourly charges.
The cleanup section removes the dedicated resource group when you finish. That deletion stops charges from these project resources.
- Use the Azure portal search bar to search for Load balancers.
- Select Load balancers from the search results.
You should see the Load balancers page with controls for creating a resource.
- Select + Create.
- Select Standard Load Balancer.
The creation form should now show the settings for a Standard Load Balancer.
- Set Resource group to NextWork-LoadBalancer.
- Enter lb-nextwork-public in the Name field.
The form now targets the isolated resource group from the previous step.
- Set Region to East US.
- Set SKU to Standard.
The load balancer now matches the region of your virtual network. Its SKU supports the Standard public IP required for the frontend.
- Set Type to Public.
- Set Tier to Regional.
The basic settings now describe a public regional load balancer in East US.
- Select Frontend IP configuration in the creation form.
- Start a new frontend configuration.
The frontend form defines the public address that visitors use.
- Enter fe-public as the frontend name.
- Set the IP version to IPv4.
The frontend is now identified as fe-public with an IPv4 address.
- Choose IP address for the frontend.
- Create a new public IP named pip-lb-public.
The frontend form should now reference the new Standard public IP resource.
- Set the routing preference to Microsoft Network.
- Select Zone-redundant if the availability field is offered.
If the availability field is absent, the displayed regional default remains in place.
- Add the fe-public frontend configuration.
You should see fe-public listed with pip-lb-public in the frontend section.
- Select Backend pools in the creation form.
- Start a new backend pool configuration.
This pool connects the public frontend to a private server.
- Enter pool-web as the backend pool name.
- Select vnet-lb-lab as the virtual network.
The pool now belongs to the virtual network that contains both web servers.
- Choose NIC as the backend pool configuration.
- Select + Add under IP configurations.
The IP configuration picker should now list the eligible VM interfaces from vnet-lb-lab.
- Choose vm-lb-1.
- Select Add.
The backend table should show vm-lb-1 through nic-vm-lb-1.
- Select Save to keep the backend pool.
Why Use a NIC-Based Pool?
A NIC-based backend pool follows each VM's network interface. This matches the private VM topology you deployed.
This configuration also keeps the flow distribution view available in Azure Monitor.
- Continue to the final review page.
- Select Create.
Expect Azure to take a few minutes to finish the deployment. The new load balancer becomes available after the deployment completes.
- Return to the NextWork-LoadBalancer resource group.
You should see lb-nextwork-public with pip-lb-public in the resource list.
Add the HTTP rule and prove it works
An HTTP health probe checks whether the website returns a successful response on port 80.
The Layer 4 rule maps public TCP traffic to the same port on a healthy backend.
- Open lb-nextwork-public from the resource group.
- Select Load balancing rules from the load balancer menu.
The rules page should show controls for publishing traffic through the frontend.
- Start a new load-balancing rule.
- Enter rule-http as the rule name.
The rule form now identifies the HTTP traffic path you are creating.
- Set the IP version to IPv4.
- Select fe-public as the frontend.
The rule now begins at the public IPv4 frontend.
- Select pool-web as the backend pool.
- Set Protocol to TCP.
The form now links the public frontend to the private backend pool using TCP.
- Set the frontend port to 80.
- Set the backend port to 80.
Traffic arriving on public port 80 now maps to port 80 on the selected backend.
- Start a new probe from the Health probe setting.
- Enter probe-http as the probe name.
The probe is now associated with the rule you are configuring.
- Set the probe protocol to HTTP.
- Set the probe port to 80.
The probe now checks the IIS website through HTTP on port 80.
- Leave the remaining probe values at their displayed defaults.
How the Probe Protects Traffic
An HTTP probe marks the endpoint healthy when it returns status 200.
The load balancer sends new connections to backends that pass this check. This keeps failed web servers away from new traffic.
- Add probe-http to the rule.
- Set session persistence to None.
The rule can now evaluate backend health without pinning traffic to a client session.
- Enable TCP reset.
- Disable Floating IP.
The remaining rule settings now match the public web traffic path.
The snet-backend subnet already uses Azure NAT Gateway for outbound connectivity. The load balancer can keep its recommended outbound-rules setting.
- Keep outbound SNAT on the recommended outbound-rules setting.
- Select Save to create rule-http.
Health information can take a few minutes to appear after the rule is created. The probe is checking the IIS endpoint during this wait.
Before you check, which backend do you expect the rule to report as healthy?
- Select Load balancing rules from the lb-nextwork-public menu.
- Select View details under Health status for rule-http.
The health window may need a few minutes to populate. That delay is expected while Azure collects probe results.
- Wait for the backend health information to appear.
- Select Refresh.
You should see vm-lb-1 marked Up.
- Return to the NextWork-LoadBalancer resource group.
- Open pip-lb-public.
The public IP resource page displays the address assigned to your load balancer.
- Record the IP address here: your-load-balancer-public-ip.
Before you browse, which server name should appear when the pool contains only vm-lb-1?
- Open a new browser tab.
- Paste your-load-balancer-public-ip into the address bar.
- Press Enter.
You should see Hello World from VM-LB-1 on the page.
That is your first public success. One stable address now reaches the private IIS site on vm-lb-1.
Backend Still Down?
- Wait a few minutes for Azure to collect another health probe result.
- Select Refresh in the health status window.
- Confirm that pool-web contains only vm-lb-1.
- Confirm that nsg-lb-web allows inbound TCP traffic on port 80.
Help me diagnose why vm-lb-1 is not healthy behind my Azure Load Balancer.
Your public endpoint is live with one healthy backend. Next, you will add vm-lb-2 to distribute new connections across both servers.
Distribute New Connections Across Two Backends
Your Azure Load Balancer already publishes a healthy web endpoint through vm-lb-1.
The backend pool still has one server. This prevents traffic distribution.
Adding vm-lb-2 to the NIC-based backend pool gives new connections a second healthy destination. A Windows PowerShell test makes that distribution visible.
In this step, get ready to:
- Add vm-lb-2 to pool-web.
- Confirm both backend servers are healthy.
- Generate new HTTP flows that reveal both server identities.
Add the second backend
A server receives new load-balanced traffic once its NIC IP configuration belongs to pool-web. You will add the existing configuration for vm-lb-2.
- Return to lb-nextwork-public in the Azure portal from earlier.
- Select Backend pools from the resource menu.
- Select pool-web.
- Under IP configurations, select + Add.
- Choose vm-lb-2 with ipconfig1.
- Select Add.
- Select Save.
The pool now lists nic-vm-lb-1/ipconfig1. It also lists nic-vm-lb-2/ipconfig1.
Good progress. Your public endpoint now has two possible destinations.
Backend health may take a few minutes to update. The probe is checking whether each server responds successfully on port 80.
- Select Load balancing rules from the resource menu.
- Under the Health status column for rule-http, select View details.
- Select Refresh until both backend instances report their current health.
You should see vm-lb-1 marked Up. You should also see vm-lb-2 marked Up.
Second Backend Not Up?
- Confirm vm-lb-2 is still running.
- Check that pool-web includes nic-vm-lb-2/ipconfig1.
- Select Refresh again after the health probe has had time to check the server.
Help me diagnose why vm-lb-2 is not becoming healthy in my Azure Load Balancer backend pool.
Generate distinct flows from Windows
With session persistence set to None, Azure Load Balancer uses a five-tuple hash to select a backend for each TCP flow. The hash uses the source IP, source port, destination IP, destination port, and protocol.
The test disables HTTP keep-alive to create nonpersistent requests. Fresh source ports give the hash more opportunities to select both servers.
- Return to pip-lb-public in NextWork-LoadBalancer.
- Record the displayed IP address here: your-load-balancer-public-ip.
- Use a text editor on your Windows device to create test-load-balancer.ps1 on your Desktop.
- Add the traffic test to test-load-balancer.ps1 by pasting this code:
$loadBalancerIp = "your-load-balancer-public-ip"
1..20 | ForEach-Object {
Invoke-RestMethod -Uri "http://$loadBalancerIp/?request=$_" -UseBasicParsing -DisableKeepAlive
}
What Does This Code Do?
- The $loadBalancerIp variable stores the public address for your load balancer.
- The 1..20 pipeline sends twenty values through the request loop.
- The $_ value gives each request a different query value.
- The -DisableKeepAlive parameter turns off HTTP keep-alive. This encourages each request to use a new transport session.
- Replace your-load-balancer-public-ip in the script with your-load-balancer-public-ip.
- Save test-load-balancer.ps1 on your Desktop.
✔️ Awesome, I've got everything!
Your saved script contains the public IP assigned to pip-lb-public.
ⓧ I'd like to double check the full code
Compare your file with this reference. The public IP line in your saved copy contains the value you recorded.
$loadBalancerIp = "your-load-balancer-public-ip"
1..20 | ForEach-Object {
Invoke-RestMethod -Uri "http://$loadBalancerIp/?request=$_" -UseBasicParsing -DisableKeepAlive
}
Before you run the test, do you think twenty fresh flows can reach both healthy backends?
- Run test-load-balancer.ps1 from your Desktop in Windows PowerShell.
You should see Hello World from VM-LB-1 in the output. You should also see Hello World from VM-LB-2 in the output.
Why Can the Order Be Uneven?
Each new flow can select either healthy backend through the five-tuple hash. A sample of twenty flows can contain uneven server counts.
Both hostnames appearing proves that the public endpoint can distribute fresh connections across the two-server pool.
Only Seeing One Server?
- Run test-load-balancer.ps1 again to generate another short sample.
- Confirm both backend instances remain Up in the health details for rule-http.
- Check that -DisableKeepAlive remains at the end of Invoke-RestMethod.
Help me troubleshoot why my PowerShell load balancer test only shows one backend server.
You have your proof. One public endpoint now reaches both healthy servers across fresh connections.
Your two-backend service is healthy and distributing new flows. Next, you will stop one web server to test how the health probe protects the endpoint.
Trigger and Diagnose a Backend Failure
Your Azure Load Balancer now sends new flows to both healthy web servers. Successful distribution does not prove that unhealthy servers are excluded.
You will intentionally stop the IIS site on vm-lb-1. The HTTP health probe will reveal whether new flows stay on vm-lb-2.
In this step, get ready to:
- Stop the Default Web Site on vm-lb-1.
- Confirm probe-http marks vm-lb-1 as Down.
- Rerun the Windows PowerShell traffic test against the remaining healthy backend.
Stop one backend application
Stopping the website creates a controlled application failure. The VM itself stays running.
- Return to vm-lb-1 in the Azure portal from earlier.
- Expand Operations in the left navigation.
- Select Run command.
- Choose RunPowerShellScript.
- Stop the website by pasting this script into the script box:
Stop-Website -Name "Default Web Site"
What Does This Script Do?
- The Stop-Website command stops an IIS website by name.
- The -Name "Default Web Site" argument targets the website serving the hostname page on vm-lb-1.
- The command leaves the virtual machine running.
Before you select Run, do you think this action stops the entire VM or only its website?
- Select Run.
- Wait for the Run Command operation to finish.
You will see the Run Command operation finish. The IIS website is now stopped.
The vm-lb-1 virtual machine remains running.
Run Command Did Not Finish?
- Confirm vm-lb-1 still shows as running in the Azure portal.
- Confirm nat-lb-lab remains attached to snet-backend.
Run Command needs outbound connectivity on port 443 to return its result.
Help me diagnose why Run Command is not completing on vm-lb-1.
✔️ Awesome, I've got everything!
Great. The Default Web Site on vm-lb-1 is stopped while the VM remains running.
ⓧ I'd like to double check the full code
Here is the complete stop-iis.ps1:
Stop-Website -Name "Default Web Site"
Diagnose the degraded pool
The HTTP health probe counts a backend as healthy only when port 80 returns status 200. Once vm-lb-1 fails that check, new TCP connections use vm-lb-2.
- Return to lb-nextwork-public in the Azure portal.
- Select Load balancing rules in the left navigation.
- Find rule-http in the rules table.
- Select View details under Health status.
This health view can take a few minutes to catch up. A short wait is expected while the probe collects fresh results.
- Select Refresh until vm-lb-1 changes to Down.
- Check that vm-lb-2 remains Up.
You have now caught the failure in Azure's health view. The pool still has one healthy path for new traffic.
Before you rerun the test, do you expect any new response to come from vm-lb-1?
- Return to the Windows PowerShell window from earlier.
- Run test-load-balancer.ps1 again to generate new HTTP flows.
Each new response should read Hello World from VM-LB-2. No new flow should return the hostname page from vm-lb-1.
Why Does the Endpoint Stay Available?
Azure Load Balancer sends new TCP connections only to healthy endpoints after one backend probes down. The public endpoint stays available through vm-lb-2.
Still Seeing the First Backend?
- Wait until the health details show vm-lb-1 as Down before rerunning the traffic test.
- Confirm test-load-balancer.ps1 still contains -DisableKeepAlive.
- Confirm vm-lb-2 remains Up if every request fails.
Help me diagnose unexpected traffic after vm-lb-1 became unhealthy.
Your load balancer has isolated an unhealthy backend without losing the public endpoint. Next up, you will return the service to two healthy backends.
Recover the Failed Backend
Your Azure Load Balancer kept the public endpoint available through vm-lb-2 after the IIS site on vm-lb-1 stopped.
The service now has one healthy backend. Restarting the site lets the health probe return the pool to its intended two-server state.
In this step, get ready to:
- Restart the Default Web Site on vm-lb-1.
- Confirm both backends are Up in the rule-http health status.
- Prove that new connections can reach both servers with Windows PowerShell.
Restart the IIS site
The virtual machine is still running. Only its Default Web Site needs to return to service.
- Return to the Azure portal from earlier.
- Select Resource groups from the portal menu.
- Select NextWork-LoadBalancer.
- Select vm-lb-1.
- Under Operations, select Run command.
- Select RunPowerShellScript.
- Paste start-iis.ps1 into the script editor:
Start-Website -Name "Default Web Site"
What Does This Script Do?
The Start-Website command starts the existing Default Web Site on vm-lb-1.
Starting the site restores the HTTP response that probe-http checks on port 80.
- Select Run in the Run Command page.
- Wait for the command to finish.
The command should finish without a website-start failure. Azure has now sent the restart instruction to vm-lb-1.
Start Command Not Completing?
- Confirm that the selected virtual machine is vm-lb-1.
- Confirm that the virtual machine itself is still running before retrying the command.
- Check that your script matches the code block above exactly.
Help me diagnose why the IIS website restart is failing on vm-lb-1.
✔️ Awesome, I've got everything!
Your restart script matches the recovery action. Continue to the health check once the command finishes.
ⓧ I'd like to double check the full code
Compare the script you pasted with the complete start-iis.ps1 file:
Start-Website -Name "Default Web Site"
Confirm full recovery
The HTTP health probe must see the restored site respond before the load balancer sends it new connections. The portal can take a few minutes to reflect that recovery.
- Return to lb-nextwork-public in the Azure portal.
- Select Load balancing rules from the resource menu.
- Find rule-http.
- Select View details under Health status.
- Select Refresh periodically until both backend instances show Up.
You should see vm-lb-1 and vm-lb-2 marked Up. That restores the pool's two healthy backends.
Before you rerun the traffic test, consider whether new flows will stay on vm-lb-2 or reach both backends.
- Switch back to Windows PowerShell from earlier.
- Press the Up Arrow key until the command that runs test-load-balancer.ps1 appears.
- Press Enter to rerun the script.
You should see both Hello World from VM-LB-1 and Hello World from VM-LB-2 across the new flows. Your public endpoint is distributing connections across two healthy backends again.
Why Is the Output Uneven?
Azure Load Balancer uses a five-tuple hash for each new flow. A short sample can favor one server while still reaching both.
Secret mission
Graph the Failure and Recovery
Turn your backend failure test into a time-based health record. Use Azure Monitor to capture the drop in vm-lb-1 probe health and its recovery after the IIS site restarts.
Clean Up Your Resources
Clean Up Your Resources
Your dedicated Microsoft Azure environment is still live after the failure and recovery test. Decide whether to keep it available, pause its two VMs, or delete its resource group because charges can continue while resources remain deployed.
Cost warning
Running vm-lb-1 and vm-lb-2 can accrue compute charges. The Standard SSD OS disks can also continue generating charges.
The NAT Gateway, public IPv4 addresses, configured Load Balancer rule, processed data, and bandwidth can add further charges. Deleting NextWork-LoadBalancer removes the billable resources dedicated to this lab.
Resources you used:
- Resource group NextWork-LoadBalancer in East US.
- Azure Virtual Machines vm-lb-1 and vm-lb-2 with two Standard SSD OS disks.
- Virtual network vnet-lb-lab with subnet snet-backend.
- Network security group nsg-lb-web.
- Azure NAT Gateway nat-lb-lab with public IP pip-nat-lb.
- Network interfaces nic-vm-lb-1 and nic-vm-lb-2.
- Azure Load Balancer lb-nextwork-public with frontend fe-public and public IP pip-lb-public.
- Backend pool pool-web with HTTP health probe probe-http and load-balancing rule rule-http.
Keep everything running
Keeping the environment live requires no cleanup action. Use this option while you are actively demonstrating the public endpoint or reviewing the monitoring evidence.
- Your public endpoint remains ready to distribute new HTTP flows across both healthy backend servers.
- Your captured Azure Monitor graph remains available as evidence of the health drop and recovery.
- All active resources remain deployed and can continue accruing charges.
Pause - I'll come back to this later
Pausing the two virtual machines stops their compute instance usage while preserving the environment for later. The disks and networking resources can continue generating charges.
- Use the Azure portal search bar to return to vm-lb-1.
- Select Stop on the VM page.
- Confirm the stop action when Azure asks.
- Use the Azure portal search bar to return to vm-lb-2.
- Select Stop on the VM page.
- Confirm the stop action when Azure asks.
- Wait for both deallocation operations to finish.
You should see Stopped (deallocated) on both VM pages. You have now paused the compute portion of the lab while keeping its configuration intact.
Delete - I don't want to use this again
Deleting the dedicated resource group is irreversible. Its clean Part 8 boundary keeps this action focused on the resources you created here.
- Return to Resource groups in the Azure portal tab from earlier.
- Select NextWork-LoadBalancer.
- Select Delete resource group.
- Type NextWork-LoadBalancer when Azure requests the resource-group name.
- Confirm the deletion.
- Wait for the resource-group deletion to finish.
- Return to Resource groups.
- Confirm that NextWork-LoadBalancer no longer appears.
That clears the dedicated Part 8 environment in one action. Azure removes its resources in dependency order.
Protect Older Resources
The older NextWork-Networking group may contain resources outside this lab. Leave it unchanged unless every resource inside is familiar and disposable.
- Select NextWork-Networking if it appears in Resource groups.
- Review every resource listed inside it.
- Leave NextWork-Networking unchanged if any resource is unfamiliar.
- Select Delete resource group only if every listed resource is disposable.
- Type NextWork-Networking if Azure requests confirmation.
- Confirm the deletion only after completing your review.
- Confirm that NextWork-Networking no longer appears if you chose to delete it.
Nice Work!
Nice Work!
Excellent work! You completed Part 8 with a public Azure Load Balancer distributing new TCP flows across two private web servers.
You've learned how to:
- Built a public endpoint that sends traffic to two private IIS servers. Its NIC-based backend pool uses an HTTP health probe with a TCP port 80 rule.
- Used Windows PowerShell to generate new TCP flows. The output demonstrated five-tuple distribution across both healthy backends.
- Triggered an IIS failure on vm-lb-1. Health status changed to Down, so new connections reached only vm-lb-2. Restarting the site restored both backends to Up.
- Secret Mission: graphed the backend health drop and recovery in Azure Monitor. The timeline connected the probe signal to the traffic behavior during the outage.
Ready to quiz yourself?