Virtual Network Gateway
Virtual Network Gateway
Azure Virtual Network Gateway provides encrypted connectivity between Azure virtual networks and on-premises networks (VPN Gateway) or dedicated private connections (ExpressRoute Gateway). The Virtual Network Gateway module allows you to create and configure gateways with BGP, active-active mode, point-to-site VPN, and custom routing.
Basic Configuration
Below is the most basic example of creating a VPN gateway:
This creates a VPN gateway with the following defaults:
- VPN Type: RouteBased
- Active-Active: Disabled
- BGP: Disabled
- IPSec Replay Protection: Enabled
- Private IP Address Allocation: Dynamic
- IP Configuration Name: vnetGatewayConfig
- Location: Uses the location defined in the main configuration
Advanced Configuration
The following example demonstrates a VPN gateway with BGP enabled and an ExpressRoute gateway in the same configuration:
Configuration Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
resource_group |
string | Yes | - | Key of the resource group where the gateway is deployed |
type |
string | Yes | - | Gateway type: Vpn or ExpressRoute |
sku |
string | Yes | - | Gateway SKU (e.g. VpnGw1AZ, VpnGw2AZ, ErGw1AZ, Standard) |
ip_configuration |
list(object) | Yes | - | One or more IP configuration blocks (see IP Configuration) |
name |
string | No | Auto-generated | Custom name. If not specified, uses naming convention |
location |
string | No | Global location | Azure region where the gateway is created |
vpn_type |
string | No | RouteBased |
VPN routing type: RouteBased or PolicyBased |
generation |
string | No | null |
Gateway generation: Generation1, Generation2, or None |
active_active |
bool | No | false |
Enables active-active mode (requires two ip_configuration entries and two public IPs) |
bgp_enabled |
bool | No | false |
Enables BGP on the gateway |
bgp_settings |
object | No | null |
BGP configuration (see BGP Settings) |
bgp_route_translation_for_nat_enabled |
bool | No | false |
Enables BGP route translation for NAT |
custom_route |
object | No | null |
Custom routes to advertise; specify address_prefixes |
default_local_network_gateway_id |
string | No | null |
Resource ID of the default local network gateway for forced tunneling |
dns_forwarding_enabled |
bool | No | null |
Enables DNS forwarding on the gateway |
edge_zone |
string | No | null |
Edge zone for the gateway |
ip_sec_replay_protection_enabled |
bool | No | true |
Enables IPSec replay protection |
policy_group |
list(object) | No | null |
Policy groups for per-user VPN (see Policy Groups) |
private_ip_address_enabled |
bool | No | null |
Enables private IP on the gateway |
remote_vnet_traffic_enabled |
bool | No | false |
Enables remote VNet traffic |
virtual_wan_traffic_enabled |
bool | No | false |
Enables Virtual WAN traffic |
vpn_client_configuration |
object | No | null |
Point-to-site VPN settings (see VPN Client Configuration) |
timeouts |
object | No | null |
Custom Terraform operation timeouts (create, read, update, delete) |
tags |
map(string) | No | {} |
Tags merged with default tags |
IP Configuration
Each entry in ip_configuration maps to one gateway IP configuration block. At minimum, provide a subnet key pointing to the GatewaySubnet.
ip_configuration = [
{
name = "vnetGatewayConfig" # Default if omitted
private_ip_address_allocation = "Dynamic" # Default if omitted
subnet = "hub.GatewaySubnet" # Required — key from the subnet variable
public_ip = "vpn01" # Optional — key from the public_ip variable
}
]
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | No | vnetGatewayConfig |
Name for this IP configuration |
private_ip_address_allocation |
string | No | Dynamic |
Private IP allocation method: Dynamic or Static |
subnet |
string | Yes | - | Key of the subnet entry (must be the GatewaySubnet) |
public_ip |
string | No | null |
Key of the public IP entry. Required for VPN gateways; DO NOT DEFINE for ExpressRoute |
Active-active mode requires two ip_configuration entries with distinct names and two separate public IPs.
BGP Settings
BGP settings are optional but required when bgp_enabled = true is set for production deployments.
bgp_settings = {
asn = 65515 #CHANGEME - this is Azure default value and should not be used in production deployments
peer_weight = 0
peering_addresses = [
{
ip_configuration_name = "vnetGatewayConfig"
apipa_addresses = ["169.254.21.1"] # APIPA range: 169.254.21.0 - 169.254.22.255
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
asn |
number | No | BGP autonomous system number for the Azure gateway. Azure default is 65515 and also reserves 65518, 65519, 65520, 4294967293, 4294967294. Avoid all when setting for production deployments |
peer_weight |
number | No | Weight added to BGP routes learned from this peer |
peering_addresses |
list(object) | No | Per-IP-configuration BGP peering address overrides |
peering_addresses[].ip_configuration_name |
string | No | Name of the IP configuration this peering address applies to |
peering_addresses[].apipa_addresses |
list(string) | No | Custom APIPA BGP addresses (must be in 169.254.21.0/24 or 169.254.22.0/24) |
VPN Client Configuration
Point-to-site VPN configuration. Supports Azure AD, certificate, and RADIUS authentication.
vpn_client_configuration = {
address_space = ["172.16.100.0/24"]
vpn_client_protocols = ["OpenVPN"]
vpn_auth_types = ["AAD"]
aad_tenant = "https://login.microsoftonline.com/<TENANT_ID>/"
aad_audience = "41b23e61-6c1e-4545-b367-cd054e0ed4b4" # Azure VPN app ID
aad_issuer = "https://sts.windows.net/<TENANT_ID>/"
}
Certificate authentication:
vpn_client_configuration = {
address_space = ["172.16.100.0/24"]
vpn_client_protocols = ["IkeV2", "SSTP"]
vpn_auth_types = ["Certificate"]
root_certificate = [
{
name = "VPNRootCert"
public_cert_data = "<base64-encoded-cert-data>"
}
]
revoked_certificate = [
{
name = "RevokedCert"
thumbprint = "<thumbprint>"
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
address_space |
list(string) | Yes | IP address pool for VPN clients |
vpn_client_protocols |
list(string) | No | Protocols: IkeV2, OpenVPN, SSTP |
vpn_auth_types |
list(string) | No | Auth types: AAD, Certificate, Radius |
aad_tenant |
string | No | AAD tenant URL (required for AAD auth) |
aad_audience |
string | No | AAD application audience (required for AAD auth) |
aad_issuer |
string | No | AAD issuer URL (required for AAD auth) |
radius_server_address |
string | No | Primary RADIUS server address |
radius_server_secret |
string | No | Primary RADIUS server secret |
radius_server |
list(object) | No | Multiple RADIUS servers with address, secret, score |
root_certificate |
list(object) | No | Trusted root certificates for certificate auth |
revoked_certificate |
list(object) | No | Revoked client certificates |
ipsec_policy |
object | No | Custom IPSec policy for the VPN client |
virtual_network_gateway_client_connection |
list(object) | No | Per-policy-group client connections |
Policy Groups
Policy groups enable per-user or per-group VPN routing policies for point-to-site scenarios.
policy_group = [
{
name = "DevGroup"
is_default = true
priority = 0
policy_member = [
{
name = "devuser"
type = "AADGroupId"
value = "<AAD-GROUP-OBJECT-ID>"
}
]
}
]
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | - | Name of the policy group |
is_default |
bool | No | false |
Whether this is the default policy group |
priority |
number | No | 0 |
Priority of the policy group |
policy_member |
list(object) | Yes | - | Members of the policy group |
policy_member[].name |
string | Yes | - | Name for this policy member |
policy_member[].type |
string | Yes | - | Member type: AADGroupId, CertificateGroupId, RadiusAzureGroupId |
policy_member[].value |
string | Yes | - | Value for the member type (e.g. AAD group object ID) |
Naming Convention
Gateway names are automatically generated using the following pattern:
For example, with the following prefixes and suffixes:
name_prefixes = {
virtual_network_gateway = "vgw-connect-"
}
name_suffixes = {
virtual_network_gateway = "-westus2"
}
virtual_network_gateway = {
vpn01 = {
resource_group = "hub"
type = "Vpn"
sku = "VpnGw1AZ"
ip_configuration = [{
subnet = "hub.GatewaySubnet"
public_ip = "vpn01"
}]
}
}
The resulting gateway name would be: vgw-connect-vpn01-westus2
To override automatic naming, specify a custom name:
virtual_network_gateway = {
vpn01 = {
name = "my-custom-vpn-gateway"
resource_group = "hub"
type = "Vpn"
sku = "VpnGw1AZ"
ip_configuration = [{
subnet = "hub.GatewaySubnet"
public_ip = "vpn01"
}]
}
}
Gateway Types and SKUs
VPN Gateway SKUs:
| SKU | Max Throughput | Max S2S Tunnels | Supports Active-Active | Generation |
|---|---|---|---|---|
VpnGw1 / VpnGw1AZ |
650 Mbps | 30 | Yes | Generation1 |
VpnGw2 / VpnGw2AZ |
1 Gbps | 30 | Yes | Generation1/2 |
VpnGw3 / VpnGw3AZ |
1.25 Gbps | 30 | Yes | Generation1/2 |
VpnGw4 / VpnGw4AZ |
5 Gbps | 100 | Yes | Generation2 |
VpnGw5 / VpnGw5AZ |
10 Gbps | 100 | Yes | Generation2 |
SKUs ending in AZ are zone-redundant and recommended for production. Basic SKU is legacy and not recommended for new deployments.
ExpressRoute Gateway SKUs: Standard, HighPerformance, UltraPerformance, ErGw1AZ, ErGw2AZ, ErGw3AZ
Best Practices
Shared
- Zone-Redundant SKUs: Use
AZ-suffixed SKUs (VpnGw1AZ,ErGw1AZ) for production to protect against zone failures - GatewaySubnet: Always use a dedicated subnet named
GatewaySubnetwith a minimum/27prefix; do not place other resources in it - Separate Gateways per Type: Deploy VPN and ExpressRoute gateways as separate resources, even when colocating in the same VNet
VPN Gateway
- BGP over Static Routes: Enable BGP (
bgp_enabled = true) for dynamic routing — required for active-active and more resilient failover - Unique ASNs: Ensure the Azure gateway ASN (
bgp_settings.asn) differs from your on-premises device ASN; avoid reserved ranges65515–65520 - Active-Active for HA: Use
active_active = truewith twoip_configurationentries and two public IPs; pair with BGP for full redundancy - Generation2 for Performance: Use
generation = "Generation2"withVpnGw2AZor higher for improved throughput and lower latency - APIPA for BGP: Configure
peering_addresses[].apipa_addressesin the169.254.21.0/24or169.254.22.0/24range to avoid BGP peer IP conflicts with address space
ExpressRoute Gateway
- Right-Size the SKU: Match the SKU to your circuit bandwidth —
ErGw1AZsupports up to 1 Gbps,ErGw2AZup to 2 Gbps,ErGw3AZup to 10 Gbps; under-sizing causes dropped traffic at the gateway, not at the circuit - FastPath for High Throughput: For
UltraPerformanceorErGw3AZSKUs, enable ExpressRoute FastPath on the connection resource to bypass the gateway data plane for latency-sensitive workloads - Redundant Circuits: Use two ExpressRoute circuits from different peering locations for resilience; a single circuit with a single gateway is a single point of failure even with a zone-redundant SKU
- Private Peering Only: Set
private_ip_address_enabled = false(default) unless Microsoft peering is explicitly required; private peering is the standard path for hybrid connectivity - Co-exist with VPN: When deploying both an ExpressRoute and VPN gateway in the same VNet for failover, both must be in the same
GatewaySubnet— size it/27or larger to accommodate both gateway IPs