Azure Load Balancer is the least glamorous service in the networking stack and one of the most misunderstood. It is not a proxy. Nothing terminates on it, no TLS, no connection handling, no data path hop you can see in a traceroute. It is a distribution function programmed into the Azure fabric: flows arrive, a five tuple hash picks a healthy backend, and packets are rewritten in transit. Understanding that model explains every behavior people find surprising.
Consequences of Not Being a Proxy
Because the LB is transparent, backends see the original client IP, no X-Forwarded-For needed. Because distribution is per flow via five tuple hash, a single client with one long lived connection stays on one backend forever, and session distribution is only as good as your clients’ connection diversity. Session persistence modes (two tuple, three tuple) reduce the hash inputs when you need a client to consistently land on the same backend, at the cost of distribution evenness. And because there is no proxy, the LB adds essentially zero latency and scales to millions of flows, which is why it fronts everything from AKS services to SQL availability group listeners.
resource "azurerm_lb" "internal" {
name = "lbi-app-prod"
location = "westeurope"
resource_group_name = azurerm_resource_group.app.name
sku = "Standard"
frontend_ip_configuration {
name = "fe-app"
subnet_id = azurerm_subnet.app.id
private_ip_address_allocation = "Static"
private_ip_address = "10.20.4.100"
zones = ["1", "2", "3"]
}
}
resource "azurerm_lb_backend_address_pool" "app" {
name = "be-app"
loadbalancer_id = azurerm_lb.internal.id
}
resource "azurerm_lb_probe" "health" {
name = "probe-health"
loadbalancer_id = azurerm_lb.internal.id
protocol = "Http"
port = 8080
request_path = "/healthz"
interval_in_seconds = 5
probe_threshold = 2
}
resource "azurerm_lb_rule" "app" {
name = "rule-app"
loadbalancer_id = azurerm_lb.internal.id
frontend_ip_configuration_name = "fe-app"
backend_address_pool_ids = [azurerm_lb_backend_address_pool.app.id]
probe_id = azurerm_lb_probe.health.id
protocol = "Tcp"
frontend_port = 443
backend_port = 8443
disable_outbound_snat = true
idle_timeout_in_minutes = 15
}
Always Standard SKU (Basic is retired), always zone redundant frontends, and disable_outbound_snat true on rules so outbound goes through your deliberate egress path from two posts ago instead of implicitly through the LB frontend.
Health Probes Decide Everything
A backend receives traffic if and only if its probe passes, so probe design is availability design. HTTP probes beat TCP probes because a listening socket proves nothing about a deadlocked application. The probe endpoint should check the process can serve, but be careful wiring deep dependency checks into it: if every instance probes the same failed database, the whole pool goes down simultaneously and the LB has nowhere to send traffic, converting a degraded state into a total outage. My rule: LB probes verify the instance, monitoring verifies the dependencies. Know the probe down behavior too: existing TCP flows are not terminated when a probe fails, traffic just stops being sent new flows, so pair probes with graceful shutdown handling in the app for clean deploys.
HA Ports: The NVA Pattern
Internal load balancers support HA ports: a rule with frontend and backend port zero that balances all TCP and UDP traffic on all ports. This exists for one dominant scenario, clustering network virtual appliances. Route tables point at the ILB frontend as next hop, and the LB spreads flows across a pool of firewall or router VMs, with probes ejecting failed nodes in seconds. Symmetry matters for stateful appliances: the five tuple hash sends both directions of a flow to the same appliance as long as the route design is symmetric, so audit your UDRs when you see one way traffic mysteries. If you run third party firewalls in Azure, this pattern is their standard active active deployment, and it is also what the Gateway Load Balancer builds on for transparent appliance chaining in front of public endpoints.
Monitor data path availability and health probe status metrics, and alert when healthy backend count drops below your redundancy floor rather than at zero, because finding out at zero is finding out from your users.
Cheers
Osama
Leave a comment