Skip to content

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:

1
2
3
4
5
6
7
8
virtual_network_gateway_connection = {
    onprem01-to-vpn01 = {
        resource_group          = "hub"
        type                    = "IPsec"
        virtual_network_gateway = "vpn01"
        local_network_gateway   = "onprem01"
    }
}

Advanced Configuration

The following example shows all three connection types with common options:

virtual_network_gateway_connection = {
    # Site-to-Site with BGP and IKEv2
    onprem01-to-vpn01 = {
        resource_group          = "hub"
        type                    = "IPsec"
        virtual_network_gateway = "vpn01"
        local_network_gateway   = "onprem01"
        connection_protocol     = "IKEv2"
        bgp_enabled             = true

        custom_bgp_addresses = {
            primary = "169.254.21.1" # Must match an APIPA address configured on the VGW
        }
    }

    # ExpressRoute connection
    er-to-er01 = {
        resource_group          = "hub"
        type                    = "ExpressRoute"
        virtual_network_gateway = "er01"
        express_route_circuit   = "er01"
        authorization_key       = "CHANGEME" # Required when circuit is in a different subscription

        express_route_gateway_bypass = false # Set true for FastPath (ErGw3AZ / UltraPerformance only)
    }

    # VNet-to-VNet
    hub-to-spoke = {
        resource_group               = "hub"
        type                         = "Vnet2Vnet"
        virtual_network_gateway      = "vpn01"
        peer_virtual_network_gateway = "spoke-vpn01"
    }
}

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 (93600)
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:

  1. Inline shared_key on the connection object (highest precedence, avoid in practice)
  2. vpn_shared_keys[<connection_key>]
  3. 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:

$env:TF_VAR_vpn_shared_keys = '{"onprem01-to-vpn01" = "<your-shared-key>"}'

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

  1. IKEv2 over IKEv1: Always set connection_protocol = "IKEv2" — IKEv1 is legacy and lacks support for modern cipher suites and re-keying behavior
  2. Strong Pre-Shared Keys: Use a randomly generated PSK of at least 32 characters; avoid dictionary words or predictable patterns. Never set shared_key inline in tfvars — inject via vpn_shared_keys (JSON map keyed by connection name) using environment variables or GitHub Actions secrets
  3. BGP Requires End-to-End Configuration: bgp_enabled = true only works when the Virtual Network Gateway has bgp_enabled = true and the Local Network Gateway has bgp_settings configured with a matching peer ASN and IP
  4. FastPath Requirements: express_route_gateway_bypass = true requires the ER gateway SKU to be UltraPerformance or ErGw3AZ; setting it on lower SKUs will cause a plan-time or apply-time error
  5. 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
  6. 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
  7. Shared Keys Must Match: The shared_key must be identical on both the Azure connection and the on-premises VPN device configuration