Virtual Network Gateway Connection
Virtual Network Gateway Connection
A Virtual Network Gateway Connection links an Azure Virtual Network Gateway to a remote endpoint. The connection type determines what it connects to:
- IPsec — Site-to-Site tunnel to an on-premises device via a Local Network Gateway
- ExpressRoute — Private circuit connection via an ExpressRoute circuit
- Vnet2Vnet — Encrypted tunnel between two Azure VNet gateways
Basic Configuration
The minimum Site-to-Site (IPSec) configuration:
Advanced Configuration
The following example shows all three connection types with common options:
Configuration Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
resource_group |
string | Yes | - | Key of the resource group where the connection is deployed |
type |
string | Yes | - | Connection type: IPsec, ExpressRoute, or Vnet2Vnet. Azurerm expects these exact values and input is case sensitive |
virtual_network_gateway |
string | Yes | - | Key of the Virtual Network Gateway resource |
local_network_gateway |
string | No* | null |
Key of the Local Network Gateway. Required for IPSec |
express_route_circuit |
string | No* | null |
Key of the ExpressRoute circuit. Required for ExpressRoute |
peer_virtual_network_gateway |
string | No* | null |
Key of the peer Virtual Network Gateway. Required for Vnet2Vnet |
shared_key |
string | No* | null |
Per-connection pre-shared key. Required for IPSec and Vnet2Vnet. Takes precedence over vpn_shared_keys when set. Prefer injecting via vpn_shared_keys — see Multiple Connections with Unique Shared Keys |
connection_protocol |
string | No | null |
IKE protocol version: IKEv1 or IKEv2. Defaults to IKEv2 when unset |
bgp_enabled |
bool | No | false |
Enables BGP dynamic routing on the connection. Requires BGP on both the VGW and LNG |
authorization_key |
string | No | null |
Authorization key for ExpressRoute circuits in a different subscription |
dpd_timeout_seconds |
number | No | null |
Dead peer detection timeout in seconds (9–3600) |
routing_weight |
number | No | 10 |
Routing weight for this connection. Higher values are preferred |
connection_mode |
string | No | null |
IPSec connection mode: Default, InitiatorOnly, or ResponderOnly |
local_azure_ip_address_enabled |
bool | No | null |
Use the private Azure IP instead of the public IP as the local tunnel endpoint |
express_route_gateway_bypass |
bool | No | false |
Enables ExpressRoute FastPath to bypass the gateway data plane. Requires ErGw3AZ or UltraPerformance SKU |
private_link_fast_path_enabled |
bool | No | false |
Enables Private Link FastPath on ExpressRoute connections |
use_policy_based_traffic_selectors |
bool | No | false |
Enables policy-based traffic selectors for compatibility with policy-based on-premises devices |
custom_bgp_addresses |
object | No | null |
Custom BGP addresses for the connection (see Custom BGP Addresses) |
ipsec_policy |
object | No | null |
Custom IPSec/IKE policy (see IPSec Policy) |
traffic_selector_policy |
list(object) | No | null |
Traffic selector policies for use_policy_based_traffic_selectors |
egress_nat_rule_ids |
list(string) | No | null |
Resource IDs of egress NAT rules to associate |
ingress_nat_rule_ids |
list(string) | No | null |
Resource IDs of ingress NAT rules to associate |
name |
string | No | Auto-generated | Custom name. If not specified, uses naming convention |
location |
string | No | Global location | Azure region where the connection is created |
timeouts |
object | No | null |
Custom Terraform operation timeouts (create, read, update, delete) |
tags |
map(string) | No | {} |
Tags merged with default tags |
* Required fields depend on connection type — see type-specific requirements above.
Shared Keys
Pre-shared keys for IPsec and Vnet2Vnet connections are injected at runtime via the vpn_shared_keys variable — a map(string) keyed by connection map key. Never commit PSKs to source control.
The shared key resolution order per connection is:
- Inline
shared_keyon the connection object (highest precedence, avoid in practice) vpn_shared_keys[<connection_key>]null
Single Connection
terraform.tfvars — no key inline:
virtual_network_gateway_connection = {
onprem01-to-vpn01 = {
resource_group = "hub"
type = "IPsec"
virtual_network_gateway = "vpn01"
local_network_gateway = "onprem01"
connection_protocol = "IKEv2"
bgp_enabled = true
}
}
Inject the key at runtime:
Multiple Connections with Unique Keys
terraform.tfvars — define all connections, no keys inline:
virtual_network_gateway_connection = {
onprem01-to-vpn01 = {
resource_group = "hub"
type = "IPsec"
virtual_network_gateway = "vpn01"
local_network_gateway = "onprem01"
connection_protocol = "IKEv2"
bgp_enabled = true
}
onprem02-to-vpn01 = {
resource_group = "hub"
type = "IPsec"
virtual_network_gateway = "vpn01"
local_network_gateway = "onprem02"
connection_protocol = "IKEv2"
bgp_enabled = true
}
}
Inject unique keys per connection at runtime:
$env:TF_VAR_vpn_shared_keys = '{"onprem01-to-vpn01" = "<key-for-onprem01>", "onprem02-to-vpn01" = "<key-for-onprem02>"}'
GitHub Actions
Store the JSON map as a repository secret (e.g. ALZ_VPN_SHARED_KEYS) and pass it as an environment variable:
- name: Set connectivity secrets
run: |
echo "TF_VAR_vpn_shared_keys=${{ secrets.ALZ_VPN_SHARED_KEYS }}" >> $GITHUB_ENV
The secret value is an HCL map keyed by connection name: {"onprem01-to-vpn01" = "<key1>", "onprem02-to-vpn01" = "<key2>"}.
Custom BGP Addresses
When bgp_enabled = true and the VGW is configured with custom APIPA BGP addresses, specify which APIPA address to use for this connection:
custom_bgp_addresses = {
primary = "169.254.21.1" # Must match a configured APIPA address on the VGW
secondary = "169.254.21.5" # Required for active-active VGW configurations
}
| Field | Type | Required | Description |
|---|---|---|---|
primary |
string | Yes | APIPA BGP address for the primary IP configuration |
secondary |
string | No | APIPA BGP address for the secondary IP configuration (active-active only) |
IPSec Policy
Override the default Azure IPSec/IKE proposal with a custom policy. When specified, all fields are required.
ipsec_policy = {
dh_group = "DHGroup14"
ike_encryption = "AES256"
ike_integrity = "SHA256"
ipsec_encryption = "AES256"
ipsec_integrity = "SHA256"
pfs_group = "PFS14"
sa_lifetime = 27000 # Seconds
sa_datasize = 102400 # KB
}
| Field | Type | Required | Description |
|---|---|---|---|
dh_group |
string | Yes | DH group for IKE phase 1: DHGroup1, DHGroup2, DHGroup14, DHGroup24, DHGroup2048, ECP256, ECP384, None |
ike_encryption |
string | Yes | IKE encryption algorithm: AES128, AES192, AES256, DES, DES3, GCMAES128, GCMAES256 |
ike_integrity |
string | Yes | IKE integrity algorithm: GCMAES128, GCMAES256, MD5, SHA1, SHA256, SHA384 |
ipsec_encryption |
string | Yes | IPSec encryption algorithm: AES128, AES192, AES256, DES, DES3, GCMAES128, GCMAES192, GCMAES256, None |
ipsec_integrity |
string | Yes | IPSec integrity algorithm: GCMAES128, GCMAES192, GCMAES256, MD5, SHA1, SHA256 |
pfs_group |
string | Yes | PFS group for IKE phase 2: ECP256, ECP384, PFS1, PFS2, PFS14, PFS24, PFS2048, PFSMM, None |
sa_lifetime |
number | No | IPSec SA lifetime in seconds |
sa_datasize |
number | No | IPSec SA lifetime in KB |
Naming Convention
Connection names are automatically generated using the following pattern:
{name_prefixes.virtual_network_gateway_connection}{key}{name_suffixes.virtual_network_gateway_connection}
For example, with the following prefixes and suffixes:
name_prefixes = {
virtual_network_gateway_connection = "con-connect-"
}
name_suffixes = {
virtual_network_gateway_connection = "-westus2"
}
virtual_network_gateway_connection = {
onprem01-to-vpn01 = {
resource_group = "hub"
type = "IPsec"
virtual_network_gateway = "vpn01"
local_network_gateway = "onprem01"
}
}
The resulting connection name would be: con-connect-onprem01-to-vpn01-westus2
To override automatic naming, specify a custom name:
virtual_network_gateway_connection = {
onprem01-to-vpn01 = {
name = "my-s2s-connection"
resource_group = "hub"
type = "IPsec"
virtual_network_gateway = "vpn01"
local_network_gateway = "onprem01"
}
}
Best Practices
- IKEv2 over IKEv1: Always set
connection_protocol = "IKEv2"— IKEv1 is legacy and lacks support for modern cipher suites and re-keying behavior - Strong Pre-Shared Keys: Use a randomly generated PSK of at least 32 characters; avoid dictionary words or predictable patterns. Never set
shared_keyinline in tfvars — inject viavpn_shared_keys(JSON map keyed by connection name) using environment variables or GitHub Actions secrets - BGP Requires End-to-End Configuration:
bgp_enabled = trueonly works when the Virtual Network Gateway hasbgp_enabled = trueand the Local Network Gateway hasbgp_settingsconfigured with a matching peer ASN and IP - FastPath Requirements:
express_route_gateway_bypass = truerequires the ER gateway SKU to beUltraPerformanceorErGw3AZ; setting it on lower SKUs will cause a plan-time or apply-time error - Authorization Keys for Cross-Subscription Circuits: When the ExpressRoute circuit lives in a different subscription, the circuit owner must generate an authorization key and provide it here via
authorization_key - One Connection Per Gateway-LNG Pair: Azure enforces a one-to-one relationship between a VPN gateway and a given Local Network Gateway; create a second LNG entry if you need multiple tunnels to the same on-premises device
- Shared Keys Must Match: The
shared_keymust be identical on both the Azure connection and the on-premises VPN device configuration