A Web App Needs Traffic Distribution? Create a Load Balancer

Published on:

A single web server becomes a bottleneck and a single point of failure. If it is busy or unavailable, the CloudTrips website cannot serve new requests.

Create a public Azure Load Balancer with two private backend VMs. Users send traffic to one public frontend IP, and the load balancer selects a backend for each new network flow.

Internet
   |
Public frontend IP
   |
Azure Load Balancer
   |-------------------|
Private WEB01       Private WEB02

Azure Load Balancer operates at Layer 4, the Transport layer of the OSI networking model. At this layer, it distributes connections using source and destination IP addresses, the TCP or UDP protocol, and port numbers. It does not read application content such as HTTP paths or headers. A later Application Gateway trip introduces that Layer 7 behavior.

This trip uses the network foundation from App Needs a Private Network? Create a VNet. Keep vnet-cloudtrips-test-weu, snet-app, and nsg-cloudtrips-app-test-weu. The Azure Firewall lab is not required and should already be removed.

The load balancer, public IP, VMs, disks, and outbound data can incur charges. Complete this trip and the following health-probe trip together, and deallocate or delete the VMs when finished.

Allow HTTP to the Backend Subnet

The existing subnet NSG permits HTTPS only to members of the CloudTrips application security group. This lab serves a simple HTTP page on TCP port 80, so add a separate rule.

Open nsg-cloudtrips-app-test-weu, select Settings > Inbound security rules, and select Add. Configure:

Source: Service Tag
Source service tag: Internet
Source port ranges: *
Destination: IP Addresses
Destination IP addresses/CIDR ranges: 10.20.1.0/24
Service: HTTP
Action: Allow
Priority: 110
Name: Allow-HTTP-LoadBalancer
Description: Allow public HTTP traffic distributed by the test load balancer

Select Add.

Keep the existing Allow-HTTPS-Internet rule. The two custom rules serve different ports:

Allow-HTTPS-Internet:      Internet -> application ASG -> TCP 443
Allow-HTTP-LoadBalancer:   Internet -> snet-app       -> TCP 80

For an HTTP request on port 80, the priority-100 HTTPS rule does not match because its port is 443. Evaluation continues to the priority-110 HTTP rule, which allows the request. The current Load Balancer uses port 80, so it does not use the HTTPS rule.

The two VMs will have no public IP addresses, so the load balancer frontend is their public entry point. Azure Load Balancer preserves the client’s source IP for inbound traffic; the NSG therefore needs to allow the Internet source.

Azure Load Balancer also sends small test connections to each backend to check whether its service port is available. These health probes come from the Azure-managed AzureLoadBalancer service tag, not from the public user. Every NSG includes a default inbound rule named AllowAzureLoadBalancerInBound that allows this platform traffic. Therefore, the custom HTTP rule permits real client requests, while the default rule separately permits Azure’s health checks. A custom deny rule with a lower priority number could still override the default probe rule and make healthy backends appear unavailable.

Create the Public Load Balancer

In the Azure portal, search for Load balancers. Select Create > Standard Load Balancer.

On Basics, configure:

Subscription: CloudTrips TEST
Resource group: rg-cloudtrips-network-test-weu
Name: lb-cloudtrips-web-test-weu
Region: West Europe
SKU: Standard
Type: Public
Tier: Regional

Basic Load Balancer has been retired. Standard is the supported SKU and also supports later zone-redundant and cross-region designs.

On Frontend IP configuration, add:

Name: fe-cloudtrips-web-test-weu
IP version: IPv4
IP type: IP address
Public IP address: Create new
Public IP name: pip-cloudtrips-lb-test-weu
SKU: Standard
Availability zone: Zone-redundant
Routing preference: Microsoft network

If the region does not show an availability-zone field, continue without it.

On Backend pools, add:

Name: be-cloudtrips-web-test-weu
Virtual network: vnet-cloudtrips-test-weu
Backend pool configuration: NIC

The pool is empty for now. The two VM network interfaces will join it after the load balancer exists.

Create the Initial Load-Balancing Rule

A load-balancing rule connects the public frontend port to the backend port. It also requires a health probe so the load balancer does not send new flows to a backend whose port is closed.

On Inbound rules, select Add a load balancing rule and configure:

Name: rule-http-80
IP version: IPv4
Frontend IP address: fe-cloudtrips-web-test-weu
Backend pool: be-cloudtrips-web-test-weu
Protocol: TCP
Port: 80
Backend port: 80
Health probe: Create new
Health probe name: probe-tcp-80
Health probe protocol: TCP
Health probe port: 80
Session persistence: None
Idle timeout: 15 minutes
TCP reset: Enabled
Floating IP: Disabled
Outbound SNAT: Use outbound rules to provide backend pool members internet access

Do not create an outbound rule for this lab. The web servers use software already included in Ubuntu and do not need to download packages. If a later workload requires outbound internet access, configure an explicit NAT Gateway or Load Balancer outbound rule rather than relying on default outbound access.

This trip uses the smallest useful TCP probe because a load-balancing rule needs backend health information. The next trip replaces it with an application-aware HTTP health check and proves failover behavior.

On Tags, add:

Application: CloudTrips
Environment: TEST
Purpose: WebTrafficDistribution

Select Review + create, and then select Create. Open the deployed load balancer and record its frontend public IP address.

Azure Load Balancer overview showing the CloudTrips Standard regional load balancer and public frontend IP

The public IP identifies the service. It does not belong to either backend VM. The load balancer can therefore keep the same frontend while backend instances are added, removed, or replaced.

Create Two Private Web VMs

Create the first VM in the Azure portal:

Resource group: rg-cloudtrips-network-test-weu
Name: vm-cloudtrips-web01-test-weu
Region: West Europe
Availability options: No infrastructure redundancy required
Security type: Standard
Image: Ubuntu Server 24.04 LTS - x64 Gen2
Size: Standard_D2s_v3
Authentication type: Password or SSH public key
Username: azureuser
Public inbound ports: None

Use a Standard SSD OS disk and enable deletion of the disk with the VM. On Networking, configure:

Virtual network: vnet-cloudtrips-test-weu
Subnet: snet-app
Public IP: None
NIC network security group: None
Delete NIC when VM is deleted: Enabled
Load balancing options: Azure load balancer
Load balancer: lb-cloudtrips-web-test-weu
Backend pool: be-cloudtrips-web-test-weu

None for the NIC NSG is intentional because snet-app already has nsg-cloudtrips-app-test-weu. Add the tags Application: CloudTrips, Environment: TEST, and Purpose: LoadBalancerBackend, and create the VM.

Repeat the same configuration for:

Name: vm-cloudtrips-web02-test-weu

This lab uses two ordinary VMs to make distribution visible. A production design would normally place instances across availability zones or use a Virtual Machine Scale Set.

Azure Virtual Machines page showing the two running private CloudTrips web backend VMs

Start a Different Test Page on Each VM

Open vm-cloudtrips-web01-test-weu, select Operations > Run command > RunShellScript, and run:

sudo mkdir -p /opt/cloudtrips-web
printf '%s\n' 'CloudTrips response from WEB01' | sudo tee /opt/cloudtrips-web/index.html >/dev/null
printf '%s\n' \
  '[Unit]' \
  'Description=CloudTrips test web server' \
  'After=network.target' \
  '' \
  '[Service]' \
  'ExecStart=/usr/bin/python3 -m http.server 80 --bind 0.0.0.0 --directory /opt/cloudtrips-web' \
  'Restart=always' \
  '' \
  '[Install]' \
  'WantedBy=multi-user.target' \
  | sudo tee /etc/systemd/system/cloudtrips-web.service >/dev/null
sudo systemctl daemon-reload
sudo systemctl enable --now cloudtrips-web
curl --silent http://127.0.0.1

The final line should return:

CloudTrips response from WEB01

On vm-cloudtrips-web02-test-weu, run the same script but replace both occurrences of WEB01 with WEB02. Its local test should return:

CloudTrips response from WEB02

The permanent systemd service restarts the small Python web server when its VM restarts. It is suitable for this lab, not as a production web server.

Verify the Backend Pool and Rule

Open lb-cloudtrips-web-test-weu. Under Settings > Backend pools, open be-cloudtrips-web-test-weu and confirm that both VM NIC IP configurations are present.

Then open Load balancing rules and confirm:

rule-http-80: frontend TCP 80 -> backend TCP 80
Backend pool: be-cloudtrips-web-test-weu
Health probe: probe-tcp-80

CloudTrips Load Balancer backend pool showing both private web VM network interfaces

The backend pool answers which servers can receive traffic. The rule answers which frontend traffic is distributed and to which backend port. The probe answers which pool members are currently eligible.

Test Traffic Distribution

From a terminal on your Mac, replace the placeholder with the load balancer’s frontend public IP and run:

for request in {1..10}; do
  curl --connect-timeout 5 http://<load-balancer-public-ip>
done

The output should contain responses from both backends:

CloudTrips response from WEB01
CloudTrips response from WEB02

Local terminal showing requests to one Load Balancer public IP reaching both CloudTrips backend VMs

Azure Load Balancer hashes each network flow; it does not promise strict WEB01, WEB02, WEB01 alternation. Several consecutive requests can reach the same VM. Seeing both names across multiple new connections proves that the one frontend distributes traffic to both private backends.

Continue or Clean Up

Continue with A Load Balancer Needs a Health Check? Configure a Health Probe. Keep the load balancer, public IP, TCP probe, rule, backend VMs, NICs, disks, and HTTP NSG rule. Deallocate the VMs only if you will not continue immediately.

If you stop here, delete the two VMs with their NICs and disks, then delete lb-cloudtrips-web-test-weu, pip-cloudtrips-lb-test-weu, and the Allow-HTTP-LoadBalancer NSG rule. Keep the VNet, subnets, NSG, ASG, and route table for later networking trips.