Azure Networking

How to Deploy a Zone-Redundant, Autoscaling Application Gateway v2 From Scratch

You have a web app behind a single Application Gateway, and last quarter it became the thing that took you down. One availability zone in the region had a power event, your gateway instance happened to live there, and for nineteen minutes every request to your public endpoint returned nothing — even though the backend App Service was perfectly healthy in the other two zones. The gateway, the one component meant to make you resilient, was itself a single point of failure. That is the gap this article closes. Application Gateway v2 — the Standard_v2 and WAF_v2 SKUs — can run as a zone-redundant resource that places its instances across multiple availability zones, and it can autoscale its instance count up and down with traffic instead of running a fixed, hand-sized fleet. Built correctly from the start, a single zone failure costs you nothing and a traffic spike scales you out automatically rather than paging you.

This is a build guide, not a survey. You will stand up a real Standard_v2 gateway from an empty resource group three ways — clickable in the portal, scripted with az CLI, and declared in Bicep — with autoscaling (a minimum and maximum instance count) and zone redundancy on (instances spread across zones 1, 2 and 3). You wire the four pieces every gateway needs — a frontend listener, a backend pool, a backend HTTP setting and a health probe joined by a routing rule — then validate that it works and actually spans zones, because “I created it” and “it is resilient” are different claims. The exemplar for the v2 platform mechanics is the end-to-end TLS build in End-to-End TLS with Application Gateway v2 + WAF; this article is the zone-redundancy and autoscaling layer underneath it.

By the end you will understand why the v1 SKU could not do this, why zone redundancy is a deployment-time decision you cannot toggle later, what the autoscale floor and ceiling cost, and how to read the metrics that prove the gateway is healthy across all three zones — plus the mistakes that turn a thirty-minute task into a two-hour one: the undersized /24 subnet, the NSG that silently breaks the platform’s health channel, and the Standard_v2 versus Standard SKU confusion that leaves you on the old, non-zonal platform.

What problem this solves

A load balancer that is itself a single point of failure is a liability with good intentions. The classic failure mode is the one above: you build redundancy at the backend (App Service instances, VMs in a scale set, pods across nodes) then funnel all of it through one gateway instance pinned to one zone. When that zone has an incident — and Azure zones are isolated precisely because incidents happen — the backend survives but nobody can reach it. You bought high availability everywhere except the one place every packet passes through.

The second problem is capacity that does not move. Application Gateway v1 ran a fixed number of instances you sized by hand. Under-provision and a traffic spike (a marketing email, a flash sale, a viral post) overwhelms the gateway: requests queue, latency climbs, connections drop, while the backend sits half-idle. Over-provision and you pay around the clock for headroom you use ninety minutes a week. Either way an engineer is in the loop dragging a slider — the work autoscaling exists to delete.

Who hits this: anyone running a public web app or API that needs layer-7 routing (path/host routing, TLS termination, cookie affinity, or a WAF) and an availability target above a single zone’s. If layer 4 is enough, the regional Azure Load Balancer is already zone-redundant and cheaper. But the moment you need HTTP-aware routing or a WAF and must survive a zone failure, a zone-redundant, autoscaling Application Gateway v2 is the answer — built that way from the first command, because the zonal decision is not reversible in place.

Pain in production What it looks like Without zone-redundant autoscaling v2 With it
Single-zone gateway dies All requests fail though backend is healthy 15–30 min outage per zone incident Zero impact; surviving zones serve
Traffic spike Latency climbs, connections drop Manual scale-up, paged engineer Autoscale adds capacity in minutes
Quiet hours Paying for peak fleet at 3 a.m. Fixed instance bill 24/7 Scales down to the floor
Maintenance / platform patch Brief capacity dip Single instance = downtime window Rolling across zones, no dip
v1 SKU end-of-life Migration pressure, no autoscale Stuck on fixed-capacity v1 On the supported v2 platform

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should be comfortable with the core networking primitives: a virtual network and subnets with CIDR ranges, a public IP, and what a network security group (NSG) does. If CIDR sizing is shaky, read Azure VNet IP Address Planning: CIDR & Subnetting first — subnet sizing is the most common Application Gateway mistake. You should know that availability zones are physically isolated datacentres within a region (Azure Regions & Availability Zones Explained). You will run everything in Cloud Shell or a local Azure CLI; a two-VM or App Service backend to point at helps.

This sits in the Networking track, one layer above the load balancer. Application Gateway is Azure’s layer-7 (HTTP/HTTPS) reverse proxy and WAF; the Azure Load Balancer vs Application Gateway decision (layer 4 vs layer 7) is upstream of it. Next steps are TLS termination and WAF tuning (the end-to-end TLS deep-dive) and outbound design with a NAT Gateway; pulling certificates from Key Vault uses a user-assigned managed identity.

Where each moving part lives and who owns it, so you wire it with the right team:

Layer Component What it does here Usual owner
Edge / DNS Public IP + DNS A record Stable entry point for clients Network / SRE
Gateway subnet Dedicated AppGatewaySubnet Hosts gateway instances only Network
Gateway Application Gateway v2 L7 routing, TLS, WAF, autoscale Network / platform
Health Backend health probe Marks backends in/out of rotation Network + app
Backend App Service / VMSS / VMs Serves the actual requests App / dev team
Security NSG on gateway subnet Allows platform + client traffic Network / security

Core concepts

Five ideas make every step obvious.

Application Gateway v2 is a different platform from v1, not a faster v1. The v2 SKUs (Standard_v2, WAF_v2) add autoscaling, zone redundancy, static VIPs and ~5x better TLS performance. The v1 SKUs (Standard, WAF) run a fixed instance count, live in a single zone, and are on a deprecation path. The names are a trap: Standard and Standard_v2 are different products. Everything here requires a v2 SKU; on v1 the autoscale and zone options do not exist.

Zone redundancy is instance placement, decided at creation. A zone-redundant gateway spreads its instances across availability zones 1, 2 and 3 in regions that support zones. If one zone fails, instances in the other two keep serving — the public IP is a zone-redundant Standard SKU IP, so the VIP survives too. The catch that bites everyone: you choose zones at creation and cannot change them in place. A gateway created without zones (or zonal — pinned to one zone) stays that way until you rebuild it. Get it right on the first command.

Autoscaling sizes the instance count between a floor and a ceiling. A v2 gateway has a minimum and maximum capacity; the platform adds and removes instances with load, within your bounds. The minimum is your always-on floor — never 0 for production, because warming from zero takes minutes and you eat cold latency or dropped connections. The maximum (up to 125) is your safety ceiling and cost cap. You are billed for what the gateway actually runs, in Capacity Units, not for the maximum you allow.

The Capacity Unit is the v2 billing and sizing atom. A v2 gateway’s work is metered in Capacity Units (CU), each bundling a slice of three dimensions: compute (≈ 50 connections/sec for Standard_v2, fewer with WAF), persistent connections (≈ 2,500), and throughput (≈ 2.22 Mbps). The gateway scales to satisfy the most-stressed dimension. The bill is a fixed price per hour plus a per-CU-hour charge — an idle gateway at the floor still costs the fixed fee. The CU model is how you predict the bill and size the floor.

A gateway is five components wired into a path. Every request follows: listener (frontend IP + port)routing rulebackend HTTP setting (protocol, port, timeout) → backend pool, with a health probe continuously deciding which members are eligible. Miss any one and you get errors: no listener, nothing answers; no healthy backend, 502; wrong HTTP-setting port, 502; probe host mismatch, backend Unhealthy. The build is creating these five and connecting them — resilience and scale config sits on top.

The vocabulary in one table

Every term, side by side, before the build (the glossary repeats these for lookup).

Concept One-line definition Where it lives Why it matters here
Standard_v2 / WAF_v2 The v2 SKUs (autoscale + zones capable) Gateway SKU tier Required for everything in this guide
Zone redundancy Instances spread across zones 1/2/3 Set at creation, immutable Survives a single-zone failure
Autoscaling Instance count between min and max Gateway SKU capacity Scales with load, caps cost
Capacity Unit (CU) The v2 unit of work and billing Metric + bill How you size the floor and predict cost
Gateway subnet Dedicated subnet for instances only VNet, /24 recommended Undersized → autoscale/upgrade fails
Frontend IP Public (and/or private) entry IP Public IP resource The client-facing VIP; zone-redundant
Listener Frontend IP + port + protocol Gateway config Where requests arrive
Backend pool The set of targets (IPs/FQDN/NIC) Gateway config What the gateway forwards to
HTTP setting Backend protocol, port, timeout, probe Gateway config How the gateway talks to the backend
Health probe Periodic check of pool members Gateway config Decides in/out of rotation
Routing rule Joins listener → setting → pool Gateway config The wiring that makes traffic flow

Standard_v2 versus WAF_v2 — choosing the SKU

The first decision is which v2 SKU to deploy. Both autoscale and both can be zone-redundant; the difference is the web application firewall. WAF_v2 is Standard_v2 plus an integrated OWASP-rule firewall (managed via a separate WAF policy resource). Need layer-7 attack protection (SQLi, XSS, OWASP CRS) → WAF_v2; WAF lives elsewhere (e.g. Front Door) or the app is internal → Standard_v2 is leaner and cheaper.

Capability Standard_v2 WAF_v2 v1 (Standard / WAF)
Autoscaling Yes Yes No (fixed instances)
Zone redundancy Yes Yes No (single zone)
Static VIP Yes Yes No (VIP can change)
WAF / OWASP CRS No Yes WAF v1 only
Header rewrite Yes Yes Limited
URL rewrite / redirect Yes Yes Redirect only
Max instances 125 125 32 (fixed)
Relative cost Lower Higher (WAF premium) Legacy

The crucial caveat: you cannot change a deployed gateway’s SKU tier between Standard_v2 and WAF_v2 in place — like the zonal decision, it is a recreate. Decide WAF-or-not before the create command.

If you need… Choose Why
L7 routing, no firewall at this layer Standard_v2 Cheaper; WAF handled elsewhere or not needed
OWASP protection at the gateway WAF_v2 Integrated firewall with managed rules
Internal-only app, trusted clients Standard_v2 No public attack surface to firewall
Public app, compliance requires WAF WAF_v2 Required control at the entry point
Lowest cost, edge already has WAF (Front Door) Standard_v2 Avoid paying for WAF twice

This guide builds Standard_v2; the WAF policy attachment is a one-line addition when you move to WAF_v2 (covered in the end-to-end TLS deep-dive). Zones and autoscaling are identical between the two.

Zone redundancy — what it buys and what it costs

Choosing zones at creation is one short array, but the most consequential choice in the build. Three postures: zone-redundant (the goal) passes [1, 2, 3] and distributes instances across all three zones for the highest availability; zonal passes a single zone, e.g. [1], pinning all instances to one zone (only when you must co-locate with a zonal resource and accept the risk); non-zonal passes nothing, the legacy default with no zone guarantee. For production in a zone-enabled region, always choose zone-redundant.

Posture Zones value Survives one-zone failure When to use SLA posture
Zone-redundant [1,2,3] Yes Production default in zone regions Highest
Zonal (pinned) [1] (or [2]/[3]) No Co-locate with a zonal dependency Single-zone
Non-zonal (omitted) No (no guarantee) Region without zones; legacy Baseline

Three facts that save real incidents. First, zone redundancy protects against a zone failure, not a region failure — surviving a region outage needs a second gateway in a second region behind Traffic Manager or Front Door. Second, the public IP must be zone-redundant too — a v2 gateway requires a Standard-SKU IP, and for true zone redundancy that IP should itself be zone-redundant (the default); a zonal IP pinned to a failed zone undermines the whole design. Third, you cannot retrofit it — there is no update --zones that re-spreads a running gateway, so plan zones into the first deployment or face a side-by-side rebuild and DNS cutover.

There is no separate charge for zone redundancy — you pay for the instances/CUs the gateway runs, same as a non-zonal gateway. The only difference is indirect: a zone-redundant gateway runs a few instances across zones, so the floor is rarely a single instance.

Autoscaling — sizing the floor and the ceiling

Autoscaling replaces a fixed instance count with a minimum and maximum capacity; the platform watches load (the CU dimensions) and adds or removes instances within your bounds. The two numbers carry weight.

The minimum is your always-warm floor and baseline cost. It must be high enough that a sudden burst does not arrive before the platform can scale, because scale-out is not instant. For most production gateways a minimum of 2 is the sane floor — it gives headroom and, with zone redundancy, presence in more than one zone. A minimum of 0 is legal and cheap but lets the gateway fully scale in, so the first request after idle pays a multi-minute warm-up — never for anything user-facing.

The maximum is your safety ceiling and cost cap, up to 125. Set it well above expected peak so a genuine spike is absorbed, but not so high that a runaway loop or attack scales you into a surprise bill. A maximum of 10 covers most mid-size workloads; raise it as you learn your traffic.

Setting Values Default / typical When to change Trade-off / limit
Minimum capacity 0–125 instances 2 (production), 0 (dev) Higher for spiky traffic / multi-zone presence Higher floor = higher baseline cost
Maximum capacity min–125 instances 10 (mid-size) Raise as peak grows Too low → throttling at peak; too high → runaway cost
Autoscale vs fixed Autoscale or fixed capacity Autoscale Fixed only for steady, predictable load Fixed wastes money off-peak
Scale signal CU dimensions (platform) Platform-managed Not directly tunable You bound it, the platform decides within

Two sizing rules learned the hard way. One instance serves roughly 10 Capacity Units for Standard_v2 (fewer for WAF), so convert your peak (connections/sec, throughput) to CUs and set the maximum with headroom above it. And scale-out takes minutes, not seconds — autoscaling smooths sustained load but does not absorb an instantaneous wall of traffic the way a pre-warmed fleet would, so size the minimum for your burst and the maximum for your peak. (The --min-capacity/--max-capacity and --zones flags appear in full in the lab.)

The dedicated subnet, public IP, and NSG

Before the gateway can exist, three supporting resources must be right. Getting these wrong is the cause of most failed builds.

The gateway subnet

Application Gateway instances live in a dedicated subnet that holds nothing else — no VMs, no private endpoints, no other service (the name is conventional; the dedication, not the name, is what matters). Size it /24 (256 addresses): a v2 gateway consumes addresses per instance, and autoscaling plus rolling upgrades need spare addresses to provision new instances before draining old ones, so a /28 fails to scale or upgrade once you have a handful of instances — recommended /24, never smaller than /26. And keep one v2 gateway per subnet (you cannot mix v1 and v2 in one subnet).

Subnet attribute Requirement Why Failure if wrong
Dedication Gateway only, nothing else Platform owns the subnet Create fails / conflicts
Size /24 recommended, /26 min Room for scale + rolling upgrade Autoscale/upgrade can’t get IPs
Name Conventional (e.g. AppGatewaySubnet) Readability only None (name is not enforced)
SKU mixing One v2 gateway per subnet v1/v2 cannot coexist Deployment conflict

The public IP

A v2 gateway requires a Standard SKU, static public IP; Basic and dynamic are not supported. For zone redundancy it should be zone-redundant (the default when created without a specific zone). This static IP is the VIP clients resolve to via DNS.

Public IP attribute v2 requirement Note
SKU Standard Basic not supported on v2
Allocation Static Dynamic not supported on v2
Zones Zone-redundant (default) Don’t pin to one zone for a zone-redundant gateway
Tier Regional Global tier is for cross-region scenarios

The NSG on the gateway subnet

This is the silent killer. If you place an NSG on the gateway subnet (recommended for hygiene), it must allow the platform’s health and management traffic, or the gateway becomes unhealthy or fails provisioning even though your application rules look fine. The required inbound allowance is TCP ports 65200–65535 from the GatewayManager service tag — the v2 control-plane channel the platform uses to manage and probe your instances. Block it and the symptom is a gateway stuck in a failed/unhealthy state with no obvious application cause.

Direction Source Destination Ports Purpose Mandatory?
Inbound GatewayManager (service tag) Any TCP 65200–65535 v2 platform management/health Yes — gateway breaks without it
Inbound AzureLoadBalancer (service tag) Any * Health probe from the LB Yes
Inbound Internet (or your client CIDR) Any 80, 443 (your listener ports) Client traffic to listeners Yes (for public)
Inbound VirtualNetwork Any As needed Intra-VNet clients If applicable
Outbound Any Any * Default allow (do not over-restrict) Keep permissive

The rule that catches everyone: people lock the gateway subnet to 443 from Internet and nothing else, then spend an afternoon wondering why the gateway is unhealthy. The fix is always the same — add the 65200–65535 from GatewayManager inbound rule. (These ports are an internal management channel, not an internet exposure on your part.)

Architecture at a glance

Walk the request path left to right. A client resolves your DNS name to the gateway’s static, zone-redundant public IP and opens an HTTPS/HTTP connection. It lands on a frontend listener — frontend IP + port (443/80) + protocol — served by gateway instances spread across availability zones 1, 2 and 3. Because the fleet is zone-redundant and autoscaled, whichever instances are running answer; if a whole zone fails, the surviving two keep answering with no config change. The listener hands off to a routing rule, which selects a backend HTTP setting (protocol, port, timeout) and a backend pool (App Service, VMSS, or VMs), while a health probe runs continuously so the rule only forwards to healthy members.

The diagram marks where a build goes wrong. The numbered badges sit on the failure-prone hops: the NSG (where the 65200–65535 rule must live), the listener and rule (a missing rule means nothing answers), the health probe (a host/path mismatch marks a healthy backend unhealthy), and the backend pool (where a 502 originates). The legend narrates each as a checkpoint — purpose, how to confirm, and the fix. Read the path once, then keep the legend beside the lab.

Left-to-right architecture of a zone-redundant, autoscaling Application Gateway v2: client over HTTPS to a static zone-redundant public IP, into a dedicated AppGateway subnet with an NSG allowing GatewayManager 65200-65535, gateway instances spread across availability zones 1, 2 and 3 with autoscaling min 2 to max 10, a listener and routing rule selecting a backend HTTP setting and backend pool, a health probe gating the App Service / VM scale set backend, with numbered badges on the NSG, listener/rule, health probe and backend pool failure points

Real-world scenario

Northwind Retail runs an e-commerce site for the Indian market on Azure in Central India, where availability zones are supported. The storefront and checkout API run on an App Service plan with three instances, fronted by a single Standard (v1) Application Gateway created two years earlier at a fixed two instances. It worked — until a power-distribution fault took down one zone for about twenty minutes during a Friday-evening sale. The App Service backend, spread across zones, never went down. But the v1 gateway’s instances sat in the affected zone, the VIP went dark, and the storefront returned nothing. The finding was blunt: the gateway was the only non-redundant hop in an otherwise resilient stack, and v1 could not be made zone-redundant.

The fix was a clean rebuild on Standard_v2. Krithika, the platform engineer, stood up a new gateway in a fresh /24 subnet with zones [1,2,3] and autoscaling at minimum 2, maximum 12, attached a new Standard, zone-redundant static public IP, re-pointed the App Service backend pool, copied the listener, HTTP setting, health probe (/healthz on the App Service host) and routing rule, and validated backend health before touching DNS. The cutover was a single DNS A-record change with a short TTL at low traffic; build-and-validate took under an hour and the swing completed within the TTL.

The payoff showed up twice in the first month. A planned Azure platform update rolled the instances zone by zone — with three across three zones and a minimum of two, capacity never dropped, so there was no maintenance window. Then a Diwali campaign drove a 4x spike on a Sunday evening; autoscaling took the gateway from two instances to seven over about eight minutes, latency stayed flat, and it scaled back to two by midnight. Nobody was paged for either. The one wrinkle was self-inflicted: she had pre-created an NSG locked to 443 from Internet, and the gateway provisioned into Failed — the fix was the rule everyone learns once, inbound allow TCP 65200–65535 from GatewayManager, after which it came up healthy across all three zones. The runbook lesson: zone redundancy and autoscaling are creation-time decisions, so the rebuild was unavoidable but cheap — and the time to do it was before the next sale, not during it.

Advantages and disadvantages

Advantages Disadvantages
Survives a single availability-zone failure with no config change Zone/SKU choice is fixed at creation — wrong choice means a rebuild
Autoscales with load; no manual sizing or paging Scale-out takes minutes, not seconds — must size the floor for bursts
Rolling platform updates cause no capacity dip (with ≥2 instances) Requires a dedicated /24 subnet you cannot reuse for anything else
Static, zone-redundant VIP — DNS never has to change WAF_v2 adds real cost vs Standard_v2; pay only if you need it
Pay for actual usage (Capacity Units), capped by your maximum Fixed hourly base charge even at the floor; not free when idle
L7 features: path/host routing, TLS termination, header rewrite, WAF More moving parts than an L4 load balancer; steeper to operate

When each side matters: the advantages dominate for any public, user-facing workload with uneven traffic where an outage costs revenue or reputation — zone redundancy and autoscaling pay for themselves the first time a zone blips or a campaign spikes. The disadvantages bite hardest on small, steady, internal workloads: if traffic is flat and L4 zone redundancy is enough, you are paying base fee and complexity for layer-7 features you do not use — there the Azure Load Balancer is the cheaper, simpler resilient choice. The most painful disadvantage is the creation-time immutability of zones and SKU: plan both before the first command.

Hands-on lab

This is the centrepiece: an end-to-end build of a zone-redundant, autoscaling Standard_v2 gateway, three ways — portal, az CLI, and Bicep — each with validation and teardown. Do the CLI path first (fastest to verify), then the portal and Bicep as alternatives producing the same result. All three deploy into Central India (any zone-enabled region works) and front a tiny backend. Expected output is shown at each step.

Lab prerequisites

Requirement How to get / check
Azure subscription with rights to create networking + gateway az account show returns your subscription
Azure CLI ≥ 2.50 (or Cloud Shell) az version
A zone-enabled region Use centralindia, eastus, westeurope, etc.
A backend to target This lab creates two tiny VMs; an App Service works too
~₹200–400 of spend tolerance for the lab duration Tear down at the end (final section)

Set shell variables once (CLI path):

# Lab variables — adjust REGION to any zone-enabled region
RG=rg-agw-lab
LOC=centralindia
VNET=vnet-agw-lab
AGW_SUBNET=AppGatewaySubnet
BACKEND_SUBNET=snet-backend
PIP=pip-agw-lab
NSG=nsg-agw-lab
AGW=agw-lab

Step 1 — Resource group and virtual network

Create the resource group, VNet, the dedicated /24 gateway subnet, and a separate backend subnet.

az group create --name $RG --location $LOC

az network vnet create \
  --resource-group $RG --name $VNET \
  --address-prefix 10.20.0.0/16 \
  --subnet-name $AGW_SUBNET --subnet-prefix 10.20.0.0/24

az network vnet subnet create \
  --resource-group $RG --vnet-name $VNET \
  --name $BACKEND_SUBNET --address-prefix 10.20.1.0/24

Expected output: each returns JSON with "provisioningState": "Succeeded". The gateway subnet is 10.20.0.0/24 (256 addresses — the recommended size).

Step 2 — NSG with the mandatory platform rule

Create an NSG, add the mandatory GatewayManager rule plus the AzureLoadBalancer and client (80/443) rules, then attach it to the gateway subnet. The step everyone skips and regrets.

az network nsg create --resource-group $RG --name $NSG

# MANDATORY: v2 platform management/health channel
az network nsg rule create --resource-group $RG --nsg-name $NSG \
  --name Allow-GatewayManager --priority 100 --direction Inbound --access Allow \
  --protocol Tcp --source-address-prefixes GatewayManager \
  --destination-port-ranges 65200-65535 \
  --destination-address-prefixes '*' --source-port-ranges '*'

# MANDATORY: Azure Load Balancer health probe
az network nsg rule create --resource-group $RG --nsg-name $NSG \
  --name Allow-AzureLB --priority 110 --direction Inbound --access Allow \
  --protocol '*' --source-address-prefixes AzureLoadBalancer \
  --destination-port-ranges '*' \
  --destination-address-prefixes '*' --source-port-ranges '*'

# Client traffic to your listeners (tighten the source for non-public apps)
az network nsg rule create --resource-group $RG --nsg-name $NSG \
  --name Allow-Client-HTTP --priority 120 --direction Inbound --access Allow \
  --protocol Tcp --source-address-prefixes Internet \
  --destination-port-ranges 80 443 \
  --destination-address-prefixes '*' --source-port-ranges '*'

# Attach the NSG to the gateway subnet
az network vnet subnet update --resource-group $RG --vnet-name $VNET \
  --name $AGW_SUBNET --network-security-group $NSG

Expected output: each rule returns JSON with its priority; the subnet update shows networkSecurityGroup populated. If the gateway ever provisions to Failed, re-check this first.

Step 3 — Standard, zone-redundant static public IP

Create the VIP — Standard SKU, static, zone-redundant (omit --zone for the zone-redundant default in a zone region).

az network public-ip create \
  --resource-group $RG --name $PIP \
  --sku Standard --allocation-method Static \
  --version IPv4

Expected output: JSON with "sku": { "name": "Standard" }, "publicIPAllocationMethod": "Static", and an assigned ipAddress. Note the IP — clients will resolve to it.

Step 4 — A minimal backend (two tiny VMs)

Create two small Linux VMs running a web server so the gateway has something healthy to forward to. (Skip if you already have an App Service or VMSS backend — use its address in Step 5.)

for i in 1 2; do
  az vm create \
    --resource-group $RG --name vm-backend-$i \
    --image Ubuntu2204 --size Standard_B1s \
    --vnet-name $VNET --subnet $BACKEND_SUBNET \
    --admin-username azureuser --generate-ssh-keys \
    --public-ip-address "" \
    --custom-data 'cloud-init.txt' 2>/dev/null || \
  az vm create \
    --resource-group $RG --name vm-backend-$i \
    --image Ubuntu2204 --size Standard_B1s \
    --vnet-name $VNET --subnet $BACKEND_SUBNET \
    --admin-username azureuser --generate-ssh-keys \
    --public-ip-address ""
done

# Install nginx + a /healthz on each (run-command, no SSH needed)
for i in 1 2; do
  az vm run-command invoke --resource-group $RG --name vm-backend-$i \
    --command-id RunShellScript \
    --scripts "sudo apt-get update && sudo apt-get install -y nginx && echo 'ok' | sudo tee /var/www/html/healthz && echo \"backend vm-$i\" | sudo tee /var/www/html/index.html && sudo systemctl enable --now nginx"
done

# Capture the two backend private IPs
BACKEND_IPS=$(az vm list-ip-addresses --resource-group $RG \
  --query "[].virtualMachine.network.privateIpAddresses[0]" -o tsv | tr '\n' ' ')
echo "Backend IPs: $BACKEND_IPS"

Expected output: two VMs (no public IPs — they sit behind the gateway), each running nginx serving index.html and /healthz. BACKEND_IPS holds two 10.20.1.x addresses.

Step 5 — Create the gateway (the core command)

This single command creates the gateway with zone redundancy and autoscaling and wires the default listener, pool, HTTP setting and rule.

az network application-gateway create \
  --resource-group $RG --name $AGW \
  --location $LOC \
  --sku Standard_v2 \
  --min-capacity 2 --max-capacity 10 \
  --zones 1 2 3 \
  --public-ip-address $PIP \
  --vnet-name $VNET --subnet $AGW_SUBNET \
  --servers $BACKEND_IPS \
  --frontend-port 80 \
  --http-settings-port 80 \
  --http-settings-protocol Http \
  --priority 100

The load-bearing step — what each flag does:

Flag Value here What it controls Change it when
--sku Standard_v2 The v2 SKU (autoscale + zones) WAF_v2 if you need the firewall
--min-capacity 2 Autoscale floor (always-on instances) Higher for spiky traffic
--max-capacity 10 Autoscale ceiling (cost cap) Raise as peak grows (max 125)
--zones 1 2 3 Zone-redundant placement Single value to pin; omit for non-zonal
--public-ip-address $PIP The frontend VIP Your Standard static IP
--subnet AppGatewaySubnet The dedicated /24 Always the gateway-only subnet
--servers backend IPs Initial backend pool members FQDN/App Service host as needed
--frontend-port 80 Listener port 443 for HTTPS (needs a cert)
--http-settings-port 80 Port the gateway uses to reach backend Match the backend’s listening port
--priority 100 Routing-rule priority (v2 requires it) Unique per rule

Expected output: the command runs for several minutes (5–15 min is normal), then returns the gateway JSON. Confirm the key properties:

az network application-gateway show --resource-group $RG --name $AGW \
  --query "{sku:sku, zones:zones, state:provisioningState, op:operationalState}" -o json

You want "provisioningState": "Succeeded", "operationalState": "Running", "zones": ["1","2","3"], and sku.name/sku.tier = Standard_v2 with no fixed capacity (autoscale lives in the autoscale config, shown next).

Step 6 — Confirm autoscaling and zones took effect

Two checks prove the resilience you configured exists.

# Autoscale min/max
az network application-gateway show --resource-group $RG --name $AGW \
  --query "autoscaleConfiguration" -o json
# Expect: { "minCapacity": 2, "maxCapacity": 10 }

# Zones
az network application-gateway show --resource-group $RG --name $AGW \
  --query "zones" -o json
# Expect: [ "1", "2", "3" ]
What you check Command query Healthy value If wrong
Autoscale bounds autoscaleConfiguration minCapacity 2, maxCapacity 10 You set fixed capacity instead — recreate with --min/--max
Zone spread zones ["1","2","3"] Empty/single → not zone-redundant; recreate with --zones 1 2 3
SKU sku.name Standard_v2 Standard → you got v1; recreate with --sku Standard_v2
State operationalState Running Stopped/Failed → check NSG rule, subnet size

Step 7 — Validate backend health (the most important check)

Creating the gateway is not the same as it working. Confirm the backend pool is Healthy — the command that tells you traffic will flow.

az network application-gateway show-backend-health \
  --resource-group $RG --name $AGW \
  --query "backendAddressPools[].backendHttpSettingsCollection[].servers[].{address:address, health:health}" -o table

Expected output: a row per backend with Health = Healthy. If Unhealthy, jump to troubleshooting — usual causes are a probe host mismatch, the backend not listening, or an NSG on the backend subnet blocking the gateway.

Now hit the public IP and confirm a real response:

AGW_IP=$(az network public-ip show --resource-group $RG --name $PIP --query ipAddress -o tsv)
curl -s http://$AGW_IP/         # Expect: "backend vm-1" / "backend vm-2" (alternating)
curl -s http://$AGW_IP/healthz  # Expect: "ok"

Hitting it a few times alternates between the two backends — proof the pool, rule, HTTP setting and probe are wired correctly.

Step 8 — Add an explicit health probe (production hygiene)

The default probe checks the backend’s root path. In production, point it at a real health endpoint (/healthz) and set the host correctly. Create a custom probe and bind it to the HTTP setting.

# Custom probe against /healthz
az network application-gateway probe create \
  --resource-group $RG --gateway-name $AGW \
  --name probe-healthz --protocol Http --path /healthz \
  --host-name-from-http-settings true \
  --interval 30 --timeout 30 --threshold 3

# Point the existing HTTP setting at the probe
az network application-gateway http-settings update \
  --resource-group $RG --gateway-name $AGW \
  --name appGatewayBackendHttpSettings \
  --probe probe-healthz
Probe setting Value Meaning Gotcha
--path /healthz What the probe requests Must return 200–399; not a slow page
--host-name-from-http-settings true Use the HTTP setting’s host for the probe Host header Wrong host → backend (esp. App Service) returns 404 → Unhealthy
--interval 30 (sec) How often to probe Too short adds load; too long slows eviction
--timeout 30 (sec) How long to wait per probe Shorter than backend’s slow path → false unhealthy
--threshold 3 Consecutive successes to mark Healthy Higher rides transient blips

Re-run show-backend-health and confirm still Healthy against the new probe.

Step 9 — Validate zone resilience and autoscale behaviour

You cannot trigger a real zone outage, but you can confirm the config that guarantees survival and watch autoscale move.

# Live capacity-unit consumption (proxy for instance count)
AGW_ID=$(az network application-gateway show -g $RG -n $AGW --query id -o tsv)
az monitor metrics list --resource "$AGW_ID" \
  --metric "CurrentCapacityUnits" --interval PT1M --aggregation Average -o table

# Generate sustained load to watch autoscale move
for i in $(seq 1 5000); do curl -s -o /dev/null http://$AGW_IP/ & done; wait

Expected output: under sustained load, CurrentCapacityUnits (and the instance count) climbs toward the maximum, then settles back to the minimum minutes after load stops. The zones confirmed in Step 6 are the resilience guarantee — with ["1","2","3"] and a minimum of 2, instances span zones and a single-zone loss leaves serving capacity.

Metric What it tells you Healthy pattern
CurrentCapacityUnits Live CU consumption Tracks load; within min/max
CurrentCompute(Units) Compute-dimension load Below ceiling at peak
Throughput Bytes/sec served Matches expected traffic
HealthyHostCount Backends in rotation Equals your pool size
UnhealthyHostCount Backends evicted 0 in steady state
FailedRequests 502/5xx from the gateway Near 0; spikes signal backend trouble

Step 10 — The portal path (same result, clickable)

To build the same gateway in the portal:

# Portal action Field / value
1 Create a resource → search Application Gateway → Create
2 Basics → Tier Standard V2 (not “Standard”)
3 Basics → Enable autoscaling Yes; Min 2, Max 10
4 Basics → Availability zone Select 1, 2, 3 (zone-redundant)
5 Basics → Virtual network / Subnet vnet-agw-lab / AppGatewaySubnet
6 Frontends → Frontend IP type Public; create/select the Standard static IP
7 Backends → Add a backend pool Add the two backend IPs (or App Service FQDN)
8 Configuration → Add a routing rule Listener: port 80; Priority 100
9 Routing rule → Backend targets → HTTP settings Port 80, protocol HTTP; add the /healthz probe
10 Review + create → wait for deployment ~5–15 min; then check Backend health blade = Healthy

The portal’s Backend health blade is the GUI equivalent of show-backend-health — green/Healthy is the goal. The Availability zone field is greyed out after creation — the portal making the immutability concrete.

Step 11 — The Bicep path (declarative, repeatable)

The same gateway as Bicep — the artifact you actually keep; portal and CLI are for one-offs and learning.

@description('Location with availability zones')
param location string = resourceGroup().location
param vnetName string = 'vnet-agw-lab'
param agwSubnetName string = 'AppGatewaySubnet'
param agwName string = 'agw-lab'
param pipName string = 'pip-agw-lab'
@description('Backend server IPs or FQDNs')
param backendAddresses array = ['10.20.1.4', '10.20.1.5']
param minCapacity int = 2
param maxCapacity int = 10

resource pip 'Microsoft.Network/publicIPAddresses@2023-11-01' = {
  name: pipName
  location: location
  sku: { name: 'Standard' }          // Standard required for v2
  zones: ['1', '2', '3']             // zone-redundant VIP
  properties: { publicIPAllocationMethod: 'Static' }
}

resource vnet 'Microsoft.Network/virtualNetworks@2023-11-01' existing = {
  name: vnetName
}
resource agwSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = {
  parent: vnet
  name: agwSubnetName
}

resource agw 'Microsoft.Network/applicationGateways@2023-11-01' = {
  name: agwName
  location: location
  zones: ['1', '2', '3']             // IMMUTABLE: zone-redundant placement
  properties: {
    sku: {
      name: 'Standard_v2'            // v2 SKU — enables autoscale + zones
      tier: 'Standard_v2'
    }
    autoscaleConfiguration: {        // autoscaling floor/ceiling
      minCapacity: minCapacity
      maxCapacity: maxCapacity
    }
    gatewayIPConfigurations: [
      {
        name: 'appGatewayIpConfig'
        properties: { subnet: { id: agwSubnet.id } }
      }
    ]
    frontendIPConfigurations: [
      {
        name: 'appGwPublicFrontendIp'
        properties: { publicIPAddress: { id: pip.id } }
      }
    ]
    frontendPorts: [
      { name: 'port80', properties: { port: 80 } }
    ]
    backendAddressPools: [
      {
        name: 'backendPool'
        properties: {
          backendAddresses: [for a in backendAddresses: { ipAddress: a }]
        }
      }
    ]
    probes: [
      {
        name: 'probe-healthz'
        properties: {
          protocol: 'Http'
          path: '/healthz'
          interval: 30
          timeout: 30
          unhealthyThreshold: 3
          pickHostNameFromBackendHttpSettings: true
        }
      }
    ]
    backendHttpSettingsCollection: [
      {
        name: 'httpSettings'
        properties: {
          port: 80
          protocol: 'Http'
          requestTimeout: 30
          pickHostNameFromBackendAddress: false
          probe: {
            id: resourceId('Microsoft.Network/applicationGateways/probes', agwName, 'probe-healthz')
          }
        }
      }
    ]
    httpListeners: [
      {
        name: 'listener80'
        properties: {
          frontendIPConfiguration: {
            id: resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations', agwName, 'appGwPublicFrontendIp')
          }
          frontendPort: {
            id: resourceId('Microsoft.Network/applicationGateways/frontendPorts', agwName, 'port80')
          }
          protocol: 'Http'
        }
      }
    ]
    requestRoutingRules: [
      {
        name: 'rule1'
        properties: {
          ruleType: 'Basic'
          priority: 100              // v2 requires a unique priority
          httpListener: {
            id: resourceId('Microsoft.Network/applicationGateways/httpListeners', agwName, 'listener80')
          }
          backendAddressPool: {
            id: resourceId('Microsoft.Network/applicationGateways/backendAddressPools', agwName, 'backendPool')
          }
          backendHttpSettings: {
            id: resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection', agwName, 'httpSettings')
          }
        }
      }
    ]
  }
}

Deploy and validate:

az deployment group create --resource-group $RG \
  --template-file appgw.bicep \
  --parameters backendAddresses="['10.20.1.4','10.20.1.5']"

# Same health check as the CLI path
az network application-gateway show-backend-health -g $RG -n $AGW -o table

Expected output: provisioningState: Succeeded and the backend health table shows Healthy. The zones and autoscaleConfiguration blocks are the two that make this resilient and elastic — note zones is a top-level property of the gateway resource, not inside properties.

Step 12 — Teardown

The gateway accrues an hourly charge, so delete the whole resource group when you finish.

az group delete --name $RG --yes --no-wait

Expected output: the command returns immediately (--no-wait); deletion of the gateway, IP, VMs, VNet and NSG completes in the background. Confirm later with az group exists --name $RG returning false.

Teardown step Command Note
Delete everything az group delete -n $RG --yes --no-wait Cleanest; removes all lab resources
Keep VNet, drop gateway only az network application-gateway delete -g $RG -n $AGW If reusing the network
Confirm gone az group exists -n $RG Returns false when complete

Common mistakes & troubleshooting

The failure modes below are the ones that turn a thirty-minute build into a long afternoon. Each is symptom → root cause → exact confirm → fix.

# Symptom Root cause Confirm (exact path/command) Fix
1 Gateway provisions to Failed/unhealthy, app rules look fine NSG on gateway subnet blocks platform mgmt az network nsg rule list -g $RG --nsg-name $NSG -o table — no 65200–65535 allow Add inbound Allow TCP 65200–65535 from GatewayManager
2 --zones had no effect / gateway not zone-redundant Created without --zones, or region lacks zones az network application-gateway show ... --query zones returns [] Recreate with --zones 1 2 3 in a zone-enabled region (immutable)
3 Autoscale flags ignored; fixed instance count Set --capacity (fixed) instead of --min/--max-capacity ... --query autoscaleConfiguration is null Recreate with --min-capacity/--max-capacity (no --capacity)
4 Got v1 features, no autoscale option Used --sku Standard not Standard_v2 ... --query sku.name = Standard Recreate with --sku Standard_v2
5 Backend health = Unhealthy, 502 to clients Probe Host header wrong (esp. App Service multi-tenant) show-backend-health Unhealthy; probe returns 404 Set --host-name-from-http-settings true or a correct --host-name
6 502 from gateway, backend up HTTP setting port ≠ backend’s listening port ... http-settings list --query "[].port" vs backend port Align --http-settings-port to the backend
7 Autoscale/upgrade fails after a few instances Gateway subnet too small (/28) Subnet prefix shows /28//29 Recreate in a /24 (subnet can’t grow under a live gateway easily)
8 Backend Unhealthy though probe path is right NSG on backend subnet blocks the gateway Backend NSG has no allow from the gateway subnet/VirtualNetwork Allow the gateway subnet CIDR to the backend port
9 Listener exists but nothing answers No routing rule joining listener → pool ... rule list -o table empty or unlinked Add a Basic rule with listener + HTTP setting + pool + priority
10 Public IP not supported error on create Basic SKU or dynamic public IP ... public-ip show --query "sku.name" = Basic Recreate the IP as Standard, Static
11 Rule create fails: priority required v2 routing rules require a unique priority Create error mentions priority Add --priority <unique int> to each rule
12 TLS listener fails / cert error Cert not from Key Vault or MI lacks access Listener config / Key Vault access policy Grant the gateway’s managed identity get on the secret (see end-to-end TLS guide)

Three notes. #1 (GatewayManager) is by far the most common — 65200–65535 is internal control-plane traffic, not an internet exposure; check it first when the gateway is unhealthy and app config looks fine. #5 (probe host) bites hardest with App Service, which routes by Host header — a wrong host hits the wrong site, gets a 404, and marks a healthy backend Unhealthy; pickHostNameFromBackendHttpSettings fixes it. #2, #3, #4 (the immutable trio) share one fix — recreate.

When a backend is Unhealthy, this table localises it fast:

If you see… It’s probably… Do this
All backends Unhealthy, app is up Probe host/path wrong, or backend NSG Check probe Host header; check backend-subnet NSG
Some Unhealthy, some Healthy Those specific instances down/misconfigured Inspect the failing members; restart/repair
Healthy in portal but clients get 502 HTTP setting port or protocol mismatch Align HTTP setting to the backend’s port/protocol
Gateway itself Failed/Stopped NSG blocks 65200–65535, or subnet too small Add GatewayManager rule; verify /24 subnet

Best practices

Security notes

A gateway is an internet-facing front door; treat it like one.

Cost & sizing

A v2 gateway has two cost components: a fixed hourly charge (the gateway exists even idle) plus a Capacity Unit (CU) charge for the work it does. WAF_v2 carries higher fixed and per-CU rates; there is no separate charge for zone redundancy. Figures below are rough, region-dependent and for planning only — confirm against the pricing calculator.

Cost driver Standard_v2 (rough) WAF_v2 (rough) Notes
Fixed gateway hour ~$0.20–0.25 /hr ~$0.35–0.45 /hr Charged whenever the gateway exists
Capacity Unit hour ~$0.008 /CU-hr ~$0.014 /CU-hr Billed on actual CU consumption
Minimum floor (2 instances ≈ ~20 CU baseline) adds to the bill adds to the bill Your always-on cost
Public IP (Standard) ~$0.005 /hr + data same Small, but real
Data processing included in CU throughput dim included Throughput is one CU dimension

Worked monthly estimate (Standard_v2, light traffic, floor of 2, ~730 hrs):

Line item Estimate (USD/mo) Estimate (INR/mo)
Fixed gateway hours (~$0.22 × 730) ~$160 ~₹13,300
Baseline CU (floor, light load) ~$15–40 ~₹1,250–3,300
Public IP + misc ~$4 ~₹330
Approx total (Standard_v2, light) ~$180–205 ~₹15,000–17,000

Sizing guidance:

Workload Min / Max capacity SKU Rationale
Dev / throwaway 0 / 2 Standard_v2 Cost-minimal; cold start acceptable
Small steady prod 2 / 5 Standard_v2 Multi-zone floor, modest ceiling
Mid-size, spiky 2 / 12 Standard_v2 / WAF_v2 Burst floor + room for campaigns
Large public, compliance 3 / 25+ WAF_v2 WAF required; higher floor + ceiling

Two cost rules: the floor is your real baseline — a minimum of 2 costs the same whether or not traffic uses it, so do not over-set it — and WAF_v2 roughly adds 50–80% to the fixed and per-CU rate, so pay it only when you need OWASP protection here. Right-size by watching CurrentCapacityUnits over a representative week and setting the maximum a comfortable margin above the observed peak.

Interview & exam questions

1. Why can Application Gateway v1 not be made zone-redundant or autoscaling? The v1 SKUs run on an older platform with a fixed instance count and no zone-distribution. Autoscaling, zone redundancy and static VIPs are exclusive to the v2 platform (Standard_v2/WAF_v2). Moving v1→v2 is a migration to a new resource, not an in-place toggle. (AZ-700)

2. A gateway provisions to a Failed state but the application config is correct. Most likely cause? An NSG on the gateway subnet is blocking the platform’s management channel. v2 requires inbound TCP 65200–65535 from the GatewayManager service tag; without it the platform cannot manage or probe the instances. Add that rule. (AZ-700, AZ-104)

3. Can you change a deployed gateway from non-zonal to zone-redundant? No. Zone configuration is set at creation and is immutable. To become zone-redundant you create a new gateway with zones [1,2,3] (typically side by side) and cut over via DNS. The same immutability applies to the SKU tier. (AZ-700)

4. What size should the dedicated gateway subnet be, and why? /24 recommended (minimum /26). The gateway consumes addresses per instance, and autoscaling plus rolling upgrades need spare addresses to provision new instances before draining old ones. A /28 runs out once you have several instances. (AZ-104, AZ-700)

5. What is a Capacity Unit and how does it relate to billing? A CU bundles compute (≈50 conn/sec), persistent connections (≈2,500) and throughput (≈2.22 Mbps). The gateway scales to satisfy the most-stressed dimension; you are billed a fixed hourly charge plus a per-CU-hour charge on actual consumption — not on your maximum. (AZ-700)

6. Backend is up, but the gateway reports it Unhealthy and clients get 502. First check? The health probe’s Host header, especially with App Service or other multi-tenant backends that route by host — a wrong host hits the wrong site and gets a 404. Set pickHostNameFromBackendHttpSettings (with the correct FQDN) or an explicit probe host. (AZ-700)

7. How do you set the autoscale floor and ceiling, and what does each control? Via --min-capacity/--max-capacity (or the autoscaleConfiguration block in Bicep) on a v2 SKU. The minimum is the always-on floor (baseline cost / multi-zone presence); the maximum (up to 125) is the ceiling and cost cap. Do not set a fixed capacity if you want autoscaling. (AZ-700)

8. When would you choose Standard_v2 over WAF_v2? When you do not need a WAF at this layer — an internal app with trusted clients, or a public app where WAF lives at the edge (Front Door). Standard_v2 is cheaper and identical for zones and autoscaling; WAF_v2 is required for OWASP CRS protection at the gateway. (AZ-700)

9. Does zone redundancy protect against a region outage? No — it protects against a single zone failure within one region. Surviving a full region outage requires a second gateway in a second region behind a global router (Traffic Manager or Front Door). (AZ-305, AZ-700)

10. Why must the public IP be Standard SKU and static for a v2 gateway? v2 only supports Standard-SKU static public IPs; Basic and dynamic are rejected at creation. For true zone redundancy the Standard IP should also be zone-redundant, so the VIP survives a zone loss with the instances. (AZ-700, AZ-104)

11. The gateway is hitting its autoscale maximum during a campaign and latency is rising. What do you do? Raise the maximum (up to 125) so the platform can add instances, and confirm the backend can absorb the extra traffic. Investigate whether the CU pressure is compute, connections or throughput. Alert on nearing the ceiling so you act before it throttles. (AZ-700)

12. Difference between a routing rule, a listener, an HTTP setting and a backend pool? The listener is the frontend IP+port+protocol where requests arrive; the backend pool is the targets; the HTTP setting is how the gateway talks to the backend (protocol, port, timeout, probe); the routing rule joins listener → HTTP setting → pool (and in v2 carries a required priority). All four plus a probe must exist and be linked for traffic to flow. (AZ-700)

Quick check

  1. Which two capabilities require the v2 SKU and cannot be added to a v1 gateway in place?
  2. What inbound NSG rule is mandatory on the gateway subnet, and why?
  3. Can you change a running gateway from non-zonal to zone-redundant? If not, what do you do?
  4. What is the recommended size of the dedicated gateway subnet, and what breaks if it is a /28?
  5. A healthy backend shows as Unhealthy and clients get 502 — what is the first thing to check?

Answers

  1. Autoscaling and zone redundancy (also static VIP). Both are exclusive to Standard_v2/WAF_v2; v1 is fixed-capacity, single-zone. Moving to them is a recreate, not a toggle.
  2. Inbound Allow TCP 65200–65535 from the GatewayManager service tag — the v2 platform’s management/health channel. Without it the gateway cannot be managed and provisions to a Failed/unhealthy state.
  3. No — zone configuration is immutable at creation. Create a new gateway with zones [1,2,3] (often side by side) and cut over via a DNS A-record change.
  4. /24 recommended (/26 minimum). A /28 runs out of addresses for autoscale and rolling upgrades once you have several instances, so scale-out and platform updates fail.
  5. The health probe’s Host header (and path). A wrong host hits the wrong site on a multi-tenant backend (App Service) and returns 404, marking it Unhealthy. Use pickHostNameFromBackendHttpSettings with the correct FQDN.

Glossary

Next steps

AzureApplication GatewayLoad BalancingAvailability ZonesAutoscalingWAFNetworkingBicep
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading