VNet Peering
Azure VNets are non-transitive by default. If Hub peers with Spoke A and Spoke B, the two spokes can't talk to each other through the hub unless I explicitly route traffic through an NVA or Azure Firewall in the hub. This trips people up constantly -- they set up peering, expect spoke-to-spoke communication, and it doesn't work.

Gateway transit
Gateway transit solves the connectivity problem by letting spokes use the hub's VPN or ExpressRoute gateway. The gateway on the hub learns about spoke subnets through the peering relationship, and the VNet gateway routes traffic between spokes -- creating transitive connectivity.
The key settings on the peering:
- Hub side: Enable
AllowGatewayTransit-- this shares the hub's gateway with the spoke - Spoke side: Enable
UseRemoteGateways-- this tells the spoke to use the hub's gateway instead of requiring its own
The VNet gateway routes traffic between each spoke, creating the gateway transit relationship.


NVA routing with FortiGate
When I need inspection, logging, or policy enforcement on spoke-to-spoke traffic, I deploy a network virtual appliance (NVA) in the hub instead of relying on gateway transit alone. FortiGate is the NVA I've deployed most often in this pattern.
Terraform modules
Two Terraform references I've used for FortiGate hub-spoke deployments:
- Fortinet's official VNET-Peering Terraform module -- the baseline for a single hub with peered spokes
- Canada PubSec ALZ FortiGate archetype -- a more opinionated landing zone deployment that includes FortiGate as the hub NVA
NVA deployment

Testing internet connectivity through the NVA
After deploying the NVA, I verify internet reachability from the FortiGate console before testing from spoke VMs. If this fails, the issue is on the NVA side (interface config, routing, or security policy) rather than the Azure route tables.

Allowing outbound internet from spokes
To allow machines in spokes to reach the internet through FortiGate, I configure an outbound security policy on the NVA. The route table on the spoke subnet sends 0.0.0.0/0 to the NVA's internal IP, and the NVA's policy decides what's allowed.
