Table of Contents#
- Application Delivery — Choosing the Right Layer
- Azure Load Balancer — Architecture and SKUs
- Public vs. Internal Load Balancers
- Regional vs. Cross-Region Load Balancers
- Load Balancing Rules and Health Probes
- Inbound NAT Rules
- Outbound Rules and SNAT Port Exhaustion
- Gateway Load Balancer — Transparent NVA Insertion
- Application Gateway — Layer 7 Architecture
- Application Gateway v2 Autoscaling and Zone Redundancy
- Backend Pools, HTTP Settings, Listeners, and Routing Rules
- Health Probes for Application Gateway
- URL Rewrite and Header Manipulation
- TLS Termination and End-to-End TLS
- Web Application Firewall on Application Gateway
- Session Affinity and Multi-Site Hosting
- Azure Front Door — Global Layer 7 Delivery
- Front Door Tiers — Standard and Premium
- Front Door Routing, Origins, and Endpoints
- Front Door Caching
- Front Door Traffic Acceleration
- Front Door Rules Engine — Rewrite and Redirect
- Securing an Origin With Private Link in Front Door
- Custom Domains and Managed Certificates on Front Door
- Azure Traffic Manager — DNS-Based Global Routing
- Traffic Manager Routing Methods
- Application Gateway for Containers — AKS-Native Ingress
- Choosing Between Load Balancer, Application Gateway, Front Door, and Traffic Manager
- A Full Worked Application Delivery Bootstrap for Meridian Freight
- Part 6 CLI Cheat Sheet
- Common Mistakes and Interview Traps
- Worked Practice Problems
- Summary and What's Next
Application Delivery — Choosing the Right Layer#
Azure offers four genuinely distinct application delivery services, and choosing among them is one of the most common real architectural decisions this series covers — each operates at a different layer, with different scope (regional vs. global) and different Layer 4/7 awareness.
| Service | Layer | Scope | Understands HTTP? |
|---|---|---|---|
| Azure Load Balancer | 4 (TCP/UDP) | Regional | No |
| Application Gateway | 7 (HTTP/HTTPS) | Regional | Yes |
| Azure Front Door | 7 (HTTP/HTTPS) | Global | Yes |
| Traffic Manager | DNS-level | Global | No — routes DNS responses, protocol-agnostic |
Meridian Freight's shipment-api uses several of these together across this chapter: an internal Load Balancer for backend VM traffic, Application Gateway with WAF for regional ingress, and Front Door for the global carrier-partner-facing endpoint — Part 16 covers the full multi-region assembly.
Azure Load Balancer — Architecture and SKUs#
Azure Load Balancer distributes Layer 4 (TCP/UDP) traffic across backend instances — VMs, VM Scale Set instances — based purely on connection-level information, with no awareness of HTTP content.
az network lb create --name lb-driver-portal --resource-group rg-driver-portal-prod \
--sku Standard --frontend-ip-name fe-driver-portal \
--public-ip-address pip-lb-driver-portal --backend-pool-name bepool-driver-portalA genuinely important, current fact worth stating explicitly: the Basic SKU was retired on September 30, 2025 — any design still referencing Basic Load Balancer (in documentation, old IaC templates, or institutional habit) needs to move to Standard, the only currently supported SKU for regular load balancing. Basic instances that still exist keep running but carry no SLA and are explicitly unsupported.
| SKU | Status | Zone redundancy | Backend pool size |
|---|---|---|---|
| Basic | Retired Sept 2025 — unsupported, no SLA | No | Up to 300 |
| Standard | Current, only supported SKU for regular LB | Yes | Up to 1,000 |
| Gateway | Specialized — NVA traffic insertion (later in this chapter) | Yes | N/A |
Public vs. Internal Load Balancers#
# Public — internet-facing frontend
az network public-ip create --name pip-lb-driver-portal --resource-group rg-driver-portal-prod --sku Standard
# Internal — frontend IP lives inside the VNet, never internet-reachable
az network lb create --name lb-shipment-api-internal --resource-group rg-shipment-api-prod \
--sku Standard --frontend-ip-name fe-internal --private-ip-address 10.1.1.100 \
--vnet-name vnet-shipment-api --subnet snet-appAn internal load balancer is the correct choice for distributing traffic between internal tiers — shipment-api's app tier calling its database tier's replicas, for instance — where the frontend should never be reachable from outside the VNet at all, distinct from a public load balancer's internet-facing role.
Regional vs. Cross-Region Load Balancers#
A cross-region (global) Load Balancer — a genuinely distinct resource type, not just a configuration option on a regional one — uses Anycast to route traffic to the closest healthy REGIONAL Standard Load Balancer, providing a single, stable frontend IP spanning multiple regions.
az network cross-region-lb create --name lb-global-shipment-api --resource-group rg-networking-prod \
--sku Standard --tier Global \
--backend-pool-name bepool-regional-lbs
# Add a regional Load Balancer's frontend as a backend of the global one
az network cross-region-lb address-pool address add --lb-name lb-global-shipment-api \
--resource-group rg-networking-prod --pool-name bepool-regional-lbs \
--name backend-eastus --frontend-ip-address "<regional-lb-frontend-ip>"Why this matters concretely for Part 16's multi-region design, worth previewing here: a cross-region Load Balancer's backend pool contains OTHER (regional) Load Balancers, not VMs directly — it's a load balancer of load balancers, providing Layer 4 global distribution as an alternative to Front Door's Layer 7 global routing for workloads that specifically need Layer 4 (non-HTTP) global traffic distribution.
Load Balancing Rules and Health Probes#
az network lb probe create --lb-name lb-driver-portal --resource-group rg-driver-portal-prod \
--name probe-http --protocol Http --port 80 --path /healthz --interval 5 --threshold 2
az network lb rule create --lb-name lb-driver-portal --resource-group rg-driver-portal-prod \
--name rule-http --protocol Tcp --frontend-port 80 --backend-port 80 \
--frontend-ip-name fe-driver-portal --backend-pool-name bepool-driver-portal \
--probe-name probe-http
# Check which backend instances the Load Balancer currently
# considers healthy, rather than assuming based on rule configuration alone
az network lb address-pool show --lb-name lb-driver-portal --resource-group rg-driver-portal-prod \
--name bepool-driver-portal --query "loadBalancerBackendAddresses[].ipAddress"A genuinely important, non-obvious detail worth stating precisely: a Load Balancer's health probe operates at the LOAD BALANCER level, checking each backend instance directly — it is NOT the same probe mechanism, and doesn't share configuration with, an Application Gateway's own health probes (covered later in this chapter) even in a design using both together. Each layer needs its own health check configured independently.
Inbound NAT Rules#
An inbound NAT rule maps a specific frontend port to a specific backend instance's specific port — commonly used for direct management access (SSH/RDP) to individual VMs behind a load balancer, without exposing each VM with its own public IP.
az network lb inbound-nat-rule create --lb-name lb-driver-portal --resource-group rg-driver-portal-prod \
--name nat-ssh-vm01 --protocol Tcp --frontend-port 50001 --backend-port 22 \
--frontend-ip-name fe-driver-portal
# List every inbound NAT rule currently open on a Load Balancer —
# a worthwhile periodic audit, since these are easy to forget about
az network lb inbound-nat-rule list --lb-name lb-driver-portal --resource-group rg-driver-portal-prod \
--query "[].{name:name, frontendPort:frontendPort, backendPort:backendPort}"Worth stating a genuinely better current alternative for this specific use case: Azure Bastion (Part 7) provides browser-based, RBAC-governed remote access without opening any inbound NAT rule at all — inbound NAT rules for SSH/RDP remain a valid pattern for legacy scenarios, but a new design should default to Bastion instead.
Outbound Rules and SNAT Port Exhaustion#
Part 4 covered NAT Gateway as the current recommended outbound mechanism; a Standard Load Balancer's own outbound rules are the older pattern worth understanding for existing designs.
az network lb outbound-rule create --lb-name lb-driver-portal --resource-group rg-driver-portal-prod \
--name outbound-rule --frontend-ip-configs fe-driver-portal \
--backend-pool bepool-driver-portal --allocated-outbound-ports 1024
# Monitor actual SNAT port usage against the allocated pool —
# the metric that would have caught the exhaustion below before it happened
az monitor metrics list --resource "<lb-resource-id>" --metric "SNATConnectionCount" \
--interval PT1MFrom the Trenches: A team relying on a Load Balancer's default outbound rules for a VMSS making many outbound connections per instance (calling an external payment API at high volume) hit SNAT port exhaustion — the pool of outbound ports allocated per instance ran out under real load, causing new outbound connections to fail intermittently in a pattern that looked like a flaky external API rather than the team's own outbound port allocation. The fix, matching Part 4's own recommendation, was migrating to NAT Gateway, which provides a dramatically larger outbound port pool per subnet than Load Balancer outbound rules ever practically allocate per instance.
Gateway Load Balancer — Transparent NVA Insertion#
Gateway Load Balancer solves a specific, narrower problem than a regular Load Balancer: transparently inserting third-party Network Virtual Appliances (an IDS/IPS, a custom traffic-inspection appliance) into a traffic path, without the traffic's source/destination IPs being altered — the NVA sees the same original packet a regular Load Balancer's mechanics would otherwise obscure.
Why "transparent" is worth stating precisely as the key differentiator: the backend application never needs to know an NVA is inspecting its traffic — no source IP rewriting breaks the application's own IP-based logic (rate limiting, geo-based decisions) the way a naive NVA-in-the-path design otherwise would.
az network lb create --name gwlb-meridian --resource-group rg-networking-prod --sku Gateway
az network lb frontend-ip create --lb-name gwlb-meridian --resource-group rg-networking-prod \
--name fe-gwlb --vnet-name vnet-meridian-hub --subnet snet-gwlbChaining a Gateway Load Balancer into another Load Balancer or Application Gateway's traffic path is done via a paired frontend IP configuration — the traffic flows through the Gateway Load Balancer's NVA fleet transparently as an extra hop, invisible to both the client and the ultimate backend.
Application Gateway — Layer 7 Architecture#
Application Gateway operates at Layer 7 — it understands HTTP/HTTPS content directly, enabling routing decisions based on URL path, host header, and other application-level attributes that a Layer 4 Load Balancer simply cannot see.
az network application-gateway create --name appgw-shipment-api --resource-group rg-shipment-api-prod \
--sku Standard_v2 --capacity 2 --vnet-name vnet-shipment-api --subnet snet-appgw \
--public-ip-address pip-appgw-shipment-api
# Confirm the gateway's own operational status before assuming it's
# actually serving traffic correctly
az network application-gateway show --name appgw-shipment-api --resource-group rg-shipment-api-prod \
--query "{state: operationalState, provisioningState: provisioningState}"Application Gateway v2 Autoscaling and Zone Redundancy#
az network application-gateway update --name appgw-shipment-api --resource-group rg-shipment-api-prod \
--min-capacity 2 --max-capacity 10 --zones 1 2 3
# Check current instance count against the configured autoscale range
az network application-gateway show --name appgw-shipment-api --resource-group rg-shipment-api-prod \
--query "autoscaleConfiguration"Worth stating a genuine v2 improvement over the original v1 SKU, no longer requiring a workaround this series' AWS chapter had to design around for its own load balancer equivalent: v2 SKU Application Gateways span multiple Availability Zones BY DEFAULT, removing the older need to provision separate per-zone instances behind a Traffic Manager just to achieve zone redundancy — v2 also removes the requirement to guess an instance count upfront, autoscaling instead based on actual traffic load between a configured minimum and maximum.
Backend Pools, HTTP Settings, Listeners, and Routing Rules#
az network application-gateway address-pool create --gateway-name appgw-shipment-api \
--resource-group rg-shipment-api-prod --name pool-api --servers 10.1.1.10 10.1.1.11 vmss-shipment-api
az network application-gateway http-settings create --gateway-name appgw-shipment-api \
--resource-group rg-shipment-api-prod --name settings-api --port 443 --protocol Https \
--cookie-based-affinity Disabled --timeout 30
az network application-gateway http-listener create --gateway-name appgw-shipment-api \
--resource-group rg-shipment-api-prod --name listener-https --frontend-port 443 \
--ssl-cert cert-shipment-api
az network application-gateway rule create --gateway-name appgw-shipment-api \
--resource-group rg-shipment-api-prod --name rule-api --http-listener listener-https \
--address-pool pool-api --http-settings settings-api --rule-type PathBasedRouting| Component | Role |
|---|---|
| Backend pool | The actual servers receiving traffic |
| HTTP settings | How the gateway talks to the backend (protocol, port, timeout, cookie affinity) |
| Listener | What the gateway listens for (port, hostname, TLS certificate) |
| Routing rule | Ties a listener to a backend pool, optionally with path-based routing |
A backend pool can target VMs by IP, a VM Scale Set directly by resource reference, or an external FQDN — worth knowing the pool isn't limited to IP addresses alone, since referencing a VMSS directly means new instances are picked up automatically as the set scales, with no manual pool membership update required.
Health Probes for Application Gateway#
az network application-gateway probe create --gateway-name appgw-shipment-api \
--resource-group rg-shipment-api-prod --name probe-api --protocol Https \
--host-name-from-http-settings true --path /healthz --interval 30 --threshold 3 \
--match-status-codes 200-399
# Check current backend health as the gateway sees it — the
# fastest way to confirm a probe is actually configured correctly
az network application-gateway show-backend-health --name appgw-shipment-api \
--resource-group rg-shipment-api-prodWhy matching a specific status code RANGE, rather than only exact 200, is worth calling out explicitly: a backend legitimately returning a 3xx redirect on its health endpoint would be marked UNHEALTHY by a probe expecting exactly 200 — a genuinely common, easy-to-miss misconfiguration that takes healthy backends out of rotation for a reason that has nothing to do with actual backend health.
URL Rewrite and Header Manipulation#
az network application-gateway rewrite-rule set create --gateway-name appgw-shipment-api \
--resource-group rg-shipment-api-prod --name rewrite-set-security-headers
az network application-gateway rewrite-rule create --gateway-name appgw-shipment-api \
--resource-group rg-shipment-api-prod --rule-set-name rewrite-set-security-headers \
--name add-hsts --response-headers "Strict-Transport-Security=max-age=31536000"
# URL rewrite — rewriting a legacy path to a new backend route,
# without touching the backend application's own routing code
az network application-gateway rewrite-rule create --gateway-name appgw-shipment-api \
--resource-group rg-shipment-api-prod --rule-set-name rewrite-set-security-headers \
--name rewrite-legacy-path --request-url "/v1/{var_uri_path}" \
--condition-variable "var_uri_path" --pattern "^/legacy/(.*)"A genuinely useful, easy-to-overlook capability worth naming explicitly: injecting security response headers (HSTS, X-Content-Type-Options) at the gateway means every backend automatically gets them without each application team needing to remember to add them in their own code — a centralized, structurally enforced security baseline rather than a per-team convention.
TLS Termination and End-to-End TLS#
| Mode | Client-to-gateway | Gateway-to-backend | Best fit |
|---|---|---|---|
| TLS termination only | Encrypted | Unencrypted HTTP | Backend and gateway share a trusted, isolated network (still worth pairing with Part 4's VNet encryption) |
| End-to-end TLS | Encrypted | Re-encrypted, separate certificate | Compliance requiring encryption at every hop, zero-trust network assumptions |
az network application-gateway http-settings update --gateway-name appgw-shipment-api \
--resource-group rg-shipment-api-prod --name settings-api \
--protocol Https --host-name-from-backend-pool trueWhy end-to-end TLS is worth treating as the safer default despite the added certificate management overhead, worth stating explicitly: TLS termination only trusts the internal network segment between the gateway and backend to be inherently safe — an assumption that doesn't hold under a genuine zero-trust security model (Part 12), where no network segment is assumed trusted by default.
Web Application Firewall on Application Gateway#
az network application-gateway waf-policy create --name waf-policy-shipment-api \
--resource-group rg-shipment-api-prod
az network application-gateway waf-policy managed-rule rule-set add \
--policy-name waf-policy-shipment-api --resource-group rg-shipment-api-prod \
--type OWASP --version 3.2The WAF_v2 SKU embeds the WAF engine directly into the request pipeline, evaluating every request against the OWASP Core Rule Set (currently 3.2/3.1) before it ever reaches the backend — Part 7 and Part 12 cover WAF policy tuning, custom rules, and bot management in full depth; this chapter's scope is knowing it attaches at the Application Gateway (and Front Door) layer specifically.
# Set the WAF to Prevention mode (block) rather than Detection mode
# (log only) once confident in the managed rule set's behavior for
# this specific application — Detection first, Prevention once tuned
az network application-gateway waf-policy update --name waf-policy-shipment-api \
--resource-group rg-shipment-api-prod --set policySettings.mode=PreventionWhy starting in Detection mode before switching to Prevention is worth stating as a deliberate rollout sequence, not unnecessary caution: the OWASP Core Rule Set is tuned for a broad range of applications and can produce false positives against a specific application's legitimate traffic patterns — running in Detection mode first, reviewing what WOULD have been blocked, and tuning exclusions before switching to Prevention avoids a WAF rollout that blocks real customer traffic on day one.
Session Affinity and Multi-Site Hosting#
Two practical Application Gateway capabilities worth knowing precisely. Session affinity (cookie-based) ensures a client's subsequent requests land on the SAME backend instance that served their first request — necessary for a backend that keeps in-memory session state rather than externalizing it.
az network application-gateway http-settings update --gateway-name appgw-shipment-api \
--resource-group rg-shipment-api-prod --name settings-api \
--cookie-based-affinity EnabledWorth stating as a real architectural tradeoff, not a free feature: session affinity undermines even load distribution across backends over time — a backend that happens to receive many "sticky" long-lived sessions early carries a disproportionate share of ongoing load compared to one that started with fewer. The stronger long-term fix, where the application allows it, is externalizing session state (to a cache like Redis, covered in Part 11) so affinity isn't needed at all — session affinity is a legitimate stopgap for an application not yet re-architected that way, not a permanent design goal.
Multi-site hosting lets ONE Application Gateway serve multiple distinct hostnames, each routed to a different backend pool via host-header-based listeners — a genuine cost and management consolidation versus provisioning a separate gateway per site.
az network application-gateway http-listener create --gateway-name appgw-shipment-api \
--resource-group rg-shipment-api-prod --name listener-admin \
--host-name admin.meridianfreight.com --frontend-port 443 --ssl-cert cert-shipment-apiAzure Front Door — Global Layer 7 Delivery#
Azure Front Door provides global, Layer 7 HTTP/HTTPS delivery — the natural fit for Meridian Freight's carrier-partner-facing shipment-api endpoint, which needs a single global entry point routing to the nearest healthy regional deployment.
A genuinely important, current fact worth stating explicitly: Azure Front Door (classic) stopped accepting new profile creation on March 31, 2025, with full retirement scheduled for March 31, 2027 — any new design should use the current Standard/Premium tiers exclusively; an existing classic profile needs an active migration plan well before the retirement date, not a wait-and-see approach.
Front Door Tiers — Standard and Premium#
| Tier | Includes | Best fit |
|---|---|---|
| Standard | CDN caching, global load balancing, basic WAF | Content delivery and global routing without advanced security needs |
| Premium | Everything in Standard, plus advanced WAF (bot protection, Private Link origin support) | Anything needing Private Link-secured origins or advanced bot/security features |
Why Premium is the correct tier for Meridian Freight's shipment-api specifically, worth stating the underlying reasoning rather than defaulting to cost-minimizing Standard: carrier-partner-facing APIs are a realistic target for credential-stuffing and bot-driven abuse, and Premium's advanced bot protection plus Private Link origin support (covered later in this chapter) directly address that threat model — Standard would leave a real security gap for this specific, externally-exposed, high-value endpoint.
Front Door Routing, Origins, and Endpoints#
az afd profile create --profile-name fd-meridian --resource-group rg-networking-prod --sku Premium_AzureFrontDoor
# Confirm the profile provisioned successfully before configuring endpoints
az afd profile show --profile-name fd-meridian --resource-group rg-networking-prod \
--query provisioningState
az afd endpoint create --endpoint-name shipment-api --profile-name fd-meridian \
--resource-group rg-networking-prod
az afd origin-group create --origin-group-name og-shipment-api --profile-name fd-meridian \
--resource-group rg-networking-prod --probe-request-type GET --probe-path /healthz \
--probe-protocol Https --probe-interval-in-seconds 30
az afd origin create --origin-group-name og-shipment-api --profile-name fd-meridian \
--resource-group rg-networking-prod --origin-name origin-eastus \
--host-name appgw-shipment-api.eastus.cloudapp.azure.com --priority 1 --weight 1000
# A second origin, lower priority, for the multi-region failover
# design Part 16 builds out in full
az afd origin create --origin-group-name og-shipment-api --profile-name fd-meridian \
--resource-group rg-networking-prod --origin-name origin-westeurope \
--host-name appgw-shipment-api.westeurope.cloudapp.azure.com --priority 2 --weight 1000Front Door Caching#
az afd route create --endpoint-name shipment-api --profile-name fd-meridian \
--resource-group rg-networking-prod --route-name route-static --origin-group og-shipment-api \
--patterns-to-match "/static/*" --enable-caching true --query-string-caching-behavior IgnoreQueryString
# A separate route for the dynamic API, explicitly with caching disabled
az afd route create --endpoint-name shipment-api --profile-name fd-meridian \
--resource-group rg-networking-prod --route-name route-api --origin-group og-shipment-api \
--patterns-to-match "/api/*" --enable-caching falseWhy caching applies naturally to /static/* but should generally be disabled for API routes serving dynamic, per-partner data — a real, common configuration mistake worth naming explicitly: caching a dynamic API response at the edge can serve one carrier partner's data to a completely different partner making a similarly-shaped request, a genuine data-leakage risk, not just a staleness inconvenience — caching rules need to be scoped deliberately per route pattern, never applied blanket across an entire endpoint.
Front Door Traffic Acceleration#
Front Door's edge network uses Anycast routing and Microsoft's own private global backbone between edge and origin — a request from a carrier partner in Singapore reaching a shipment-api origin in eastus travels the shortest path to the NEAREST Front Door edge over the public internet, then the rest of the way to the origin over Microsoft's private network, rather than the public internet the entire distance.
Why this matters concretely, worth stating the underlying reasoning: the public internet segment of the request — the genuinely unpredictable part, subject to congestion and suboptimal routing — is minimized to just the "last mile" to the nearest edge, with the long-haul portion running over Microsoft's own optimized, private backbone — a real, measurable latency improvement for geographically distant clients, not just a caching benefit.
Front Door Rules Engine — Rewrite and Redirect#
az afd rule-set create --rule-set-name rules-security --profile-name fd-meridian --resource-group rg-networking-prod
az afd rule create --rule-set-name rules-security --profile-name fd-meridian --resource-group rg-networking-prod \
--rule-name redirect-http-to-https --order 1 \
--match-variable RequestScheme --operator Equal --match-values HTTP \
--action-name UrlRedirect --redirect-protocol Https
# Associate the rule set with the actual route it should apply to
az afd route update --endpoint-name shipment-api --profile-name fd-meridian \
--resource-group rg-networking-prod --route-name route-api --rule-sets rules-securityThe Rules Engine lets URL rewrite, redirect, and header manipulation logic run at the edge, before a request ever reaches an origin — genuinely useful for enforcing HTTPS-only access, or restructuring a legacy URL scheme without touching backend application code.
Rate Limiting at the Edge#
A Premium-tier WAF capability worth naming explicitly, since it directly protects shipment-api's carrier-partner-facing endpoint against the abuse pattern this chapter's tier recommendation already flagged: rate limiting blocks a client exceeding a defined request threshold, enforced at Front Door's edge before the request ever reaches an origin.
az network front-door waf-policy rule create --policy-name waf-policy-fd-shipment-api \
--resource-group rg-networking-prod --name rate-limit-partners \
--rule-type RateLimitRule --rate-limit-duration 1 --rate-limit-threshold 100 \
--priority 1 --action BlockWhy enforcing this at the EDGE, rather than in application code, matters concretely: a request blocked by Front Door's rate limiting never consumes any origin compute at all — the origin (and any backend WAF at the Application Gateway layer) never even sees the excess requests, which is both a stronger security posture and a genuine cost saving compared to letting abusive traffic reach the origin before being rejected.
Managed Rule Set Exclusions#
A genuinely common tuning need worth covering explicitly: a specific, known-legitimate request pattern (a large file upload's multipart form field, for instance) can trigger a false positive against a specific OWASP rule.
az network front-door waf-policy managed-rule-set add --policy-name waf-policy-fd-shipment-api \
--resource-group rg-networking-prod --type Microsoft_DefaultRuleSet --version 2.1 \
--rule-group-override "REQUEST-942-APPLICATION-ATTACK-SQLI" \
--exclusion "RequestBodyPostArgNames:document_content"Why a targeted exclusion for one specific field is worth preferring over disabling the entire rule group, worth stating the underlying reasoning: disabling a whole SQL-injection rule group to fix one false positive on one form field removes real protection for every OTHER field and request that rule group would otherwise still correctly defend — a scoped exclusion fixes the specific false positive without weakening the broader protection.
Securing an Origin With Private Link in Front Door#
A Premium-tier-only capability worth calling out explicitly, since it directly justifies the tier recommendation made earlier: an origin can be reached via Private Link rather than a public endpoint, meaning shipment-api's actual backend never needs a public IP at all.
az afd origin create --origin-group-name og-shipment-api --profile-name fd-meridian \
--resource-group rg-networking-prod --origin-name origin-private \
--enabled-state Enabled --private-link-resource "<app-gateway-resource-id>" \
--private-link-location eastus --private-link-request-message "Front Door origin request"Why this is worth treating as a meaningful security upgrade, not just a networking convenience: an origin with NO public endpoint at all cannot be attacked directly, bypassing Front Door entirely — a real, documented attack pattern against organizations that expose both a CDN/Front Door AND a public origin, where an attacker simply targets the origin's public IP directly, skipping Front Door's WAF and rate limiting altogether.
# Confirm the origin's Application Gateway actually has NO public IP
# attached once the Private Link migration is complete
az network application-gateway show --name appgw-shipment-api --resource-group rg-shipment-api-prod \
--query "frontendIPConfigurations[].publicIPAddress"Custom Domains and Managed Certificates on Front Door#
az afd custom-domain create --custom-domain-name shipment-api-domain --profile-name fd-meridian \
--resource-group rg-networking-prod --host-name shipment-api.meridianfreight.com \
--certificate-type ManagedCertificate
az afd route update --endpoint-name shipment-api --profile-name fd-meridian \
--resource-group rg-networking-prod --route-name route-api \
--custom-domains shipment-api-domainWhy Front Door's managed certificates are worth defaulting to over a manually procured and uploaded certificate, worth stating explicitly: Microsoft handles issuance AND automatic renewal entirely — eliminating an entire class of "the certificate expired and nobody noticed" outage that has caused real, documented incidents across the industry when certificate renewal was left as a manual, easy-to-forget process. A custom (bring-your-own) certificate remains supported for organizations with a specific certificate authority requirement, but the managed option removes real, recurring operational risk for the common case.
Azure Traffic Manager — DNS-Based Global Routing#
Traffic Manager operates at the DNS level — it doesn't proxy traffic at all, instead returning a DNS response pointing the client directly at the most appropriate endpoint, based on a chosen routing method.
az network traffic-manager profile create --name tm-meridian --resource-group rg-networking-prod \
--routing-method Priority --unique-dns-name meridian-freight-global \
--ttl 30 --protocol HTTPS --port 443 --path /healthz
# Add endpoints with explicit priority order
az network traffic-manager endpoint create --profile-name tm-meridian --resource-group rg-networking-prod \
--type azureEndpoints --name endpoint-eastus --target-resource-id "<eastus-endpoint-id>" --priority 1Why Traffic Manager is worth knowing as genuinely distinct from Front Door, not a redundant older alternative, worth stating precisely: because Traffic Manager only returns a DNS answer and never proxies traffic itself, it works for ANY protocol — not just HTTP/HTTPS — making it the right choice for a non-web TCP/UDP service needing global routing, which Front Door (HTTP/HTTPS-only) cannot serve at all.
Traffic Manager Routing Methods#
| Method | Behavior |
|---|---|
| Priority | Primary endpoint always preferred; failover to the next priority only if primary is unhealthy |
| Weighted | Distributes traffic proportionally across endpoints by assigned weight |
| Performance | Routes to the endpoint with lowest network latency from the client's location |
| Geographic | Routes based on the client's geographic origin — for data-residency-driven routing |
| Multivalue | Returns multiple healthy endpoint IPs in one DNS response, letting the client choose |
| Subnet | Routes based on the client's IP address range, mapped to specific endpoints |
Nested Traffic Manager Profiles#
A genuinely powerful capability worth knowing exists: Traffic Manager profiles can be nested, combining routing methods for a compound policy no single method achieves alone — the classic pattern is an outer Weighted or Priority profile whose "endpoints" are themselves other Traffic Manager profiles using Performance routing.
az network traffic-manager endpoint create --profile-name tm-meridian-outer \
--resource-group rg-networking-prod --type nestedEndpoints --name endpoint-na-region \
--target-resource-id "<nested-profile-resource-id>" --priority 1 --min-child-endpoints 1Why this matters concretely for a genuinely global, multi-region design: it lets Meridian Freight route by lowest latency WITHIN a geographic tier (Performance routing across North American regions) while still enforcing a hard geographic failover boundary at the outer level (Priority routing falling back to Europe only if the entire North American tier is unhealthy) — a compound policy neither routing method alone could express, and the exact pattern Part 16's full multi-region design builds on.
Application Gateway for Containers — AKS-Native Ingress#
A brief, forward-looking preview worth flagging here since it's a genuinely new application-delivery mechanism, ahead of Part 10's full AKS coverage: Application Gateway for Containers (AGC) is a managed Layer 7 ingress specifically for AKS, routing HTTP/HTTPS/gRPC (and AI inference) traffic into a cluster while Azure operates the actual data plane outside the cluster itself.
A genuinely important, current fact worth stating explicitly: AGC is the direct evolution of the older Application Gateway Ingress Controller (AGIC), and AKS's managed NGINX ingress add-on (based on the older Ingress API) stops receiving Azure support after November 2026 — a design being built today for AKS ingress should target the Kubernetes Gateway API standard, which AGC implements, rather than the legacy Ingress API pattern many existing tutorials and examples still show. Part 10 covers AKS itself and this ingress choice in full depth; this chapter's job is flagging that "how traffic reaches an AKS cluster" is itself a genuine application-delivery decision, not something to bolt on as an afterthought once the cluster already exists.
Choosing Between Load Balancer, Application Gateway, Front Door, and Traffic Manager#
| Need | Recommendation |
|---|---|
| Distribute TCP/UDP traffic across VMs in one region | Azure Load Balancer |
| URL/host-based routing, WAF, within one region | Application Gateway |
| Global HTTP delivery, caching, WAF, single entry point | Azure Front Door |
| Global routing for a non-HTTP protocol, or DNS-level failover | Traffic Manager |
| Global HTTP delivery AND regional path-based routing | Front Door in front of regional Application Gateways (the combined pattern this chapter's bootstrap uses) |
A Full Worked Application Delivery Bootstrap for Meridian Freight#
# 1. Internal Load Balancer for shipment-api's app-to-database tier traffic
az network lb create --name lb-shipment-api-internal --resource-group rg-shipment-api-prod \
--sku Standard --frontend-ip-name fe-internal --private-ip-address 10.1.1.100 \
--vnet-name vnet-shipment-api --subnet snet-app
# 2. Regional Application Gateway with WAF for path-based routing and TLS termination
az network application-gateway create --name appgw-shipment-api --resource-group rg-shipment-api-prod \
--sku WAF_v2 --min-capacity 2 --max-capacity 10 --zones 1 2 3 \
--vnet-name vnet-shipment-api --subnet snet-appgw --public-ip-address pip-appgw-shipment-api
# 3. Global Front Door Premium in front of the regional Application Gateway,
# with the origin secured via Private Link
az afd profile create --profile-name fd-meridian --resource-group rg-networking-prod --sku Premium_AzureFrontDoor
az afd origin create --origin-group-name og-shipment-api --profile-name fd-meridian \
--resource-group rg-networking-prod --origin-name origin-eastus \
--private-link-resource "<appgw-resource-id>" --private-link-location eastus
# 4. Force HTTPS via the Rules Engine
az afd rule create --rule-set-name rules-security --profile-name fd-meridian --resource-group rg-networking-prod \
--rule-name redirect-http-to-https --match-variable RequestScheme --operator Equal \
--match-values HTTP --action-name UrlRedirect --redirect-protocol HttpsPart 6 CLI Cheat Sheet#
| Area | Command | Purpose |
|---|---|---|
| Load Balancer | az network lb create --sku Standard | Create a Standard SKU Load Balancer |
| LB rules | az network lb rule create | Create a load balancing rule |
| LB probes | az network lb probe create | Create a health probe |
| Cross-region | az network cross-region-lb create | Create a global Layer 4 load balancer |
| App Gateway | az network application-gateway create --sku WAF_v2 | Create a v2 Application Gateway with WAF |
| App Gateway | az network application-gateway rewrite-rule set create | Configure header/URL rewrite |
| App Gateway | az network application-gateway waf-policy create | Create a WAF policy |
| Front Door | az afd profile create --sku Premium_AzureFrontDoor | Create a Premium Front Door profile |
| Front Door | az afd origin create --private-link-resource | Secure an origin with Private Link |
| Front Door | az afd route create --enable-caching true | Configure edge caching for a route |
| Traffic Manager | az network traffic-manager profile create | Create a DNS-based global routing profile |
| Traffic Manager | az network traffic-manager endpoint create --type nestedEndpoints | Nest one profile inside another for compound routing |
| WAF rate limit | az network front-door waf-policy rule create --rule-type RateLimitRule | Block clients exceeding a request threshold at the edge |
| WAF exclusion | az network front-door waf-policy managed-rule-set add --exclusion | Scope a managed rule exclusion to a specific field |
| WAF mode | az network application-gateway waf-policy update --set policySettings.mode | Switch between Detection and Prevention |
| Gateway LB | az network lb create --sku Gateway | Create a Gateway Load Balancer for NVA insertion |
| Session affinity | az network application-gateway http-settings update --cookie-based-affinity | Enable sticky sessions |
| Multi-site | az network application-gateway http-listener create --host-name | Host multiple sites on one gateway |
| Custom domain | az afd custom-domain create --certificate-type ManagedCertificate | Add a custom domain with an auto-renewing certificate |
Common Mistakes and Interview Traps#
| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Referencing Basic SKU Load Balancer in a new design | Retired September 2025 — unsupported, no SLA | Use Standard SKU exclusively for any new deployment |
| Assuming a Load Balancer and an Application Gateway share health probe configuration in a combined design | Each layer's health probes are entirely independent | Configure and verify health probes separately at each layer |
| Relying on default Load Balancer outbound rules for a high-outbound-connection-volume workload | Can hit SNAT port exhaustion under real load | Use NAT Gateway (Part 4) for outbound connectivity instead |
| Creating a new Azure Front Door (classic) profile | Classic stopped accepting new profiles March 2025, full retirement March 2027 | Use the current Standard/Premium tiers for any new profile |
| Caching a dynamic, per-partner API response at the Front Door edge | Can serve one partner's data to a different partner making a similarly-shaped request | Scope caching rules deliberately per route pattern, never applied blanket |
| Exposing an origin with a public IP behind Front Door | An attacker can bypass Front Door's WAF and rate limiting by hitting the origin directly | Use Private Link (Premium tier) to give the origin no public endpoint at all |
| Choosing Front Door for a non-HTTP protocol's global routing needs | Front Door is HTTP/HTTPS-only | Use Traffic Manager instead for any non-HTTP protocol |
| Matching only exact HTTP 200 in an Application Gateway health probe | A legitimate 3xx response marks a healthy backend as unhealthy | Match a status code range appropriate to the actual healthy response set |
| Enabling session affinity as a permanent architectural default | Undermines even load distribution across backends over time | Treat it as a stopgap; externalize session state (Part 11) when possible |
| Manually procuring and uploading a Front Door certificate without a renewal process | A missed renewal causes a real, entirely avoidable outage | Default to Front Door's managed certificates, which renew automatically |
| Building new AKS ingress on the legacy Ingress API / managed NGINX add-on | Loses Azure support after November 2026 | Target the Kubernetes Gateway API via Application Gateway for Containers for new designs |
| Switching a new WAF policy directly to Prevention mode without a Detection-mode tuning period | Can block legitimate customer traffic on day one from an untuned rule set | Run in Detection mode first, review findings, tune exclusions, then switch to Prevention |
| Disabling an entire WAF rule group to fix one false positive on one specific field | Removes real protection for every other field and request that rule group would otherwise defend | Use a scoped exclusion targeting only the specific field causing the false positive |
| Enforcing rate limits only in application code, not at the edge | Abusive requests still consume backend/origin compute before being rejected | Enforce rate limiting at Front Door's edge so blocked requests never reach the origin at all |
Worked Practice Problems#
Problem 1: Meridian Freight's docs-processor VMSS, processing a high volume of outbound calls to an external document-classification API, begins experiencing intermittent outbound connection failures under peak load. The team initially suspects the external API is unreliable. What's the more likely Azure-side cause, and how would you confirm it?
Answer: SNAT port exhaustion on the Standard Load Balancer's default outbound rules is the more likely cause — each backend instance has a limited pool of outbound ports allocated by the Load Balancer's outbound rule configuration, and a high volume of concurrent outbound connections per instance can exhaust that pool, causing new outbound connections to fail intermittently in a pattern that looks identical to an unreliable external API. Confirming it involves checking Azure Monitor metrics for SNATConnectionCount and AllocatedSNATPorts against actual usage — a fix is migrating to NAT Gateway (Part 4), which provides a substantially larger outbound port pool per subnet than Load Balancer outbound rules practically allocate.
Problem 2: A team configures Azure Front Door Standard in front of shipment-api's Application Gateway origin, keeping the Application Gateway's public IP reachable directly as a "just in case Front Door has an issue" fallback. A security audit later finds the WAF protections on Front Door are being bypassed by traffic hitting the Application Gateway's public IP directly. What's the design flaw, and what's the fix?
Answer: Keeping the origin's public IP directly reachable defeats the security purpose of putting Front Door and its WAF in front of it at all — an attacker (or any client) can simply target the Application Gateway's public IP directly, completely bypassing Front Door's WAF, rate limiting, and bot protection. The "just in case Front Door has an issue" reasoning also doesn't hold up: Front Door is a highly available, globally distributed service, and a direct-access fallback path introduces a permanent, unconditional security gap in exchange for hedging against a rare failure mode. The fix is upgrading to Front Door Premium and securing the origin via Private Link, removing its public IP entirely — the origin becomes reachable ONLY through Front Door, closing the bypass path structurally rather than relying on anyone remembering not to use the direct IP.
Problem 3: Meridian Freight enables Front Door caching on all routes, including /api/rates, which returns carrier-specific pricing data unique to each authenticated partner. Shortly after, a partner reports seeing pricing data that doesn't match their account. What happened, and what's the fix?
Answer: Caching was applied to a dynamic, per-partner API route without excluding it — Front Door's edge cache served a cached response (from a different partner's earlier request with a similarly-shaped URL) to this partner, since the cache didn't distinguish between partners for that route. This is a genuine data-leakage incident, not just a staleness bug. The fix is explicitly excluding /api/rates (and any other route returning caller-specific dynamic data) from caching, reserving caching for genuinely cacheable, non-personalized content like /static/* — caching rules need to be scoped deliberately per route pattern based on whether the response is actually safe to share across different callers, never applied as a blanket "cache everything" default.
Problem 4: An architect recommends Traffic Manager for globally routing Meridian Freight's shipment-api HTTP traffic, citing "it's simpler and cheaper than Front Door." A colleague objects that this loses caching and WAF capabilities. Evaluate both positions.
Answer: The colleague's objection is well-founded for this specific use case: Traffic Manager only returns a DNS response pointing a client at an endpoint — it never proxies or inspects traffic itself, so it provides no caching, no WAF, and no Layer 7 routing capability at all, unlike Front Door. For an HTTP API that benefits from edge caching (static assets) and needs WAF protection against a real bot/credential-stuffing threat model (as this chapter's earlier Front Door tier discussion established), Traffic Manager's simplicity comes at the cost of capabilities shipment-api genuinely needs. The architect's "simpler and cheaper" framing isn't wrong in isolation — Traffic Manager IS simpler and cheaper — but the right tool choice depends on the specific workload's requirements, not on defaulting to the cheaper option: Traffic Manager is the right recommendation for a NON-HTTP protocol needing global routing, but not for an HTTP API needing edge caching and WAF, which is exactly Front Door's purpose.
Problem 5: A platform team migrates an Application Gateway from v1 to v2 and is surprised to find the new deployment automatically spans three Availability Zones without any additional Traffic Manager or multi-instance configuration they previously needed for v1. What changed, and why does this matter for the migration's cost and complexity?
Answer: Application Gateway v2 SKUs span multiple Availability Zones by default, a genuine architectural improvement over v1, which required manually provisioning separate per-zone Application Gateway instances behind a Traffic Manager to achieve equivalent zone redundancy. This matters concretely for the migration's cost and complexity: the v1 design's Traffic Manager and multiple gateway instances can likely be decommissioned entirely, since v2's built-in zone redundancy provides the same protection natively — a genuine simplification, not just a version bump, reducing both the resource count and the operational surface area the platform team needs to maintain going forward.
Problem 6: Meridian Freight's driver-portal backend keeps session state in local instance memory rather than an external cache, so the platform team enables session affinity on its Application Gateway to keep each user's requests routed to the same instance. Six months later, as the fleet scales to handle growth, the team notices load is distributed unevenly across instances, with a few instances consistently running hotter than others. What's the underlying cause, and what's the more durable long-term fix?
Answer: Session affinity is the underlying cause — once a client's session is pinned to a specific backend instance, it stays pinned for the session's full lifetime regardless of how load shifts afterward, so instances that happened to receive many long-lived sessions early accumulate a disproportionate share of ongoing load compared to instances that started more lightly loaded. This is an inherent tradeoff of session affinity, not a misconfiguration to tune away. The more durable long-term fix is externalizing driver-portal's session state to a shared cache (Redis, covered in Part 11) so any instance can serve any request statelessly — at which point session affinity can be disabled entirely, restoring genuinely even load distribution across the fleet regardless of how long any individual session lives.
Problem 7: A team manually procures a TLS certificate for shipment-api's Front Door custom domain from a third-party certificate authority, uploads it, and considers the setup complete. Eleven months later, the certificate expires unexpectedly during a period when the engineer who originally set it up is on leave, causing a multi-hour outage. What process gap caused this, and what would Front Door's managed certificate option have done differently?
Answer: The process gap is relying on a manually procured certificate with no automated renewal tracking — nothing forced anyone to notice the approaching expiration date, and the one person most likely to remember was unavailable when it mattered. Front Door's managed certificate option would have prevented this entirely: Microsoft handles both issuance and automatic renewal for the domain, with no manual renewal step for any engineer to forget or be unavailable for. A custom certificate remains a legitimate choice when an organization has a specific certificate authority requirement (an internal PKI, a specific compliance mandate naming an approved CA), but absent that requirement, the managed certificate option removes an entire class of real, recurring operational risk.
Problem 8: A team planning a new AKS deployment for a future Meridian Freight service finds several existing tutorials and Helm charts online recommending the managed NGINX ingress add-on, and adopts it without further research since "it's the standard approach." What risk does this introduce given the current AKS ingress landscape, and what should the team target instead?
Answer: The managed NGINX ingress add-on is based on the legacy Kubernetes Ingress API, and Azure support for it ends after November 2026 — adopting it for a NEW deployment being built today means building on a path that loses support relatively soon after going live, likely forcing a disruptive ingress migration not long after launch. The team should target Application Gateway for Containers instead, which implements the Kubernetes Gateway API — the direction AKS is aligning toward long-term, consistent with upstream Kubernetes' own move away from the older Ingress API. The lesson here generalizes beyond this specific case: "recommended in existing tutorials" isn't the same as "current guidance," especially in a fast-moving area like Kubernetes ingress, and confirming a recommendation's currency against official, dated sources is worth the extra step before committing a new production design to it.
Problem 9: Meridian Freight switches a newly configured WAF policy on shipment-api's Application Gateway directly to Prevention mode immediately after creating it, reasoning that "we want protection active from day one." Within hours, several legitimate carrier partners report their document-upload requests are being rejected with 403 errors. What process step was skipped, and what should the team do now?
Answer: The team skipped the Detection-mode tuning period this chapter recommends — switching straight to Prevention mode enforces the full, untuned OWASP Core Rule Set against real traffic immediately, and a legitimate request pattern (likely the multipart form data in the document upload, a common false-positive trigger) is being blocked as if it were an attack. The immediate fix is switching back to Detection mode to stop blocking legitimate traffic while investigating, then reviewing the WAF logs to identify exactly which rule triggered on the upload requests, adding a scoped exclusion for that specific field (not disabling the whole rule group), and only then switching back to Prevention mode once confirmed the exclusion resolves the false positive without meaningfully weakening protection elsewhere.
Problem 10: Meridian Freight's global expansion plan calls for the lowest-latency routing available within North America and within Europe separately, but with a hard requirement that European traffic NEVER fails over to North American infrastructure (a data-residency requirement) even during a full North American outage, and vice versa. A single flat Performance-routing Traffic Manager profile is proposed. Why doesn't this satisfy the requirement, and what's the correct design?
Answer: A single flat Performance-routing profile routes purely by lowest measured latency across ALL configured endpoints — during a North American outage, if a European endpoint happened to offer acceptable (if not optimal) latency to some North American clients, Performance routing could route traffic there, violating the hard data-residency requirement that European and North American traffic must never cross that boundary regardless of health status. The correct design uses two SEPARATE Traffic Manager profiles, one per geographic tier, each internally using Performance routing among endpoints within that tier only — with no nesting or failover relationship between the two tiers at all, since the requirement explicitly forbids cross-tier failover. This is a case where nested profiles (this chapter's earlier pattern) would be the WRONG tool — nesting is for compound routing where failover BETWEEN tiers is desired; a hard data-residency boundary calls for genuinely separate, non-interacting profiles instead.
Summary and What's Next#
- Azure Load Balancer (Layer 4), Application Gateway (Layer 7, regional), Front Door (Layer 7, global), and Traffic Manager (DNS-level, protocol-agnostic) solve genuinely distinct problems — choosing among them depends on layer awareness and scope needs, not a single "best" option.
- Basic Load Balancer was retired in September 2025 — Standard is the only current, supported SKU for regular load balancing.
- SNAT port exhaustion is a real failure mode for Load Balancer outbound rules under high connection volume — NAT Gateway (Part 4) is the current recommended fix.
- Application Gateway v2 spans multiple zones by default and autoscales, removing v1's need for manual per-zone instances behind a Traffic Manager.
- Azure Front Door (classic) is being retired (full retirement March 2027) — new designs use the current Standard/Premium tiers exclusively.
- Front Door Premium's Private Link origin support closes a real security gap — an origin with no public IP cannot be attacked by bypassing Front Door's WAF directly.
- Edge caching must be scoped deliberately per route — caching a dynamic, per-caller API response is a genuine data-leakage risk, not just a staleness inconvenience.
- Session affinity is a legitimate stopgap, not a permanent architectural goal — it undermines even load distribution over time, and externalizing session state removes the need for it entirely.
- Front Door's managed certificates remove an entire class of "the certificate expired and nobody noticed" outage by handling issuance and renewal automatically.
- AKS ingress is moving to the Kubernetes Gateway API via Application Gateway for Containers — the legacy Ingress API/managed NGINX add-on path loses support after November 2026, worth knowing before Part 10's full AKS treatment.
- Rate limiting and scoped WAF exclusions belong at the edge, tuned deliberately — Detection mode before Prevention, and a targeted exclusion rather than disabling a whole rule group.
- Nested Traffic Manager profiles express compound routing policies a single profile cannot — but a hard data-residency boundary calls for genuinely separate, non-nested profiles instead.
Continue to Part 7 (07-networking-private-access-and-security.md) for the private connectivity and network security layer — Private Link, NSGs, Azure Firewall, and WAF policy in depth — that locks down everything this chapter's application delivery services expose.