Networking Azure

ExpressRoute Deep Dive: Private Peering, Route Filters, and VPN Failover

At 02:00 a backhoe three states away cuts a fiber bundle into a colocation meet-me room, and a “redundant” ExpressRoute circuit that had run flawlessly for two years goes dark on one link. If the design was right, nobody notices: the second link inside the same circuit carries every packet and the on-call engineer sleeps. If the design was subtly wrong — a second BGP session that quietly went down weeks ago, a VPN backup sized at half the circuit’s throughput, an AS-path policy that never actually made the backup less preferred — the incident that follows is the one people write post-mortems about. ExpressRoute gives you a private, predictable Layer-3 path into Azure that never touches the public internet, but the resiliency story only holds together if BGP is configured deliberately, every failure domain is understood, and the backup path is tested for capacity and not merely reachability.

This is the design deep-dive for that resilience. We will build the model from the primitives up — circuit, peering location, peering, and the two MSEE connections that every circuit carries whether you asked for them or not — then work through the BGP that makes traffic move the way you intend: the MSEE ASN 12076, your peer ASN, MED and AS-path prepend for the Azure-to-on-prem direction, and weight and local-preference for the on-prem-to-Azure direction. From there we design the whole resiliency ladder: a single resilient circuit, active-active vs active-passive patterns with a Site-to-Site VPN as backup, ExpressRoute Global Reach for datacenter-to-datacenter over the Microsoft backbone, and maximum resiliency with dual circuits at two different peering locations. We enable FastPath to take the gateway out of the data path, control advertised prefixes with route filters (Microsoft peering) and BGP policy (private peering), and — the part most teams skip — we test failover, measure BGP convergence, and stand up Connection Monitor so the next fiber cut is a graph, not a surprise.

Every design decision here comes with the exact az CLI, the Terraform, and a vendor-neutral Cisco IOS-style router snippet, because the failover behaviour you want lives half in Azure and half on your edge. And because this is a reference you will reopen the night a link drops, the trade-offs, the BGP attributes, the SKU limits, the resiliency tiers, and the failure modes are all laid out as scannable tables — read the prose once, keep the tables open when it matters. By the end you will be able to design an ExpressRoute topology to a stated availability target, defend every routing knob to an architecture review, and prove your failover works with a number instead of a hope.

What problem this solves

A single ExpressRoute circuit, correctly provisioned, already carries two independent BGP sessions to two Microsoft Enterprise Edge routers (MSEEs) — Microsoft engineers this redundancy into every circuit and you cannot turn it off. Teams read “redundant by default” and stop thinking, which is exactly where the trouble starts. That built-in redundancy protects against the failure of one MSEE or one link inside the meet-me room. It does not protect against: the peering location itself losing power, your provider’s equipment failing on both links, a fiber cut on your side of the cross-connect, a fat-fingered BGP change on your edge, or your provider’s backbone having a bad day. A design that treats “the circuit is redundant” as “we are covered” has one failure domain — the facility — sitting under everything, invisible until it fails.

What breaks without a deliberate resiliency design: a fiber cut at the peering location takes the whole circuit down (both links share that facility), and if there is no second circuit at a different location and no VPN backup, hybrid connectivity is simply gone until the provider restores service — which for a physical cut can be hours. Worse are the silent failures: one of the two BGP sessions drops and nobody alerts on it, so the circuit runs non-redundant for weeks; then the surviving MSEE goes into planned maintenance and everything fails at once, in a window Microsoft told you about and you didn’t connect to your own single-session state. Or the backup exists but was only ever tested with a ping — so when it takes over during a real cut, the VPN gateway’s aggregate throughput is a fraction of the circuit’s and batch jobs that ran in forty minutes are now projected at six hours.

Who hits this: every enterprise running production workloads over ExpressRoute — banks, payments platforms, healthcare, anyone whose hybrid path is load-bearing. It bites hardest on teams that treat ExpressRoute as “just a faster VPN” and never learn the BGP, teams whose backup VPN gateway SKU was chosen for cost without a throughput calculation, and teams that pass their quarterly failover drill because the drill only checks that traffic moves, never that it moves fast enough. The fix is never “buy a bigger circuit” in isolation — it is to enumerate every failure domain, put an independent path under each, make BGP prefer the paths in the order you intend, and test the whole thing under production-representative load.

To frame the entire field before the deep dive, here is every failure domain an ExpressRoute design must reason about, what it takes down, and the mitigation that removes it:

Failure domain What fails What it takes down Mitigation that removes it Residual risk after mitigation
One MSEE / one link in the facility A single MSEE or its cross-connect One of two BGP sessions Built into every circuit (primary + secondary) Both links still share the facility
Your edge router (one device) A single on-prem/colo router Its BGP session(s) Two edge routers, one per link Both routers may share a rack/power feed
Peering location (facility power/fiber) The whole meet-me room The entire circuit (both links) Second circuit at a different peering location Both circuits may share your provider’s backbone
Provider backbone Your provider’s network Both circuits if same provider Second circuit via a different provider Cost; operational complexity
Azure region The region hosting your VNets Workloads in that region Multi-region design; circuit reaches multiple regions Cross-region latency; data-plane design
ExpressRoute gateway The VNet gateway The VNet’s ER connectivity Zone-redundant gateway (ErGw*Az) Gateway is a shared throughput point (see FastPath)
Total ExpressRoute outage All circuits/paths All private-peering hybrid connectivity Site-to-Site VPN backup over the internet VPN throughput << circuit; must size it
BGP misconfiguration (human) A route policy change Whichever direction the policy governs Codify policy in IaC; test in a window Drift if changes are made by hand

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should already understand the ExpressRoute basics this deep-dive builds on: that ExpressRoute is a private Layer-3 connection into Azure through a connectivity provider, that a circuit has a SKU family (billing: MeteredData vs UnlimitedData) and a SKU tier (Standard vs Premium), and that private peering carries traffic to your VNets while Microsoft peering carries traffic to Microsoft public endpoints. If any of that is fuzzy, read ExpressRoute Fundamentals: Circuits, Private vs Microsoft Peering and Connectivity Models first — this article assumes it. You should be comfortable with BGP fundamentals (eBGP sessions, the attribute decision order, AS-path, local-preference, MED, weight), reading az JSON output, and enough of a router CLI to recognise a router bgp stanza.

This sits at the top of the hybrid connectivity track — it is the design and resiliency layer above the fundamentals. It pairs tightly with Production Site-to-Site VPN to Azure: Active-Active Gateways with BGP, because the VPN backup we design here is that article’s gateway pressed into a failover role. It connects upward to Azure Multi-Region Active-Active Architecture: Designing for Zero-Downtime (ExpressRoute resiliency is one pillar of a multi-region story) and to Hub-and-Spoke vs Virtual WAN: Choosing an Enterprise Cloud Network Topology (where the circuit terminates in your topology). For verification it leans on Diagnosing Connectivity with Network Watcher: Connection Monitor, Connection Troubleshoot and Next Hop, whose Connection Monitor is the tool we use to prove reachability end-to-end.

A quick map of who owns what during an ExpressRoute incident, so you page the right team fast rather than staring at the wrong console:

Layer What lives here Who usually owns it Failure classes it can cause
On-prem / colo edge router Your BGP config, route policy, MD5 Your network team Wrong direction preferred; session down on your side
Cross-connect / meet-me room Physical fiber, VLAN dot1q tagging Colo + provider Link down; VLAN/C-tag mismatch
Connectivity provider backbone Transport between you and MSEE Provider Both links down (same provider); latency
MSEE (Microsoft edge) ASN 12076, primary + secondary Microsoft (platform) One MSEE in maintenance; BGP from Azure side
ExpressRoute gateway ErGw*Az SKU, connection, weight Your team (platform) Gateway throughput cap; wrong connection weight
VNet / spokes Address space advertised over BGP App + platform Prefix overlap; route-limit breach
VPN gateway (backup) Coexistence, BGP, throughput Your team (platform) Backup undersized; wrong preference vs ER

Core concepts

Six mental models make every later design decision obvious.

A circuit is a logical resource, not a cable — and it is always dual-homed. The circuit is the Azure object representing your connectivity through a provider. It has a service key (s-key), a bandwidth (50 Mbps to 10 Gbps, or up to 100 Gbps on ExpressRoute Direct), a SKU family, a SKU tier, and a billing model. When you create it, Azure provisions two connections to two different MSEEs in the peering location — a primary and a secondary — and this is not optional and not a toggle. Every peering you configure on the circuit therefore comes up as two eBGP sessions, one to each MSEE. Your job is to terminate both and keep both Established. “The circuit is redundant” means link-redundant within the facility; it says nothing about the facility, your provider, or your edge.

The peering location is a physical facility, distinct from the Azure region. A peering location (e.g. “Silicon Valley”, “London2”, “Chennai”) is the colocation/meet-me facility where Microsoft’s edge and your provider interconnect. It is not an Azure region — a circuit in the “Washington DC” peering location can reach VNets in many regions (all regions in the geopolitical area on Standard; any region worldwide on Premium). The facility is your deepest single failure domain on one circuit: both the primary and secondary connections land in the same meet-me room. True path diversity means a second circuit at a different peering location.

BGP is the whole game, and it has two independent directions. ExpressRoute runs eBGP between your peer ASN and Microsoft’s MSEE ASN 12076. Traffic direction matters because different attributes govern each way. On-prem→Azure (the forward path) is decided by your routers using weight (Cisco-proprietary, evaluated first, local to one router) and local-preference (standard, evaluated early, propagated across your AS) — you make the ExpressRoute-learned routes more attractive than VPN-learned ones. Azure→on-prem (the return path) is decided by Azure/Microsoft choosing among the routes you advertise, influenced by AS-path length (prepend extra ASNs on the path you want less preferred) and MED (Multi-Exit Discriminator, a hint to a neighbouring AS about which entry point to prefer). Getting failover right means setting the correct knob for each direction — a design that only tunes one direction fails asymmetrically.

More-specific routes and longest-prefix-match are the tie-breaker. BGP’s first decision, before any attribute, is longest-prefix-match: a /24 always wins over a /16 for an address inside it, regardless of local-pref, AS-path, or weight. This is a tool and a trap. Advertising a more-specific prefix over your preferred path forces traffic onto it deterministically; advertising the same specific prefixes over both paths (correct for failover) lets your attribute policy choose while withdrawal handles the failure. Advertising a summary over one path and specifics over another accidentally pins traffic to the specifics forever — a classic self-inflicted “why is my backup carrying production” bug.

The gateway is a throughput point unless you bypass it. To reach VNets, a circuit connects to an ExpressRoute gateway — a VNet gateway of an ErGw* SKU (not a VPN gateway; a VNet can host both). The gateway sits in the data path and caps throughput at its SKU’s ceiling (from ~1 Gbps on ErGw1Az to ~10 Gbps on ErGw3Az, higher on scalable gateways). FastPath changes this: it programs the MSEE to send data-plane traffic directly to VMs in the VNet, bypassing the gateway; only control-plane (BGP) still flows through it. FastPath is how you get consistent low latency at circuit line-rate instead of gateway-limited throughput — but it has SKU prerequisites and it bypasses any UDR-forced NVA in that path, which you must decide consciously.

A backup path you tested only for reachability is not tested. ExpressRoute has no meaningful availability if a single facility outage takes you fully offline, so the supported backup is a Site-to-Site VPN over the internet, coexisting with the ExpressRoute gateway. The design invariant is ExpressRoute always preferred; VPN carries traffic only when ExpressRoute is down. The failure most teams don’t rehearse is capacity: a VPN gateway’s aggregate throughput and a single IPsec tunnel’s cap are both far below a multi-gigabit circuit, and a single TCP flow rides a single tunnel. Drive production-representative load across the backup in a window — ER preference keeps it idle in steady state, so the drill costs an iperf run, not an outage.

The vocabulary in one table

Before the deep sections, pin down every moving part. The glossary at the end repeats these for lookup; this table is the mental model side by side:

Concept One-line definition Where it lives Why it matters to resiliency
Circuit Logical ER resource through a provider Azure resource group Dual-homed by default; one facility under it
Service key (s-key) GUID mapping your cross-connect to the circuit Circuit property Provider needs it to provision
Peering location Physical meet-me facility Provider/colo Deepest single failure domain per circuit
Private peering BGP domain carrying VNet (RFC1918+) traffic On the circuit The peering your workloads use
Microsoft peering BGP domain to Microsoft public endpoints On the circuit Needs a route filter to advertise anything
MSEE (ASN 12076) Microsoft’s edge routers Peering location Two per circuit; the BGP neighbours
Connection (primary/secondary) The two links to two MSEEs On the circuit Both must be Established
ExpressRoute gateway ErGw* VNet gateway In the VNet Data-path throughput cap; bypass via FastPath
Connection weight Azure-side preference among gateways On the connection Higher wins → prefer ER over VPN outbound from Azure
Weight / local-pref On-prem BGP preference knobs Your routers Forward-path preference (ER over VPN)
AS-path prepend / MED Return-path preference hints Advertised routes Make the VPN path look worse to Azure
Global Reach DC-to-DC over the Microsoft backbone Circuit feature Connects on-prem sites without your WAN
FastPath Data-plane bypass of the gateway Connection setting Line-rate, low latency; bypasses UDR/NVA
Route filter Allow-list of BGP communities Microsoft peering Scopes which public prefixes are advertised
BFD Sub-second link-failure detection Per BGP session Cuts convergence from ~tens of s to sub-second

Circuits, peerings and the two-link contract

Everything starts with the circuit, and the first design decision — before any BGP — is SKU tier and family, because it silently caps two things you care about: reachability scope and the route limit.

The SKU family is your billing model. MeteredData bills a flat monthly circuit fee plus per-GB egress; UnlimitedData bills a higher flat fee with no per-GB charge. The break-even is purely a function of your monthly egress: high-volume egress favours UnlimitedData, spiky or modest egress favours MeteredData. You can move MeteredData → UnlimitedData in place, but not back — Azure blocks the downgrade — so start metered unless you already know you are a heavy egress shop.

The SKU tier controls reach and route limits. Standard reaches VNets within the same geopolitical region and accepts up to 4,000 IPv4 routes on private peering. Premium reaches VNets in any geopolitical region worldwide (global reach of the circuit, not to be confused with the Global Reach feature), raises the private-peering route limit to 10,000 IPv4 routes, and raises the number of VNets a circuit can connect to. Premium is an add-on charge on top of the circuit.

The SKU decision as a matrix — pick the row your requirements land in:

Requirement Standard Premium Notes
Reach VNets in the same geo Yes Yes Standard suffices for single-geo
Reach VNets in another geo No Yes Premium required for cross-geo VNet reach
Private-peering route limit 4,000 10,000 Breach drops the BGP session (not graceful)
Max connected VNets (scales with bandwidth) Lower Higher Premium raises the ceiling
Global Reach feature available Yes Yes Global Reach is separate from the SKU tier
Cost Base circuit Base + Premium add-on Premium is billed on top

And the family (billing) decision:

Dimension MeteredData UnlimitedData Change direction
Monthly base fee Lower Higher
Egress (outbound) charge Per-GB Included (no per-GB)
Ingress (inbound) charge Free Free Ingress is always free
Best for Modest/spiky egress Sustained high egress Metered → Unlimited only (one-way)
Break-even Compute from your GB/month Compute from your GB/month Model before committing

The circuit bandwidth is a separate axis — 50/100/200/500 Mbps, 1/2/5/10 Gbps on standard ExpressRoute, and up to 100 Gbps on ExpressRoute Direct (where you own the port pair into Microsoft’s edge). Bandwidth can be increased in place (subject to provider capacity) but not decreased; size with headroom because you cannot walk it back. The bandwidth is the aggregate across both links — it is not per-link — and it is the number your VPN backup must be sized against.

Provisioning states — the sequence that saves money

A circuit has two independent state machines you must both watch. The circuit provisioning state (circuitProvisioningState) is Azure’s view: Enabled once the resource exists. The service-provider provisioning state (serviceProviderProvisioningState) is the provider’s handoff: it starts NotProvisioned, and only your provider moves it to Provisioned after they map your physical cross-connect to the s-key. Do not configure peering until the provider flips to Provisioned — the BGP won’t come up and you’ll chase ghosts.

The states, what each means, and what you do in it:

State field Value Meaning Your action
circuitProvisioningState Enabled Azure resource created Retrieve the s-key, hand to provider
serviceProviderProvisioningState NotProvisioned Provider hasn’t wired it Wait; poll; don’t configure peering
serviceProviderProvisioningState Provisioning Provider is wiring it Wait; billing may start here
serviceProviderProvisioningState Provisioned Cross-connect mapped Now configure peering
serviceProviderProvisioningState Deprovisioning Provider tearing down Circuit being decommissioned

A billing gotcha worth internalising: billing starts when the circuit reaches Provisioned, not when you send your first packet. If the provider provisions well ahead of your readiness, you are paying for an idle circuit. Sequence the provider order to land close to when you can actually configure and test peering.

Create the circuit:

az network express-route create \
  --name erc-prod-svc \
  --resource-group rg-connectivity \
  --location eastus2 \
  --provider "Equinix" \
  --peering-location "Silicon Valley" \
  --bandwidth 1000 \
  --sku-family MeteredData \
  --sku-tier Standard

Retrieve the service key and current states:

az network express-route show \
  --name erc-prod-svc \
  --resource-group rg-connectivity \
  --query "{key:serviceKey, provider:serviceProviderProvisioningState, circuit:circuitProvisioningState, bandwidth:serviceProviderProperties.bandwidthInMbps}" \
  -o table

Poll the provider state until it flips (script this against your change window):

az network express-route show \
  --name erc-prod-svc \
  --resource-group rg-connectivity \
  --query serviceProviderProvisioningState -o tsv

The same circuit in Terraform, so the SKU and billing choice is reviewed and never drifts:

resource "azurerm_express_route_circuit" "prod" {
  name                  = "erc-prod-svc"
  resource_group_name   = azurerm_resource_group.conn.name
  location              = "eastus2"
  service_provider_name = "Equinix"
  peering_location      = "Silicon Valley"
  bandwidth_in_mbps     = 1000

  sku {
    tier   = "Standard"      # or "Premium" for cross-geo reach / 10k routes
    family = "MeteredData"   # or "UnlimitedData" — one-way change only
  }

  tags = { workload = "hybrid-core", tier = "prod" }
}

Private peering and the redundant BGP pair

Private peering is the peering your VNets ride, and standing it up correctly means bringing up both BGP sessions and keeping them up. It needs two point-to-point subnets — either two /30s (one primary, one secondary) or a single /29 that Azure splits — for the link addressing. Azure takes the first usable address in each subnet; your edge router takes the second. You supply your peer ASN (2-byte or 4-byte; private ASNs in the 64512–65534 range are fine on your side, as are the 4-byte private ranges), the VLAN ID (the C-tag / inner dot1Q that must match what your provider configures on the cross-connect — Microsoft peering, if used, takes a different VLAN), and in production an MD5 shared key to authenticate the sessions.

The parameters that must line up, and who owns each:

Parameter What it is Constraint / range Who must agree
--peering-type AzurePrivatePeering Which peering Fixed string
--peer-asn Your BGP ASN 2-byte or 4-byte; private ranges fine You choose
--primary-peer-subnet Primary link /30 Azure takes .1, you take .2 You + provider VLAN
--secondary-peer-subnet Secondary link /30 Azure takes .5, you take .6 You + provider VLAN
--vlan-id C-tag / inner dot1Q Must match cross-connect You + provider (exact match)
--shared-key MD5 secret Same on both ends You (set both sides)
Peer ASN uniqueness Same ASN both links Use one ASN for the circuit You

Create the peering:

az network express-route peering create \
  --circuit-name erc-prod-svc \
  --resource-group rg-connectivity \
  --peering-type AzurePrivatePeering \
  --peer-asn 65010 \
  --primary-peer-subnet 192.168.10.0/30 \
  --secondary-peer-subnet 192.168.10.4/30 \
  --vlan-id 100 \
  --shared-key "<md5-secret>"

The same in Terraform:

resource "azurerm_express_route_circuit_peering" "private" {
  peering_type                  = "AzurePrivatePeering"
  express_route_circuit_name    = azurerm_express_route_circuit.prod.name
  resource_group_name           = azurerm_resource_group.conn.name
  peer_asn                      = 65010
  primary_peer_address_prefix   = "192.168.10.0/30"
  secondary_peer_address_prefix = "192.168.10.4/30"
  vlan_id                       = 100
  shared_key                    = var.er_md5_secret   # keep out of state where possible
}

On your edge you bring up two eBGP sessions to MSEE ASN 12076, one per link. The example is Cisco IOS-style; translate to your platform. Note the MD5 password and the summarized network statement (more on summarization in the route-limit section):

! ----- Primary link (to MSEE #1) -----
interface GigabitEthernet0/1.100
 encapsulation dot1Q 100
 ip address 192.168.10.2 255.255.255.252
!
! ----- Secondary link (to MSEE #2) -----
interface GigabitEthernet0/2.100
 encapsulation dot1Q 100
 ip address 192.168.10.6 255.255.255.252
!
router bgp 65010
 bgp log-neighbor-changes
 address-family ipv4
  ! advertise a SUMMARY, not the whole table
  network 10.50.0.0 mask 255.255.0.0
  ! Primary
  neighbor 192.168.10.1 remote-as 12076
  neighbor 192.168.10.1 password <md5-secret>
  neighbor 192.168.10.1 activate
  ! Secondary
  neighbor 192.168.10.5 remote-as 12076
  neighbor 192.168.10.5 password <md5-secret>
  neighbor 192.168.10.5 activate
 exit-address-family

Both sessions must reach Established. Confirm ARP and BGP health from the Azure side, per link — the --path argument is how you check each MSEE independently:

# ARP resolution on the primary link (proves L2/L3 up)
az network express-route list-arp-tables \
  --resource-group rg-connectivity \
  --name erc-prod-svc \
  --path primary --peering-name AzurePrivatePeering -o table

# Routes learned/advertised on the primary link (proves BGP up)
az network express-route list-route-tables \
  --resource-group rg-connectivity \
  --name erc-prod-svc \
  --path primary --peering-name AzurePrivatePeering -o table

# BGP session summary (state, prefixes) on the primary
az network express-route list-route-tables-summary \
  --resource-group rg-connectivity \
  --name erc-prod-svc \
  --path primary --peering-name AzurePrivatePeering -o table

Run each with --path secondary as well. If only one side is up, you have a redundant circuit running non-redundant — treat it as a P1, because the next MSEE maintenance event will take you fully down. The per-link checks and what a healthy result looks like:

Check Command (--path primary / secondary) Healthy result Unhealthy → likely cause
ARP table list-arp-tables Your router MAC resolved on both Empty → L2/VLAN or link down
Route table list-route-tables Your advertised prefixes present Missing → BGP down or not advertising
Summary list-route-tables-summary State Established, prefix count sane Active/Idle → session not up
Both paths up Run both --path values Both Established One down → non-redundant circuit (P1)

Connecting the circuit to a VNet gateway

BGP being up gets routes to the MSEE, but to actually move packets to VNets the circuit must connect to an ExpressRoute gateway via a connection. The gateway is a VNet gateway of an ErGw* SKU — critically, not a VPN gateway, though the same VNet can host both a VPN gateway and an ER gateway in the GatewaySubnet. Choose a zone-redundant SKU (ErGw1Az/ErGw2Az/ErGw3Az) in a region with Availability Zones so the gateway itself survives a zone failure.

The ER gateway SKUs and what they buy:

Gateway SKU Approx throughput Zone-redundant FastPath support When to pick
Standard / HighPerformance (legacy) ~1 / ~2 Gbps No No Legacy only; migrate off
UltraPerformance (legacy) ~10 Gbps No Yes Legacy high-throughput
ErGw1Az ~1 Gbps Yes No (base) Small prod, zone-redundant
ErGw2Az ~2 Gbps Yes Limited Mid prod
ErGw3Az ~10 Gbps Yes Yes Production default, FastPath
Scalable gateway Higher, scale units Yes Yes Very high throughput needs

Create the connection, and note the routing weight — this is the Azure-side ExpressRoute connection weight that will make ExpressRoute preferred over the VPN connection outbound from Azure:

az network vpn-connection create \
  --name cn-er-hub \
  --resource-group rg-connectivity \
  --vnet-gateway1 ergw-hub \
  --express-route-circuit2 erc-prod-svc \
  --routing-weight 20000

The same, plus a zone-redundant gateway, in Terraform:

resource "azurerm_virtual_network_gateway" "er" {
  name                = "ergw-hub"
  resource_group_name = azurerm_resource_group.conn.name
  location            = "eastus2"
  type                = "ExpressRoute"
  sku                 = "ErGw3AZ"          # zone-redundant, FastPath-capable

  ip_configuration {
    name                          = "ipcfg"
    subnet_id                     = azurerm_subnet.gateway.id  # must be "GatewaySubnet"
    public_ip_address_id          = azurerm_public_ip.ergw.id
    private_ip_address_allocation = "Dynamic"
  }
}

resource "azurerm_virtual_network_gateway_connection" "er" {
  name                       = "cn-er-hub"
  resource_group_name        = azurerm_resource_group.conn.name
  location                   = "eastus2"
  type                       = "ExpressRoute"
  virtual_network_gateway_id = azurerm_virtual_network_gateway.er.id
  express_route_circuit_id   = azurerm_express_route_circuit.prod.id
  routing_weight             = 20000       # high → prefer ER over VPN outbound
}

BGP routing control: making traffic go where you intend

This is the heart of a resilient design, and the single idea to hold onto is that routing has two directions and each is controlled by a different party with different attributes. Get one direction right and the other wrong, and you get asymmetric routing — traffic leaves on ExpressRoute and returns on the VPN, or vice versa — which stateful firewalls and NAT devices hate and which produces “it works but drops half the connections” bugs that are miserable to diagnose.

The BGP decision process, in order, with who controls each step for the ExpressRoute case:

# BGP decision step Higher/lower wins Who sets it (ER context) Use it for
1 Weight (Cisco-proprietary) Higher You, per-router, locally Forward path on one router
2 Local-preference Higher You, across your AS Forward path (ER over VPN)
3 Locally originated Rarely relevant here
4 AS-path length Shorter You prepend to worsen a path Return path (worsen VPN)
5 Origin type IGP < EGP < ? Rarely tuned
6 MED Lower You/Azure hint the neighbour Return path entry-point choice
7 eBGP over iBGP eBGP Topology
8 Lowest IGP metric to next hop Lower IGP design
9 Tie-breakers (router-id, etc.) Various

Two facts fall out of the table. Weight and local-preference are evaluated first (steps 1–2), before AS-path — so on your side they are the strongest, most deterministic levers, and they govern the forward path (on-prem→Azure). AS-path and MED are evaluated later (steps 4, 6) and are what Azure sees when choosing among the routes you advertise — so they govern the return path (Azure→on-prem). A correct failover design tunes both.

Forward path (on-prem → Azure): weight and local-preference

Azure advertises your VNet prefixes over both the ExpressRoute private peering and the VPN BGP session (when both are up). Your routers therefore learn the same VNet prefixes twice and must prefer the ExpressRoute copy. Do that with local-preference — deterministic, propagated across your AS, evaluated before AS-path so it isn’t undone by anything Azure does upstream:

! Tag VPN-learned routes with LOWER local-preference so ER wins.
ip prefix-list AZURE-VNETS permit 10.0.0.0/8 le 24
!
route-map FROM_AZURE_VPN permit 10
 set local-preference 80          ! lower than default 100
!
route-map FROM_AZURE_ER permit 10
 set local-preference 200         ! higher than default — ER strongly preferred
!
router bgp 65010
 address-family ipv4
  ! ExpressRoute neighbours (both MSEE links)
  neighbor 192.168.10.1 route-map FROM_AZURE_ER in
  neighbor 192.168.10.5 route-map FROM_AZURE_ER in
  ! VPN tunnel BGP neighbour
  neighbor 10.200.0.4  route-map FROM_AZURE_VPN in
 exit-address-family

Weight is the even-stronger, router-local lever — useful when you want a specific router to prefer a specific link regardless of what the rest of your AS thinks (e.g. to pin the primary MSEE link over the secondary on a given router). Because weight is not propagated, use local-preference for AS-wide policy and weight only for per-device tuning:

! On one router, prefer the primary MSEE link over the secondary for outbound.
router bgp 65010
 address-family ipv4
  neighbor 192.168.10.1 weight 200   ! primary link preferred on THIS router
  neighbor 192.168.10.5 weight 100   ! secondary link (default-ish)
 exit-address-family

The forward-path levers compared:

Lever Scope Evaluated Propagates across AS? Use for
Weight Single router 1st (before all) No Per-device link preference
Local-preference Whole AS 2nd Yes AS-wide “prefer ER over VPN”
More-specific route Global 0th (LPM) Yes Pin traffic to a path (careful)

Return path (Azure → on-prem): AS-path prepend and MED

Azure must choose which path to send return traffic on. It sees the routes you advertise over ExpressRoute and over the VPN. To make the VPN path less attractive to Azure, make the routes you advertise over the VPN look worse — longer AS-path. AS-path prepending repeats your ASN on the advertisement so the path length grows; Azure prefers the shorter ExpressRoute path (step 4 in the decision process):

! Prepend our ASN on routes advertised OVER THE VPN so Azure prefers ER.
route-map TO_AZURE_VPN permit 10
 set as-path prepend 65010 65010 65010   ! three extra hops → longer path
!
router bgp 65010
 address-family ipv4
  neighbor 10.200.0.4 route-map TO_AZURE_VPN out
 exit-address-family

On the Azure VPN gateway you can also set AS-path prepend and a weight on the connection when you cannot touch on-prem policy — Azure exposes routingWeight on the connection and BGP peering config on the gateway. MED is the finer tool: it hints a neighbouring AS which of your multiple entry points to prefer (lower MED wins). Use MED when you advertise the same prefix over two ExpressRoute circuits at different locations and want Azure to prefer one entry point for return traffic without changing AS-path.

The return-path levers compared:

Lever Direction it influences Who sets it How it works When to use
AS-path prepend Azure → on-prem You (advertise-out) or Azure gateway Longer path = less preferred Worsen the VPN path vs ER
MED Neighbouring AS → you You (advertise-out) Lower MED = preferred entry Choose entry point among circuits
Connection routing weight Azure → on-prem (gateway choice) Azure connection property Higher weight = preferred gateway Prefer ER gateway over VPN gateway
More-specific route Both You (advertise-out) LPM overrides attributes Deterministic pin (use with care)

The more-specific-route trap, made explicit

The most common self-inflicted failover bug: advertise a summary (10.50.0.0/16) over ExpressRoute and specifics (10.50.1.0/24, 10.50.2.0/24…) over the VPN. Because longest-prefix-match wins before any attribute, Azure sends traffic for 10.50.1.5 to the VPN’s /24 even while ExpressRoute is up and preferred by every attribute you set. Your expensive circuit sits idle carrying nothing for those prefixes, and your “backup” carries production. The rule is simple: advertise the same prefixes over both paths — same summaries, or same specifics — and let attribute policy plus withdrawal do the selection.

The prefix-advertisement patterns and their outcomes:

Advertise over ER Advertise over VPN Result Verdict
10.50.0.0/16 10.50.0.0/16 Attributes choose; ER preferred; VPN idle Correct
10.50.x.0/24 (all) 10.50.x.0/24 (all) Attributes choose; ER preferred; VPN idle Correct (more routes)
10.50.0.0/16 10.50.x.0/24 (specifics) LPM sends traffic to VPN specifics always Broken — VPN carries prod
10.50.x.0/24 (specifics) 10.50.0.0/16 LPM sends traffic to ER specifics always Fine for ER-preference, but breaks failover to VPN
0.0.0.0/0 default over VPN anything Default competes only on withdrawal, risks pulling internet-bound traffic Avoid

Never advertise a 0.0.0.0/0 default over the VPN as a lazy backup. It “works” in that it only wins when ExpressRoute withdraws its specifics — but a default route also risks pulling internet-bound traffic into the tunnel and interacts badly with forced-tunneling policy. Advertise the same specific VNet prefixes over both paths; let longest-prefix-match plus your attribute policy select.

Active-active vs active-passive: designing the VPN backup

With BGP direction understood, the resiliency pattern is a choice between two shapes. Active-passive keeps ExpressRoute carrying all traffic and the VPN idle-but-ready, taking over only on ExpressRoute failure. Active-active (in the ExpressRoute sense) is really two variants: two ExpressRoute circuits both carrying traffic, or an ExpressRoute-plus-VPN where you deliberately use both — though for ER+VPN the near-universal choice is passive VPN, because the whole point of ExpressRoute is its predictable performance and you don’t want production riding an internet tunnel in steady state.

The patterns, ordered by resilience and cost:

Pattern Steady state On ER failure Cost Availability target it fits
Single circuit, no backup ER carries all Full outage until repair Lowest Dev/test only
Single circuit + VPN backup (active-passive) ER carries all, VPN idle VPN takes over (reduced capacity) Low Standard prod; capacity-degraded failover accepted
Dual circuits, same location + VPN Both ER carry; VPN idle Surviving ER carries; VPN if both fail Medium Prod; no facility diversity
Dual circuits, different locations (maximum resiliency) + VPN Both ER carry (or one active one standby); VPN idle Surviving location carries; VPN if all fail High Mission-critical; facility-diverse
Dual circuits, different providers + locations Full path + provider diversity Survives provider outage too Highest Regulated / zero-single-point

The VPN-as-backup coexistence design

Microsoft supports ExpressRoute and VPN gateway coexistence in the same VNet. The design invariant is ExpressRoute always preferred, VPN carries traffic only when ExpressRoute is down, achieved with the two-direction BGP policy above plus the connection routing weight on the Azure side:

# Azure-side preference: ER connection weight HIGH, VPN connection weight LOW.
# Higher routing weight wins when Azure chooses between gateways.
az network vpn-connection update --name cn-er-hub \
  --resource-group rg-connectivity --set routingWeight=20000

az network vpn-connection update --name cn-vpn-backup \
  --resource-group rg-connectivity --set routingWeight=100

The complete backup design checklist, and the direction/party each item controls:

Design item Direction it governs Where you set it Failure if you skip it
ER connection routing weight > VPN Azure → on-prem (gateway choice) az network vpn-connection ... routingWeight Azure may return traffic over VPN while ER is up
Local-pref: ER routes > VPN routes on-prem On-prem → Azure Edge router route-map On-prem may send over VPN while ER is up
AS-path prepend on VPN advertisements Azure → on-prem (path length) Edge or Azure gateway Azure sees equal paths; may load-share
Same prefixes over both paths Both Edge advertise-out LPM pins traffic to the wrong path
VPN gateway sized to circuit throughput Capacity on failover Gateway SKU + active-active Failover survives routing but collapses on load
BGP (not static) on the VPN Failover automation VPN gateway + connection Manual failover; slow, error-prone

Sizing the backup so failover survives real load

This is the item teams skip and regret. A VPN gateway’s aggregate throughput and a single IPsec tunnel’s cap are both far below a multi-gigabit circuit, and — the subtle killer — a single TCP flow rides a single tunnel, so one chatty replication stream does not spread across tunnels even in active-active. If your 1 Gbps circuit runs at 700 Mbps and your backup is a VpnGw1 (~650 Mbps aggregate), the backup is structurally undersized before you even hit the single-flow problem.

The VPN gateway SKUs against what a circuit might demand — read this the day you choose the backup SKU:

VPN gateway SKU Aggregate throughput (approx) Max IPsec tunnels Active-active Backs a circuit of ~
VpnGw1 / VpnGw1AZ ~650 Mbps 30 Yes ≤ ~500 Mbps circuit
VpnGw2 / VpnGw2AZ ~1 Gbps 30 Yes ~1 Gbps circuit (tight)
VpnGw3 / VpnGw3AZ ~1.25 Gbps 30 Yes ~1 Gbps circuit (comfortable)
VpnGw4 / VpnGw4AZ ~5 Gbps 100 Yes multi-Gbps circuits
VpnGw5 / VpnGw5AZ ~10 Gbps 100 Yes up to ~10 Gbps circuits

Two design moves make the backup a first-class path: choose an AZ (zone-redundant) SKU sized to the circuit’s sustained throughput (not its provisioned max), and enable active-active so there are two gateway instances, two public IPs, and two tunnels to on-prem, letting flows hash across tunnels:

# Right-size and enable active-active on the backup VPN gateway
az network vnet-gateway update \
  --name vpngw-backup --resource-group rg-connectivity \
  --sku VpnGw3AZ --set activeActive=true

The single most important validation is to drive production-representative load across the backup in a window — ER preference keeps it idle in steady state, so an iperf3 run costs nothing but proves capacity. Reachability failover (a ping succeeds) is not the same as capacity failover (the batch job still finishes in time). A backup tested only for reachability is not tested.

ExpressRoute Global Reach: datacenter-to-datacenter over the backbone

Global Reach is a distinct ExpressRoute feature that connects two of your on-prem sites to each other over the Microsoft backbone, using their ExpressRoute circuits’ private peering as transit. Without it, ExpressRoute private peering connects each site to Azure but not the sites to each other — traffic between two branches would have to hairpin through a VNet or ride your own WAN. Global Reach links the two circuits’ private-peering routing domains so branch A can reach branch B directly across Microsoft’s global network.

When Global Reach earns its place versus alternatives:

Requirement Global Reach Your own MPLS/WAN Hairpin via VNet Verdict
Site-to-site over a private path Yes (Microsoft backbone) Yes Yes (adds latency + gateway) Global Reach is clean
No dependence on your WAN provider Yes No Yes Global Reach decouples
Global reach between far regions Yes (Microsoft global network) Depends on your WAN Yes but hairpin Global Reach for global
Avoid VNet/gateway hairpin latency Yes (direct DC-to-DC) Yes No Global Reach avoids the hop
Bandwidth billing Add-on per circuit Your WAN cost ER egress + gateway Compare to WAN spend
Works across different geos Yes Depends Yes Global Reach spans geos

Prerequisites and constraints worth knowing before you design around it:

Enable Global Reach by adding a connection between the two circuits’ private peerings (you need the second circuit’s peering resource ID and an unused /29 for the Global Reach link addressing):

# Link circuit A's private peering to circuit B over the Microsoft backbone
PEER_B_ID=$(az network express-route peering show \
  --circuit-name erc-site-b --resource-group rg-connectivity \
  --name AzurePrivatePeering --query id -o tsv)

az network express-route peering connection create \
  --circuit-name erc-site-a \
  --resource-group rg-connectivity \
  --peering-name AzurePrivatePeering \
  --name gr-a-to-b \
  --peer-circuit "$PEER_B_ID" \
  --address-prefix 10.255.255.0/29

A design note: Global Reach connects your sites — it is not a substitute for the Azure-to-on-prem resiliency this article is mostly about. It is complementary: use ExpressRoute+VPN for Azure hybrid resilience, and Global Reach when you also want your branches talking to each other over the same private backbone.

Maximum resiliency: dual circuits at different peering locations

The single most impactful resiliency upgrade is to remove the peering-location failure domain by provisioning a second circuit at a different peering location. One circuit’s two links share a facility; two circuits at two facilities survive the loss of an entire meet-me room, a regional fiber event, or a provider’s problem at one site. Microsoft’s own maximum resiliency guidance is exactly this: two circuits, two peering locations, connected to the same or geo-redundant gateways.

The resiliency ladder, from a single link to full diversity, with the failure each rung survives:

Resiliency tier Circuits Peering locations Providers Survives Roughly for
Minimal 1 1 1 One MSEE/link only Non-prod
Standard 1 1 1 + gateway zone failure (AZ gw) Basic prod
Standard + VPN 1 (+VPN) 1 1 + total ER outage (degraded) Cost-conscious prod
High 2 1 1 Circuit/device failure, not facility Prod, single site
Maximum resiliency 2 2 1 Facility/location loss Mission-critical
Maximum + multi-provider 2 2 2 + provider backbone outage Regulated / zero-SPOF

Provision the second circuit at a different location, exactly as the first:

az network express-route create \
  --name erc-prod-svc-2 \
  --resource-group rg-connectivity \
  --location eastus2 \
  --provider "Megaport" \
  --peering-location "Washington DC" \
  --bandwidth 1000 \
  --sku-family MeteredData \
  --sku-tier Standard

Then connect both circuits to your gateway (or to geo-redundant gateways) and advertise the same prefixes over both so BGP can use either. To make one circuit primary and the other standby (rather than active-active), tune the connection routing weight and on-prem local-preference/AS-path so the primary circuit is preferred and the second takes over only on failure. To run active-active (both carrying traffic, load-shared), advertise equal-cost paths and let BGP multipath.

The active-active vs active-standby dual-circuit choice:

Aspect Active-active (both carry) Active-standby (one carries)
Utilisation Both circuits used; ~2× effective bandwidth One idle until failover
Failover behaviour Loss of one halves capacity, no cutover Cutover on failure (BGP convergence)
Routing symmetry Must ensure symmetric return (MED/AS-path) Easier to keep symmetric
Cost efficiency Both circuits earn their keep Pay for idle standby
Complexity Higher (multipath, symmetry) Lower
Best for Steady high throughput, both sites near Clear primary site, DR posture

ExpressRoute Metro — two locations in one metro

ExpressRoute Metro is a newer configuration that provisions a single circuit whose two links terminate in two different peering locations within the same metropolitan area — giving you facility diversity inside one circuit and one bill, in metros where it is offered. It is the sweet spot when you want location diversity but a full second circuit at a distant location is more than the workload needs. Availability is metro-specific; confirm your metro supports it. Where Metro is available and diversity-within-a-metro suffices, it is a lower-cost path to removing the single-facility risk than two full circuits.

Metro vs two full circuits:

Dimension ExpressRoute Metro Two full circuits, two locations
Facility diversity Yes (two locations in one metro) Yes (two locations, any distance)
Geographic spread Within one metro only Can span metros/regions
Number of circuits/bills One Two
Availability Select metros only Anywhere circuits exist
Cost Lower (one circuit) Higher (two circuits)
Best for Metro-local diversity Wide geographic diversity / DR

FastPath: taking the gateway out of the data path

The ExpressRoute gateway sits in the data path by default and is a throughput and latency bottleneck — it caps at the gateway SKU’s bandwidth and adds a hop with variable queuing. FastPath programs the MSEE to send data-plane traffic directly to VMs in the VNet, bypassing the gateway; control-plane (BGP, route exchange) still flows through the gateway, but the packets skip it. For latency-sensitive workloads (trading, real-time media, chatty database replication) FastPath is the difference between “ExpressRoute is fast” and “ExpressRoute is fast and consistent”, because you remove the gateway’s variable queuing from every flow and get circuit line-rate instead of gateway-limited throughput.

FastPath requirements and the scenarios it does/doesn’t cover:

Scenario FastPath support Prerequisite Gotcha
Traffic to VMs in the gateway’s own VNet Base FastPath ErGw3Az/UltraPerformance/scalable gw Simplest case; widely supported
Traffic to a Private Endpoint / Private Link Advanced FastPath Premium circuit + supported gw Separate enablement; check current support
Traffic to spokes over VNet peering Advanced FastPath Premium circuit + supported gw Separate enablement; region caveats
Traffic that must traverse an NVA via UDR Bypassed FastPath ignores the UDR → NVA is skipped
Low-throughput gateway SKUs No Needs high-capacity gw ErGw1Az/legacy Standard don’t qualify

Requirements worth internalising:

Enable FastPath (gateway bypass) on the connection:

az network vpn-connection update \
  --name cn-er-hub \
  --resource-group rg-connectivity \
  --set expressRouteGatewayBypass=true

In Terraform, set it on the connection:

resource "azurerm_virtual_network_gateway_connection" "er" {
  # ...as above...
  express_route_gateway_bypass = true   # FastPath: data-plane bypasses the gateway
}

The FastPath trade-off, made explicit:

You gain You give up Mitigation
Circuit line-rate (no gateway cap) Gateway-based throughput metering Monitor on the circuit, not the gateway
Consistent low latency (no gateway queuing)
UDR-forced NVA/firewall inspection on that path Inspect elsewhere (e.g. on-prem) or don’t FastPath those subnets
Some advanced scenarios need Premium + enablement Budget for Premium; verify support

Route control: filters and the route limit

For private peering, you control what Azure learns by what your routers advertise (the network/redistribution statements), and what your routers accept with inbound route maps. Azure advertises your VNet address spaces plus routes from connected VNets. Keep the advertised set tight: every prefix counts against the route limit, and a sloppy redistribution can blackhole traffic.

Route filters are a separate mechanism that applies to Microsoft peering only. Microsoft peering would otherwise advertise the full set of Microsoft public prefixes (all regions, all services). A route filter is an allow-list of BGP community values that scopes advertisement down to the services and regions you actually consume. Without an attached route filter, a Microsoft peering advertises nothing — the filter is what activates prefix advertisement.

Create a route filter and a community allow-rule, then attach it:

az network route-filter create \
  --name rf-m365 \
  --resource-group rg-connectivity \
  --location eastus2

# Allow only specific service/region communities (values from Microsoft's list)
az network route-filter rule create \
  --resource-group rg-connectivity \
  --filter-name rf-m365 \
  --name allow-services \
  --access Allow \
  --communities 12076:5010 12076:5020 \
  --route-filter-rule-type Community

# Attach the filter to the Microsoft peering (this activates advertisement)
az network express-route peering update \
  --circuit-name erc-prod-svc \
  --resource-group rg-connectivity \
  --name MicrosoftPeering \
  --route-filter rf-m365

Get the community values from Microsoft’s published service/region community list rather than guessing — 12076:5010 etc. above illustrate the pattern; the real per-service, per-region values are documented and change as services are added.

Where each route-control mechanism applies:

Mechanism Applies to What it controls Set where
network / redistribution Private peering Which of your prefixes Azure learns Your edge router
Inbound route map Private peering Which Azure prefixes you accept Your edge router
Route filter (communities) Microsoft peering Which Microsoft public prefixes you learn Azure (attached to peering)
AS-path prepend / local-pref Both Path preference Edge + Azure gateway

The route limit — a hard cliff, not a slope

Private peering accepts a maximum of 4,000 IPv4 routes on Standard and 10,000 on Premium (and separate, smaller limits for IPv6). Blow past it and the BGP session drops — it does not gracefully degrade, it drops — taking that whole peering down. The usual cause is redistributing your entire on-prem routing table into BGP instead of summarized supernets. Always advertise summaries (one 10.50.0.0/16 rather than 256 /24s) and watch the count.

The route limits and the discipline that keeps you under them:

Limit Standard Premium Consequence of breach Prevention
IPv4 routes (private peering) 4,000 10,000 BGP session drops (peering down) Advertise summaries, not the full table
IPv6 routes (private peering) Lower (separate) Higher (separate) Session drops Summarize v6 too
VNets per circuit Lower Higher (scales with bandwidth) New connections rejected Premium; consolidate VNets
Routes from Microsoft peering Governed by the route filter Same Over-broad filter = many routes Scope the filter tightly

Advertise summaries on the edge, and confirm the count from Azure:

! Advertise a single summary instead of many specifics
router bgp 65010
 address-family ipv4
  aggregate-address 10.50.0.0 255.255.0.0 summary-only
 exit-address-family
# Watch the advertised/received prefix count per link — stay well under the limit
az network express-route list-route-tables-summary \
  --resource-group rg-connectivity --name erc-prod-svc \
  --path primary --peering-name AzurePrivatePeering \
  --query "value[].{neighbor:neighbor, asn:asn, up:upDown, prefixes:statePfxRcd}" -o table

Failover testing and BGP convergence

A resiliency design you have not tested is a hypothesis. Testing ExpressRoute failover means deliberately breaking a path in a change window and measuring how fast — and how completely — traffic moves to the backup, then restoring. The two numbers that matter are convergence time (how long until the backup carries traffic) and capacity (whether the backup carries it fast enough).

Convergence is BGP-driven and its speed depends on how the failure is detected. With default BGP timers, a session isn’t declared dead until the hold timer expires — commonly on the order of tens of seconds up to ~180 seconds. BFD (Bidirectional Forwarding Detection) detects link failure in sub-second time and tears the BGP session immediately, collapsing convergence to a second or two. If you need fast failover, enable BFD on the ExpressRoute BGP sessions (Microsoft supports BFD on private peering).

Convergence expectations by detection method and failure type:

Failure type Detection Approx convergence (default timers) With BFD Notes
Link physically down Interface down → immediate Seconds (link-state) Sub-second Fastest; L1 signals
MSEE maintenance (graceful) BGP withdraw Seconds Seconds Graceful, planned
BGP session stuck (no traffic, link up) Hold-timer expiry Up to ~180 s Sub-second Where BFD helps most
Whole circuit down (facility) Both sessions withdraw Seconds–tens of s Sub-second Failover to VPN/second circuit
Gateway failure (non-AZ) Platform detection Longer Use AZ gateways to avoid

Enable BFD on the edge (Cisco-style) to shrink the worst case:

! Enable BFD on the ExpressRoute BGP neighbours for sub-second detection
interface GigabitEthernet0/1.100
 bfd interval 300 min_rx 300 multiplier 3
!
router bgp 65010
 address-family ipv4
  neighbor 192.168.10.1 fall-over bfd
  neighbor 192.168.10.5 fall-over bfd
 exit-address-family

The failover test procedure

Test by administratively shutting the ExpressRoute BGP sessions on your edge (do this in a window, with a rollback ready), then confirming traffic moved to the VPN and measuring the gap:

! On the on-prem edge router — shut BOTH ER sessions to simulate a circuit loss
router bgp 65010
 neighbor 192.168.10.1 shutdown
 neighbor 192.168.10.5 shutdown

While the sessions are down, confirm the effective routes on a test NIC now point at the VPN, and measure the outage window with a continuous ping:

# Effective routes on a probe NIC — next hop should now be the VPN gateway
az network nic show-effective-route-table \
  --name nic-probe01 --resource-group rg-workload -o table
# Look for your on-prem prefixes with Next Hop Type = VirtualNetworkGateway (VPN)
# In steady state (ER up) they should read ExpressRouteGateway

Then drive load, not just a ping, across the backup — this is the capacity test that reachability alone hides:

# From an on-prem host, push representative load over the (now-active) VPN path
iperf3 -c 10.50.4.10 -t 60 -P 8    # 8 parallel streams to spread across tunnels
# Compare achieved throughput to your batch/replication requirement

Restore by removing the shutdowns and confirm ExpressRoute reclaims the traffic:

router bgp 65010
 no neighbor 192.168.10.1 shutdown
 no neighbor 192.168.10.5 shutdown

The test matrix — what to break, what to observe, what “pass” means:

Test How to induce Observe Pass criteria
One MSEE link down Shut one ER neighbour Other link carries; both were Established No traffic loss; second link takes all
Whole circuit down Shut both ER neighbours Effective routes flip to VPN Convergence within target; VPN carries
Capacity on failover Load-test over the VPN iperf3 throughput vs requirement Backup meets batch/replication need
Convergence with BFD Compare shut-time to traffic-move Ping gap duration Sub-second/seconds, meets RTO
Failback Remove shutdowns Effective routes return to ER ER reclaims; no flap/asymmetry
Return-path symmetry Trace both directions during failover Same path both ways No asymmetric routing through firewalls

Monitoring: Connection Monitor and the metrics that predict outages

Alerting on aggregate circuit state is how the silent-single-session outage happens. Alert instead on the signals that predict an outage: per-session BGP availability, ARP availability, and throughput against your provisioned bandwidth — and add Connection Monitor for real end-to-end reachability, not just control-plane state.

The ExpressRoute metrics worth alerting on, and what each catches:

Metric Dimension Alert when Catches Why it’s leading
BgpAvailability Per peering / per link < 100 A BGP session down The silent non-redundant circuit
ArpAvailability Per peering / per link < 100 L2/L3 link problem Cross-connect/VLAN issue before BGP flaps
BitsInPerSecond Per circuit > 80% of bandwidth Saturation Congestion before users feel it
BitsOutPerSecond Per circuit > 80% of bandwidth Saturation Egress ceiling approaching
GlobalReachBitsInPerSecond Per Global Reach conn > threshold DC-to-DC saturation Branch link congestion
Advertised/received route count Per peering Near the limit Route-limit cliff Prevents the session-drop breach

Wire an alert on per-session BGP availability — the single alert that prevents the most common ExpressRoute outage:

# Alert if a BGP session on the circuit goes unavailable (per-peering)
CIRCUIT_ID=$(az network express-route show -g rg-connectivity -n erc-prod-svc --query id -o tsv)

az monitor metrics alert create \
  --name alrt-er-bgp-down \
  --resource-group rg-connectivity \
  --scopes "$CIRCUIT_ID" \
  --condition "avg BgpAvailability < 100" \
  --window-size 5m --evaluation-frequency 1m \
  --severity 1 \
  --description "ExpressRoute private peering BGP availability dropped below 100"

# And a saturation alert on egress
az monitor metrics alert create \
  --name alrt-er-egress-saturation \
  --resource-group rg-connectivity \
  --scopes "$CIRCUIT_ID" \
  --condition "avg BitsOutPerSecond > 800000000" \
  --window-size 5m --evaluation-frequency 1m \
  --severity 2 \
  --description "ExpressRoute egress above 80% of 1 Gbps"

Connection Monitor — end-to-end reachability, not just BGP

Control-plane health (BGP up) does not prove the data plane works end-to-end. Connection Monitor (in Network Watcher) runs continuous synthetic probes from an on-prem host (with the monitoring agent) to an Azure VM over the circuit, alerting on real reachability, latency, and loss, and showing the hop-by-hop path so you can see where a degradation bites. Stand it up so your alert is “on-prem can’t reach the app over ExpressRoute”, not merely “a BGP metric dipped”.

# Create a Connection Monitor (v2) test from on-prem to an Azure VM over ExpressRoute
az network watcher connection-monitor create \
  --name cm-er-onprem-to-azure \
  --resource-group rg-connectivity \
  --location eastus2 \
  --endpoint-source-name onprem-host \
  --endpoint-source-resource-id "<arc-or-agent-machine-id>" \
  --endpoint-dest-name azure-app-vm \
  --endpoint-dest-resource-id "<azure-vm-id>" \
  --test-config-name tcp-443 \
  --protocol Tcp --tcp-port 443 \
  --frequency 30

What each monitoring layer proves, and why you want all of them:

Layer Tool Proves Blind spot it removes
Per-session BGP/ARP Circuit metrics + alert Control plane up, both links The silent non-redundant circuit
Throughput vs bandwidth BitsIn/OutPerSecond Not saturated Congestion mistaken for app slowness
End-to-end reachability Connection Monitor Data plane works app-to-app “BGP is up but users can’t connect”
Path/latency/loss Connection Monitor topology Where degradation bites Guessing which hop failed
Route count Route-table summary Under the limit The route-limit cliff

Architecture at a glance

Picture the whole resilient hybrid path as three concentric rings of redundancy, read from your datacenter outward to Azure. The innermost ring is a single ExpressRoute circuit: your two edge routers each hold an eBGP session — MD5-authenticated, ideally BFD-enabled — to a different Microsoft Enterprise Edge router (MSEE, ASN 12076) across a dual-tagged (VLAN 100) cross-connect in the peering location. Those two sessions are the circuit’s built-in redundancy; they carry your summarized VNet prefixes (10.50.0.0/16 advertised, VNet space learned) and terminate, on the Azure side, at a zone-redundant ExpressRoute gateway (ErGw3Az) in the hub VNet’s GatewaySubnet. With FastPath enabled on the connection, data-plane packets skip that gateway and land directly on VMs at circuit line-rate; only BGP still flows through it.

The middle ring removes the facility as a single point of failure: a second circuit at a different peering location (or an ExpressRoute Metro circuit spanning two locations in one metro), advertising the same prefixes, connected to the same or a geo-redundant gateway. BGP prefers the primary circuit via connection routing weight on the Azure side and local-preference on-prem; if the whole primary facility drops, the second circuit’s sessions carry every packet after a few seconds of convergence. The outermost ring is the last-resort path: a zone-redundant, active-active VPN gateway (sized to the circuit’s sustained throughput) holding IPsec tunnels to on-prem over the internet, coexisting in the same hub. Its connection weight is low and its advertised routes are AS-path-prepended, so it sits idle while any ExpressRoute path lives — and takes over only if every circuit is gone.

Follow a packet from an on-prem client to an Azure VM: it hits your edge router, which prefers the ExpressRoute-learned VNet route (high local-pref) over the VPN-learned one (low local-pref), forwards over the primary MSEE link, and — with FastPath — reaches the VM directly. The return packet leaves Azure, where the ER connection’s high routing weight and the VPN’s prepended AS-path make Azure choose the same ExpressRoute gateway, so the flow is symmetric in both directions — the property stateful firewalls depend on. Every ring is watched: per-session BgpAvailability/ArpAvailability alerts catch a silent link, throughput alerts catch saturation, and Connection Monitor proves the whole app-to-app path is alive, not just that BGP is up. The design’s one job is that a failure in any ring is invisible in the ring outside it — a fiber cut in the inner ring is caught by the second link; a facility loss in the middle ring is caught by the second circuit; a total ExpressRoute outage in the outer ring is caught by the VPN.

Real-world scenario

Meridian Payments runs a card-settlement platform on Azure: a hub-and-spoke topology in Central India and South India, with core batch settlement running against an on-prem mainframe over ExpressRoute. Connectivity was a single 1 Gbps circuit at the Chennai peering location (MeteredData, Standard) plus a Site-to-Site VPN backup, with connection weights set so ExpressRoute was preferred and a quarterly failover drill that always passed. The platform team was six engineers; ExpressRoute plus the VPN gateway cost about ₹2.4 lakh/month. The circuit typically ran at 700 Mbps during the nightly settlement window; batch settlement completed in 40 minutes on ExpressRoute.

The incident began at 02:00 on a settlement night when a contractor severed a fiber bundle into the Chennai meet-me room. Both of the circuit’s links went down together — they shared the facility — and ExpressRoute withdrew. The VPN backup took over exactly as the drill had shown: az network nic show-effective-route-table on the settlement VNet flipped to VirtualNetworkGateway, pings from the mainframe subnet succeeded, and the on-call engineer confirmed “failover worked” within four minutes. Then throughput collapsed. The 40-minute batch was projected at 6+ hours over the tunnel; the settlement window would blow past the banking cut-off and trigger regulatory penalties. The team had validated routing failover for eight straight quarters and never once validated capacity failover.

Root cause was two-fold. First, the VPN gateway was a VpnGw1 (~650 Mbps aggregate) backing a circuit that regularly ran at 700 Mbps — the backup was structurally undersized before any other factor. Second, a single IPsec tunnel caps around 1.25 Gbps and, more importantly, the chatty mainframe replication was a single TCP flow that rides a single tunnel, so even active-active wouldn’t have spread it. The quarterly drill had only ever run a ping and a small file copy — both trivially within VpnGw1’s capacity — so the structural undersizing was invisible until a real cut drove real settlement load across it. And there was no second circuit at a different location: the entire design had the Chennai facility as an unremoved single failure domain.

The fix landed in three waves. That night, they invoked the manual DR runbook to run settlement from the secondary region’s mainframe replica, absorbing the cut-off risk with a regulator notification. Within the week, they rebuilt the backup as a first-class path: moved to VpnGw3AZ (~1.25 Gbps, zone-redundant) in active-active mode with two public IPs and two tunnels, and split the monolithic replication into parallel streams so it spreads across tunnels. Within the quarter, they provisioned a second 1 Gbps circuit at the Mumbai peering location (a different provider, for provider diversity too), advertised the same prefixes over both, and set connection weights and on-prem local-preference so Chennai was primary and Mumbai took over on failure — removing the single-facility domain entirely. They also enabled BFD on all ER sessions (cutting worst-case convergence from ~180 s to sub-second) and rewrote the drill to include an iperf3 capacity test at settlement-representative load.

The next drill ran the full settlement batch over the backup Mumbai circuit during a window — 41 minutes, within one minute of the primary. The lesson that went onto the wall: “A backup you have only tested for reachability is not tested. Drive production load across it — and if the whole facility can die, put a second facility under it.” The incident as a timeline, because the order of realisations is the lesson:

Time Event Action taken Effect What it should have been
02:00 Fiber cut, Chennai meet-me room (alert fires; ER withdraws) Both links down together A second circuit at Mumbai would carry it
02:04 VPN took over Confirm effective routes flipped “Failover worked” — routing only Capacity, not just reachability
02:10 Throughput collapse Watch batch ETA balloon to 6h+ Settlement window at risk Backup was VpnGw1 (~650 Mbps)
02:25 Cut-off risk real Invoke DR: run from South India replica Regulatory penalty avoided Backup should carry primary load
+1 week Rebuild backup VpnGw3AZ active-active + parallel streams Capacity closer to circuit First-class backup from day one
+1 quarter Remove single facility Second circuit at Mumbai + BFD Facility-diverse, sub-second failover Maximum resiliency from the start
+1 quarter Fix the drill iperf3 at settlement load Backup proven under real load The drill validated capacity

Advantages and disadvantages

ExpressRoute’s private, predictable path is exactly why it is worth this design effort — and exactly why its failure modes are subtle. Weigh the model honestly:

Advantages (why ExpressRoute is worth it) Disadvantages (why it bites)
Private Layer-3 path, never touches the public internet — predictable latency and throughput The circuit’s redundancy is only link-level within one facility; the facility is a hidden single point of failure
Every circuit is dual-homed to two MSEEs automatically — link redundancy is free Teams read “redundant” as “covered” and never add facility/provider diversity
BGP gives deterministic, automatic failover between paths Getting BGP right in both directions is non-trivial; asymmetric routing is easy to create
FastPath removes the gateway throughput/latency cap for line-rate performance FastPath bypasses UDR-forced NVA/firewall inspection — a security trade-off you must own
A VPN backup coexists cheaply and stays idle in steady state A VPN backup sized for cost, not throughput, survives routing failover but collapses under load
Global Reach connects your own DCs over the Microsoft backbone Global Reach and per-feature charges add cost; overlapping on-prem prefixes break it
Rich per-session metrics (BgpAvailability, throughput) enable leading-indicator alerts Alerting on aggregate circuit state hides the silent single-session outage until MSEE maintenance
Premium tier reaches any geo and lifts route limits to 10,000 The route limit is a cliff: breach it and the whole BGP session drops, not degrades

The model is right for enterprises whose hybrid path is load-bearing and who need predictable performance and private connectivity — banks, healthcare, payments, large SaaS. It is over-engineered for a small workload where a well-designed active-active VPN would meet the SLA at a fraction of the cost. The disadvantages are all manageable: enumerate failure domains and put diversity under each, tune BGP in both directions and verify symmetry, size the backup for capacity not just reachability, and alert on per-session leading indicators. Every one of these is a decision you make on purpose — which is the entire point of designing rather than accepting defaults.

Hands-on lab

You cannot provision a real ExpressRoute circuit without a provider and a physical cross-connect, so this lab builds and validates the pieces you can stand up free-of-provider: it configures the peering resource against a circuit (create the circuit only if you have a provider; otherwise use an existing test circuit), stands up the coexisting VPN backup gateway, wires the connection weights and BGP preference that make ExpressRoute preferred, sets up the metric alert and Connection Monitor, and runs the effective-route-table and route-summary validation you’d use in a real failover test. Where a step needs a real provisioned circuit, we mark it and give the exact command; the VPN-side and monitoring steps run fully. Run in Cloud Shell (Bash); delete at the end.

Step 1 — Variables and resource group.

RG=rg-er-lab
LOC=eastus2
az group create -n $RG -l $LOC -o table

Step 2 — (If you have a provider) create the circuit, else skip to Step 3.

# Only run if you have a connectivity provider ready to provision.
az network express-route create \
  --name erc-lab --resource-group $RG --location $LOC \
  --provider "Equinix" --peering-location "Silicon Valley" \
  --bandwidth 200 --sku-family MeteredData --sku-tier Standard -o table

# Retrieve the s-key to hand to the provider, and watch the provider state
az network express-route show -n erc-lab -g $RG \
  --query "{key:serviceKey, provider:serviceProviderProvisioningState}" -o table

Expected (before the provider acts): serviceProviderProvisioningState = NotProvisioned. Do not configure peering until it reads Provisioned.

Step 3 — Build the hub VNet with a GatewaySubnet (hosts both ER and VPN gateways).

az network vnet create -n vnet-hub -g $RG -l $LOC \
  --address-prefix 10.90.0.0/16 --subnet-name GatewaySubnet \
  --subnet-prefix 10.90.255.0/27 -o table
az network public-ip create -n pip-vpngw -g $RG --sku Standard --allocation-method Static -o table

Expected: a VNet with a GatewaySubnet (the exact name matters) and a standard public IP.

Step 4 — Create the coexisting VPN backup gateway (active-active, zone-redundant, BGP). This is a real, chargeable resource and takes ~30–45 minutes to provision.

az network public-ip create -n pip-vpngw2 -g $RG --sku Standard --allocation-method Static -o table
az network vnet-gateway create \
  -n vpngw-backup -g $RG -l $LOC \
  --vnet vnet-hub --gateway-type Vpn --vpn-type RouteBased \
  --sku VpnGw2AZ --public-ip-addresses pip-vpngw pip-vpngw2 \
  --asn 65515 --no-wait
# Poll until provisioningState = Succeeded
az network vnet-gateway show -n vpngw-backup -g $RG --query provisioningState -o tsv

Expected eventually: Succeeded, with active-active (two public IPs) and BGP ASN 65515.

Step 5 — (Real circuit) configure private peering and connect it with a HIGH routing weight.

# Requires the circuit at 'Provisioned'. Configure the redundant BGP pair:
az network express-route peering create \
  --circuit-name erc-lab -g $RG \
  --peering-type AzurePrivatePeering --peer-asn 65010 \
  --primary-peer-subnet 192.168.10.0/30 \
  --secondary-peer-subnet 192.168.10.4/30 \
  --vlan-id 100 --shared-key "labSecret123!"

# Create an ER gateway (ErGw1Az for the lab) and connect with high weight
az network vnet-gateway create -n ergw-lab -g $RG -l $LOC \
  --vnet vnet-hub --gateway-type ExpressRoute --sku ErGw1AZ \
  --public-ip-addresses $(az network public-ip create -n pip-ergw -g $RG --sku Standard --allocation-method Static --query publicIp.id -o tsv) --no-wait

az network vpn-connection create --name cn-er -g $RG \
  --vnet-gateway1 ergw-lab --express-route-circuit2 erc-lab --routing-weight 20000

Step 6 — Verify the redundant BGP pair (both links Established).

# Run for BOTH paths; both must be Established with your prefixes present
for P in primary secondary; do
  echo "=== $P ==="
  az network express-route list-route-tables-summary \
    -g $RG --name erc-lab --path $P --peering-name AzurePrivatePeering -o table
done

Expected (real circuit): both primary and secondary show state Established. If only one is up, that is the silent-non-redundant condition this whole article is about — treat it as a P1.

Step 7 — Wire the per-session BGP-availability alert (the single most valuable alert).

CIRCUIT_ID=$(az network express-route show -n erc-lab -g $RG --query id -o tsv 2>/dev/null)
if [ -n "$CIRCUIT_ID" ]; then
  az monitor metrics alert create \
    --name alrt-er-bgp-down -g $RG --scopes "$CIRCUIT_ID" \
    --condition "avg BgpAvailability < 100" \
    --window-size 5m --evaluation-frequency 1m --severity 1 \
    --description "ER BGP availability dropped below 100"
fi

Expected: an alert rule created and enabled (skipped cleanly if you have no circuit).

Step 8 — Validate the failover mechanism with effective routes. On a probe NIC in a spoke, the effective route table shows the next hop for on-prem prefixes; in steady state it reads ExpressRouteGateway, and after you shut the ER BGP sessions (in a window) it should read VirtualNetworkGateway.

# (Create a tiny probe VM in a spoke first if you want a live NIC to inspect.)
az network nic show-effective-route-table --name <nic-name> -g $RG -o table
# Next Hop Type for on-prem prefixes: ExpressRouteGateway (ER up) → VirtualNetworkGateway (ER down)

Validation checklist. You stood up a hub with a GatewaySubnet, an active-active zone-redundant VPN backup gateway with BGP, and (with a provider) a private-peering circuit connected at a high routing weight so ExpressRoute is preferred; you confirmed both BGP links, wired the per-session availability alert, and learned the effective-route-table check that proves failover. The lab steps mapped to what each proves:

Step What you did What it proves Real-world analogue
3–4 Hub + active-active VPN gateway The backup coexists and is capacity-shaped The first-class backup Meridian built
5 Peering + high connection weight ER is preferred over VPN outbound from Azure The design invariant
6 Both --path links Established Redundancy is real, not assumed Catching the silent single-session
7 Per-session BGP alert Degradation is visible before outage The alert that prevents the 02:00 surprise
8 Effective-route-table flip Failover mechanism actually moves traffic The failover-drill validation

Cleanup (VPN and ER gateways bill by the hour — delete promptly).

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

Cost note. A VpnGw2AZ gateway is the main cost here — a few tens of rupees per hour — so an hour of this lab is well under ₹200; deleting the resource group stops everything. A real ExpressRoute circuit has a monthly floor regardless of usage, which is exactly why this lab keeps the circuit optional.

Common mistakes & troubleshooting

The failover-and-BGP playbook — the part you bookmark. First as a scannable table you can read while a link is down, then the entries that bite hardest with full reasoning.

# Symptom Root cause Confirm (exact cmd / portal path) Fix
1 Circuit “redundant” but an MSEE maintenance took it fully down One BGP session was silently down for weeks list-route-tables-summary --path primary and --path secondary — one is not Established Alert on per-session BgpAvailability < 100; restore the down session
2 Backup VPN takes over on failover but throughput collapses VPN gateway SKU undersized vs circuit sustained load az network vnet-gateway show --query sku; compare to BitsOutPerSecond peak Move to a VpnGwN AZ sized to sustained throughput; active-active
3 ExpressRoute is up but Azure returns traffic over the VPN ER connection routing weight ≤ VPN weight az network vpn-connection show --query routingWeight on both Set ER connection weight high (e.g. 20000), VPN low (100)
4 On-prem sends to Azure over VPN while ER is up On-prem local-pref/AS-path not preferring ER Show route-maps on ER vs VPN neighbours ER routes local-pref 200, VPN 80 (or weight on ER neighbours)
5 Backup carries production for some prefixes even with ER up Summary over ER, specifics over VPN → LPM wins list-route-tables --path primary vs what VPN advertises Advertise the same prefixes over both paths
6 Whole private peering dropped at once, all traffic lost Route-limit breach (4,000 Std / 10,000 Prem) → session drop list-route-tables-summary prefix count near limit; logs Advertise summaries; aggregate-address ... summary-only
7 Failover takes ~3 minutes; RTO missed Default BGP hold timer; no BFD Measure ping gap on shut; check fall-over bfd config Enable BFD on ER neighbours → sub-second
8 Traffic works but stateful firewall drops half the flows Asymmetric routing (out ER, back VPN or vice versa) Trace both directions; compare effective routes vs on-prem path Align both directions: weight+local-pref+AS-path consistent
9 Microsoft peering advertises nothing No route filter attached (filter activates advertisement) az network express-route peering show --name MicrosoftPeering --query routeFilter = null Create + attach a route filter with the right communities
10 FastPath enabled but firewall no longer sees the traffic FastPath bypasses the UDR-forced NVA Confirm expressRouteGatewayBypass=true on the connection; check UDR intent Don’t FastPath inspected subnets, or inspect elsewhere
11 BGP session won’t come up after provider “done” Provider state not actually Provisioned, or VLAN mismatch --query serviceProviderProvisioningState; verify VLAN/C-tag with provider Wait for Provisioned; match VLAN ID exactly both ends
12 Global Reach connect fails or routes missing Overlapping on-prem prefixes between the two sites Compare advertised prefixes from both circuits De-overlap the address spaces; re-advertise
13 Cross-geo VNet unreachable over the circuit Circuit is Standard (same-geo only) az network express-route show --query "sku.tier" = Standard Upgrade to Premium for cross-geo reach
14 Billing started before you were ready Circuit hit Provisioned ahead of your config Circuit creation/provision timestamps vs your readiness Sequence provider order to your window; you pay from Provisioned

The expanded form, for the entries that cause the most damage:

1. The “redundant” circuit that wasn’t. Root cause: One of the two BGP sessions dropped (a router reload, a cross-connect flap, a config change) and nobody alerted on it, so the circuit ran non-redundant until the surviving MSEE went into planned maintenance and everything failed at once — in a window Microsoft announced. Confirm: az network express-route list-route-tables-summary --path primary and --path secondary — one shows a state other than Established, or a prefix count of zero. Fix: Alert on per-session BgpAvailability < 100 (not aggregate circuit state); restore the down session; add BFD so future flaps recover fast. This is the single most common ExpressRoute outage there is.

2. The backup that collapsed under load. Root cause: The VPN backup was sized for cost, not throughput — a VpnGw1 (~650 Mbps) behind a 1 Gbps circuit running at 700 Mbps — and a single TCP flow rides a single tunnel, so even active-active wouldn’t spread a monolithic replication stream. Confirm: az network vnet-gateway show --query sku gives the SKU; compare its aggregate throughput to the circuit’s peak BitsOutPerSecond. Fix: Move to a VpnGwN AZ SKU sized to sustained throughput, enable active-active, and split monolithic flows so they hash across tunnels. Test with iperf3 at production load, not a ping.

3 & 4. Azure returns over VPN / on-prem sends over VPN while ER is up. Root cause: The two-direction preference is half-configured. If Azure returns traffic over the VPN, the ER connection routing weight isn’t higher than the VPN’s. If on-prem sends over the VPN, your edge route-maps aren’t preferring ER (local-pref/weight/AS-path). Confirm: az network vpn-connection show --query routingWeight on both connections; show the inbound route-maps on your ER vs VPN neighbours. Fix: ER connection weight high (20000), VPN low (100); ER-learned routes local-pref 200 on-prem, VPN-learned 80; AS-path-prepend the VPN advertisements. Tune both directions or you get asymmetry (#8).

5. The backup carrying production. Root cause: You advertised a summary over ExpressRoute and specifics over the VPN. Longest-prefix-match wins before every attribute, so Azure sends the specifics’ traffic down the VPN even while ER is preferred by every knob you set. Confirm: list-route-tables --path primary shows the summary on ER; the VPN advertises specifics. Fix: Advertise the same prefixes over both paths — same summaries or same specifics. Never mix granularity across paths.

6. The whole peering dropping at once. Root cause: You exceeded the route limit (4,000 Standard / 10,000 Premium) — usually by redistributing the entire on-prem table — and the BGP session dropped (it does not degrade; it drops). Confirm: list-route-tables-summary prefix count sitting near the limit; the session flapping. Fix: Advertise summaries (aggregate-address ... summary-only); if you legitimately need >4,000 routes, move to Premium (10,000). Watch the count with an alert before it hits the cliff.

7. Slow failover. Root cause: Default BGP timers mean a stuck-but-up session isn’t declared dead until the hold timer expires (up to ~180 s), so failover is minutes, not seconds. Confirm: Measure the ping gap when you shut the ER sessions; check for fall-over bfd in the config. Fix: Enable BFD on the ER BGP neighbours for sub-second detection; Microsoft supports BFD on private peering.

8. Asymmetric routing. Root cause: Traffic leaves on one path and returns on another (out ER, back VPN, or across two circuits), and a stateful firewall or NAT in the path drops the half it never saw the SYN for. Confirm: Trace both directions during a failover test; compare Azure effective routes to the on-prem chosen path. Fix: Make the preference consistent in both directions (weight + local-pref forward, routing weight + AS-path return); for dual circuits use MED to keep return traffic symmetric.

Best practices

Security notes

The security controls that also improve resilience — they pull the same direction here:

Control Mechanism Secures against Also improves
BGP MD5 authentication --shared-key + neighbour password Route injection on the transit VLAN Session stability (rejects rogue peers)
Private Link over private peering Private Endpoint + private DNS Public-IP exposure of PaaS Simpler routing, fewer route-limit prefixes
Tight Microsoft-peering route filter Community allow-list Over-broad public advertisement Fewer routes, less route-limit pressure
IaC + PR review for route policy Terraform/Bicep + reviews Unauthorised/accidental route change No preference drift breaking failover
Encryption over ExpressRoute IPsec/MACsec / end-to-end TLS Cleartext on a private-but-unencrypted path Meets compliance without changing routing
Zone-redundant gateways ErGw*Az / VpnGw*AZ Single-zone gateway loss Availability of the path itself

Cost & sizing

The bill drivers and how they interact with the resiliency design:

A rough monthly picture for a mid-size production hybrid setup in India, and what each line buys:

Cost driver What you pay for Rough INR / month What it buys Watch-out
1 Gbps circuit (Metered, Standard) Circuit floor + egress ~₹1.5–2.5 lakh + egress Private, predictable path Egress adds up at high volume
Premium add-on Cross-geo + 10k routes + tens of thousands Cross-geo reach; higher limits Only if you need it
Second circuit (different location) Full second circuit ~2× the circuit line Removes facility SPOF Doubles circuit spend
ExpressRoute Metro (alternative) One circuit, two locations ~1× circuit Facility diversity, one bill Select metros only
ER gateway (ErGw3Az) Zone-redundant gw, FastPath ~₹40–70k Throughput + FastPath Bypass metering with FastPath
VPN backup (VpnGw3AZ, active-active) Zone-redundant backup gw ~₹40–80k Capacity-shaped failover Undersizing defeats the purpose
Global Reach Enablement + DC-to-DC egress ~₹20–50k + egress Private DC-to-DC Compare to WAN spend
Connection Monitor + alerts Network Watcher tests Modest End-to-end visibility Cheap; skip it and you’re blind

The sizing rule in prose: start with one Standard MeteredData circuit sized with bandwidth headroom (you can grow it, not shrink it) plus a capacity-matched active-active VPN backup; add Premium only for cross-geo reach or >4,000 routes; and for mission-critical workloads add a second circuit at a different location (or Metro) to remove the facility failure domain. The most expensive thing you can do is not size the backup — a failover that survives routing but collapses under load costs far more in one incident than the SKU upgrade costs in a year.

Interview & exam questions

1. A circuit is described as “redundant by default.” What does that actually protect against, and what does it not? Every circuit is provisioned with two connections to two MSEEs (primary + secondary), so it survives the loss of one MSEE or one link within the peering location. It does not protect against the peering location itself failing (both links share that facility), your provider’s backbone, your edge router, or a BGP misconfiguration. True facility diversity requires a second circuit at a different peering location (or ExpressRoute Metro).

2. You need ExpressRoute preferred and a VPN backup that only carries traffic on failure. Name the knob for each direction. Two independent levers. Azure → on-prem (return path): set the ExpressRoute connection routing weight higher than the VPN connection’s (higher wins). On-prem → Azure (forward path): on your edge, give ExpressRoute-learned routes higher local-preference (and/or weight) than VPN-learned routes, and AS-path-prepend the VPN advertisements so Azure also sees the VPN path as longer. Tune both or you get asymmetric routing.

3. Why does advertising a summary over ExpressRoute and specifics over the VPN break your failover? BGP’s first decision is longest-prefix-match, evaluated before any attribute. A more-specific /24 over the VPN beats a /16 summary over ExpressRoute for any address in that /24, so Azure sends that traffic down the VPN even while ExpressRoute is up and preferred by every attribute you set. The fix is to advertise the same prefixes over both paths.

4. What is the private-peering route limit, what happens if you exceed it, and how do you avoid it? 4,000 IPv4 routes on Standard, 10,000 on Premium. Exceeding it drops the BGP session — it does not degrade, it drops — taking the whole peering down. The usual cause is redistributing the entire on-prem table; the fix is to advertise summarized supernets (one 10.50.0.0/16 instead of 256 /24s) and, if you legitimately need more, move to Premium.

5. Your failover takes about three minutes and misses the RTO. What’s happening and how do you fix it? With default BGP timers, a session that is stuck-but-up isn’t declared dead until the hold timer expires — up to ~180 seconds. Enable BFD (Bidirectional Forwarding Detection) on the ExpressRoute BGP neighbours; it detects link failure in sub-second time and tears the session immediately, collapsing convergence to a second or two. Microsoft supports BFD on private peering.

6. What is FastPath, what does it require, and what does it bypass? FastPath programs the MSEE to send data-plane traffic directly to VMs, bypassing the ExpressRoute gateway (control-plane/BGP still flows through the gateway). It requires a high-capacity gateway (ErGw3Az/scalable), and advanced scenarios (Private Endpoint, spokes over peering) need enablement and typically Premium. Critically, it bypasses any UDR that forces traffic through an NVA/firewall — so do not FastPath subnets whose traffic must be inspected.

7. What is ExpressRoute Global Reach and when do you use it? Global Reach connects two of your on-prem sites to each other over the Microsoft backbone using their circuits’ private peering as transit — the sites can reach each other without your own WAN or a VNet hairpin. Use it to decouple site-to-site connectivity from a WAN provider or to connect branches across far regions privately. The on-prem prefixes must not overlap, and it is an add-on charge.

8. What is a route filter, which peering does it apply to, and what happens without one? A route filter is an allow-list of BGP community values that scopes which Microsoft public prefixes are advertised. It applies to Microsoft peering only (private peering is controlled by what your routers advertise/accept). Without an attached route filter, a Microsoft peering advertises nothing — the filter is what activates advertisement. Scope it to only the service/region communities you consume.

9. Difference between the two ExpressRoute SKU families, and can you switch? The family is the billing model: MeteredData (lower flat fee + per-GB egress) vs UnlimitedData (higher flat fee, no per-GB egress). Ingress is always free. You can move MeteredData → UnlimitedData in place, but not back — Azure blocks the downgrade — so start metered unless you already know you are a heavy-egress shop.

10. How do you test ExpressRoute failover properly, and what do most teams get wrong? In a window, administratively shut both ExpressRoute BGP sessions on your edge, confirm the effective route table flips to the VPN (VirtualNetworkGateway), measure the convergence gap with a continuous ping, and then drive production-representative load (iperf3) across the backup — before restoring. Most teams only test reachability (a ping succeeds) and never capacity, so an undersized backup passes every drill and collapses under real load.

11. Why alert on per-session BGP availability instead of aggregate circuit state? Because the most common ExpressRoute outage is a silent single-session failure: one of the two BGP sessions drops, aggregate circuit state still looks fine, the circuit runs non-redundant for weeks, and then the surviving MSEE goes into planned maintenance and everything fails at once. BgpAvailability < 100 per peering catches the single-session condition immediately.

12. Which BGP attribute governs which direction, and in what order are they evaluated? In evaluation order: weight (Cisco-proprietary, per-router, first) and local-preference (across your AS, second) are set by you and govern the forward path (on-prem→Azure); AS-path length (fourth) and MED (sixth) are what Azure sees among the routes you advertise and govern the return path (Azure→on-prem). Longest-prefix-match precedes all of them. Getting failover right means setting the correct knob for each direction.

These map to AZ-700 (Network Engineer Associate)design and implement hybrid connectivity, ExpressRoute, VPN, BGP — and to AZ-305 (Solutions Architect)design network connectivity and resilience. The BGP and failover specifics also touch AZ-104 for gateway configuration. A compact cert-mapping for revision:

Question theme Primary cert Exam objective area
Circuit/peering/SKU model, route filters AZ-700 Design & implement ExpressRoute
BGP direction, weight/local-pref/AS-path/MED AZ-700 Hybrid routing with BGP
ER+VPN coexistence, failover, active-active AZ-700 Hybrid connectivity resilience
Resiliency tiers, dual circuits, Metro, Global Reach AZ-305 Design network resilience
Gateways (ErGw*Az), FastPath, zone redundancy AZ-104 / AZ-700 Configure gateways & connectivity
Connection Monitor, metric alerts AZ-700 Monitor & troubleshoot networking

Quick check

  1. Your circuit is “redundant by default,” yet a Microsoft-announced MSEE maintenance took it fully down. What single condition made that possible, and what alert would have caught it?
  2. ExpressRoute is up, but Azure keeps returning traffic to on-prem over the VPN. Name the one Azure-side setting you check first, and what it should be relative to the VPN.
  3. True or false: advertising a /16 summary over ExpressRoute and the constituent /24s over the VPN is a good way to keep the VPN idle.
  4. Your failover takes ~180 seconds and misses the RTO. What feature reduces this to sub-second, and where do you enable it?
  5. You enabled FastPath and now a subnet’s traffic no longer passes through your firewall. Why, and what are your two options?

Answers

  1. Exactly one of the two BGP sessions was silently down (a router flap or change nobody alerted on), so the circuit ran non-redundant; when the surviving MSEE went into maintenance, there was no second session to carry traffic. The catch: alert on per-session BgpAvailability < 100 (checking --path primary and --path secondary), not on aggregate circuit state.
  2. The ExpressRoute connection routing weight — it must be higher than the VPN connection’s routing weight (higher wins when Azure chooses between gateways for the return path). Set ER to e.g. 20000 and VPN to 100. (Also verify on-prem local-pref for the forward direction.)
  3. False. Longest-prefix-match is evaluated before every attribute, so Azure sends the /24 traffic down the VPN even while ER is up and preferred — the VPN carries production, not idle. Advertise the same prefixes over both paths.
  4. BFD (Bidirectional Forwarding Detection) — it detects link failure in sub-second time and tears the BGP session immediately instead of waiting for the hold timer. Enable it on the ExpressRoute BGP neighbours on your edge routers (Microsoft supports BFD on private peering).
  5. FastPath bypasses the gateway and any UDR that forces traffic through an NVA/firewall, so the inspection hop is skipped. Options: (a) don’t FastPath the subnets that require inspection, accepting the gateway in the path for them; or (b) move inspection to a point FastPath doesn’t bypass (e.g. on-prem, or a design that doesn’t rely on a UDR-forced NVA in the Azure path).

Glossary

Next steps

You can now design an ExpressRoute topology to a stated availability target and defend every routing knob. Build outward:

AzureExpressRouteBGPHybrid NetworkingRoutingResiliencyVPN FailoverAZ-700
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