The gcp:compute/haVpnGateway:HaVpnGateway resource, part of the Pulumi GCP provider, provisions a Google-managed HA VPN gateway that terminates VPN tunnels with automatic failover across two interfaces. This guide focuses on three capabilities: basic gateway creation, dual-stack IPv4/IPv6 support, and Cloud Interconnect integration.
HA VPN gateways attach to VPC networks and may integrate with Cloud Interconnect attachments and Cloud Routers for encrypted hybrid connectivity. The examples are intentionally small. Combine them with VPN tunnels, external VPN gateway definitions, and BGP configuration for complete connectivity.
Create a gateway for VPN tunnel termination
Most HA VPN deployments start by creating a gateway that terminates VPN tunnels from on-premises or other cloud environments, providing two interfaces for automatic failover.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const network1 = new gcp.compute.Network("network1", {
name: "network1",
autoCreateSubnetworks: false,
});
const haGateway1 = new gcp.compute.HaVpnGateway("ha_gateway1", {
region: "us-central1",
name: "ha-vpn-1",
network: network1.id,
});
import pulumi
import pulumi_gcp as gcp
network1 = gcp.compute.Network("network1",
name="network1",
auto_create_subnetworks=False)
ha_gateway1 = gcp.compute.HaVpnGateway("ha_gateway1",
region="us-central1",
name="ha-vpn-1",
network=network1.id)
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
network1, err := compute.NewNetwork(ctx, "network1", &compute.NetworkArgs{
Name: pulumi.String("network1"),
AutoCreateSubnetworks: pulumi.Bool(false),
})
if err != nil {
return err
}
_, err = compute.NewHaVpnGateway(ctx, "ha_gateway1", &compute.HaVpnGatewayArgs{
Region: pulumi.String("us-central1"),
Name: pulumi.String("ha-vpn-1"),
Network: network1.ID(),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var network1 = new Gcp.Compute.Network("network1", new()
{
Name = "network1",
AutoCreateSubnetworks = false,
});
var haGateway1 = new Gcp.Compute.HaVpnGateway("ha_gateway1", new()
{
Region = "us-central1",
Name = "ha-vpn-1",
Network = network1.Id,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.HaVpnGateway;
import com.pulumi.gcp.compute.HaVpnGatewayArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var network1 = new Network("network1", NetworkArgs.builder()
.name("network1")
.autoCreateSubnetworks(false)
.build());
var haGateway1 = new HaVpnGateway("haGateway1", HaVpnGatewayArgs.builder()
.region("us-central1")
.name("ha-vpn-1")
.network(network1.id())
.build());
}
}
resources:
haGateway1:
type: gcp:compute:HaVpnGateway
name: ha_gateway1
properties:
region: us-central1
name: ha-vpn-1
network: ${network1.id}
network1:
type: gcp:compute:Network
properties:
name: network1
autoCreateSubnetworks: false
The gateway provisions two interfaces in the specified region, each with its own public IP address. The network property attaches the gateway to your VPC. After creating the gateway, you configure VPN tunnels that reference these interfaces and connect to your peer VPN devices.
Enable dual-stack IPv4 and IPv6 support
Applications migrating to IPv6 or requiring dual-stack connectivity can configure the gateway to support both IP families.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const network1 = new gcp.compute.Network("network1", {
name: "network1",
autoCreateSubnetworks: false,
});
const haGateway1 = new gcp.compute.HaVpnGateway("ha_gateway1", {
region: "us-central1",
name: "ha-vpn-1",
network: network1.id,
stackType: "IPV4_IPV6",
labels: {
mykey: "myvalue",
},
});
import pulumi
import pulumi_gcp as gcp
network1 = gcp.compute.Network("network1",
name="network1",
auto_create_subnetworks=False)
ha_gateway1 = gcp.compute.HaVpnGateway("ha_gateway1",
region="us-central1",
name="ha-vpn-1",
network=network1.id,
stack_type="IPV4_IPV6",
labels={
"mykey": "myvalue",
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
network1, err := compute.NewNetwork(ctx, "network1", &compute.NetworkArgs{
Name: pulumi.String("network1"),
AutoCreateSubnetworks: pulumi.Bool(false),
})
if err != nil {
return err
}
_, err = compute.NewHaVpnGateway(ctx, "ha_gateway1", &compute.HaVpnGatewayArgs{
Region: pulumi.String("us-central1"),
Name: pulumi.String("ha-vpn-1"),
Network: network1.ID(),
StackType: pulumi.String("IPV4_IPV6"),
Labels: pulumi.StringMap{
"mykey": pulumi.String("myvalue"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var network1 = new Gcp.Compute.Network("network1", new()
{
Name = "network1",
AutoCreateSubnetworks = false,
});
var haGateway1 = new Gcp.Compute.HaVpnGateway("ha_gateway1", new()
{
Region = "us-central1",
Name = "ha-vpn-1",
Network = network1.Id,
StackType = "IPV4_IPV6",
Labels =
{
{ "mykey", "myvalue" },
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.HaVpnGateway;
import com.pulumi.gcp.compute.HaVpnGatewayArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var network1 = new Network("network1", NetworkArgs.builder()
.name("network1")
.autoCreateSubnetworks(false)
.build());
var haGateway1 = new HaVpnGateway("haGateway1", HaVpnGatewayArgs.builder()
.region("us-central1")
.name("ha-vpn-1")
.network(network1.id())
.stackType("IPV4_IPV6")
.labels(Map.of("mykey", "myvalue"))
.build());
}
}
resources:
haGateway1:
type: gcp:compute:HaVpnGateway
name: ha_gateway1
properties:
region: us-central1
name: ha-vpn-1
network: ${network1.id}
stackType: IPV4_IPV6
labels:
mykey: myvalue
network1:
type: gcp:compute:Network
properties:
name: network1
autoCreateSubnetworks: false
The stackType property controls which IP protocols the gateway interfaces support. Setting it to IPV4_IPV6 enables dual-stack operation, allowing you to establish VPN tunnels over either IPv4 or IPv6. The labels property adds metadata for organization and cost tracking.
Attach to Cloud Interconnect for encrypted hybrid connectivity
Organizations with dedicated Cloud Interconnect circuits can layer IPsec encryption over the physical connection by attaching the VPN gateway to Interconnect attachments.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const network = new gcp.compute.Network("network", {
name: "test-network",
autoCreateSubnetworks: false,
});
const address1 = new gcp.compute.Address("address1", {
name: "test-address1",
addressType: "INTERNAL",
purpose: "IPSEC_INTERCONNECT",
address: "192.168.1.0",
prefixLength: 29,
network: network.selfLink,
});
const router = new gcp.compute.Router("router", {
name: "test-router",
network: network.name,
encryptedInterconnectRouter: true,
bgp: {
asn: 16550,
},
});
const attachment1 = new gcp.compute.InterconnectAttachment("attachment1", {
name: "test-interconnect-attachment1",
edgeAvailabilityDomain: "AVAILABILITY_DOMAIN_1",
type: "PARTNER",
router: router.id,
encryption: "IPSEC",
ipsecInternalAddresses: [address1.selfLink],
});
const address2 = new gcp.compute.Address("address2", {
name: "test-address2",
addressType: "INTERNAL",
purpose: "IPSEC_INTERCONNECT",
address: "192.168.2.0",
prefixLength: 29,
network: network.selfLink,
});
const attachment2 = new gcp.compute.InterconnectAttachment("attachment2", {
name: "test-interconnect-attachment2",
edgeAvailabilityDomain: "AVAILABILITY_DOMAIN_2",
type: "PARTNER",
router: router.id,
encryption: "IPSEC",
ipsecInternalAddresses: [address2.selfLink],
});
const vpn_gateway = new gcp.compute.HaVpnGateway("vpn-gateway", {
name: "test-ha-vpngw",
network: network.id,
vpnInterfaces: [
{
id: 0,
interconnectAttachment: attachment1.selfLink,
},
{
id: 1,
interconnectAttachment: attachment2.selfLink,
},
],
});
import pulumi
import pulumi_gcp as gcp
network = gcp.compute.Network("network",
name="test-network",
auto_create_subnetworks=False)
address1 = gcp.compute.Address("address1",
name="test-address1",
address_type="INTERNAL",
purpose="IPSEC_INTERCONNECT",
address="192.168.1.0",
prefix_length=29,
network=network.self_link)
router = gcp.compute.Router("router",
name="test-router",
network=network.name,
encrypted_interconnect_router=True,
bgp={
"asn": 16550,
})
attachment1 = gcp.compute.InterconnectAttachment("attachment1",
name="test-interconnect-attachment1",
edge_availability_domain="AVAILABILITY_DOMAIN_1",
type="PARTNER",
router=router.id,
encryption="IPSEC",
ipsec_internal_addresses=[address1.self_link])
address2 = gcp.compute.Address("address2",
name="test-address2",
address_type="INTERNAL",
purpose="IPSEC_INTERCONNECT",
address="192.168.2.0",
prefix_length=29,
network=network.self_link)
attachment2 = gcp.compute.InterconnectAttachment("attachment2",
name="test-interconnect-attachment2",
edge_availability_domain="AVAILABILITY_DOMAIN_2",
type="PARTNER",
router=router.id,
encryption="IPSEC",
ipsec_internal_addresses=[address2.self_link])
vpn_gateway = gcp.compute.HaVpnGateway("vpn-gateway",
name="test-ha-vpngw",
network=network.id,
vpn_interfaces=[
{
"id": 0,
"interconnect_attachment": attachment1.self_link,
},
{
"id": 1,
"interconnect_attachment": attachment2.self_link,
},
])
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
network, err := compute.NewNetwork(ctx, "network", &compute.NetworkArgs{
Name: pulumi.String("test-network"),
AutoCreateSubnetworks: pulumi.Bool(false),
})
if err != nil {
return err
}
address1, err := compute.NewAddress(ctx, "address1", &compute.AddressArgs{
Name: pulumi.String("test-address1"),
AddressType: pulumi.String("INTERNAL"),
Purpose: pulumi.String("IPSEC_INTERCONNECT"),
Address: pulumi.String("192.168.1.0"),
PrefixLength: pulumi.Int(29),
Network: network.SelfLink,
})
if err != nil {
return err
}
router, err := compute.NewRouter(ctx, "router", &compute.RouterArgs{
Name: pulumi.String("test-router"),
Network: network.Name,
EncryptedInterconnectRouter: pulumi.Bool(true),
Bgp: &compute.RouterBgpArgs{
Asn: pulumi.Int(16550),
},
})
if err != nil {
return err
}
attachment1, err := compute.NewInterconnectAttachment(ctx, "attachment1", &compute.InterconnectAttachmentArgs{
Name: pulumi.String("test-interconnect-attachment1"),
EdgeAvailabilityDomain: pulumi.String("AVAILABILITY_DOMAIN_1"),
Type: pulumi.String("PARTNER"),
Router: router.ID(),
Encryption: pulumi.String("IPSEC"),
IpsecInternalAddresses: pulumi.StringArray{
address1.SelfLink,
},
})
if err != nil {
return err
}
address2, err := compute.NewAddress(ctx, "address2", &compute.AddressArgs{
Name: pulumi.String("test-address2"),
AddressType: pulumi.String("INTERNAL"),
Purpose: pulumi.String("IPSEC_INTERCONNECT"),
Address: pulumi.String("192.168.2.0"),
PrefixLength: pulumi.Int(29),
Network: network.SelfLink,
})
if err != nil {
return err
}
attachment2, err := compute.NewInterconnectAttachment(ctx, "attachment2", &compute.InterconnectAttachmentArgs{
Name: pulumi.String("test-interconnect-attachment2"),
EdgeAvailabilityDomain: pulumi.String("AVAILABILITY_DOMAIN_2"),
Type: pulumi.String("PARTNER"),
Router: router.ID(),
Encryption: pulumi.String("IPSEC"),
IpsecInternalAddresses: pulumi.StringArray{
address2.SelfLink,
},
})
if err != nil {
return err
}
_, err = compute.NewHaVpnGateway(ctx, "vpn-gateway", &compute.HaVpnGatewayArgs{
Name: pulumi.String("test-ha-vpngw"),
Network: network.ID(),
VpnInterfaces: compute.HaVpnGatewayVpnInterfaceArray{
&compute.HaVpnGatewayVpnInterfaceArgs{
Id: pulumi.Int(0),
InterconnectAttachment: attachment1.SelfLink,
},
&compute.HaVpnGatewayVpnInterfaceArgs{
Id: pulumi.Int(1),
InterconnectAttachment: attachment2.SelfLink,
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;
return await Deployment.RunAsync(() =>
{
var network = new Gcp.Compute.Network("network", new()
{
Name = "test-network",
AutoCreateSubnetworks = false,
});
var address1 = new Gcp.Compute.Address("address1", new()
{
Name = "test-address1",
AddressType = "INTERNAL",
Purpose = "IPSEC_INTERCONNECT",
IPAddress = "192.168.1.0",
PrefixLength = 29,
Network = network.SelfLink,
});
var router = new Gcp.Compute.Router("router", new()
{
Name = "test-router",
Network = network.Name,
EncryptedInterconnectRouter = true,
Bgp = new Gcp.Compute.Inputs.RouterBgpArgs
{
Asn = 16550,
},
});
var attachment1 = new Gcp.Compute.InterconnectAttachment("attachment1", new()
{
Name = "test-interconnect-attachment1",
EdgeAvailabilityDomain = "AVAILABILITY_DOMAIN_1",
Type = "PARTNER",
Router = router.Id,
Encryption = "IPSEC",
IpsecInternalAddresses = new[]
{
address1.SelfLink,
},
});
var address2 = new Gcp.Compute.Address("address2", new()
{
Name = "test-address2",
AddressType = "INTERNAL",
Purpose = "IPSEC_INTERCONNECT",
IPAddress = "192.168.2.0",
PrefixLength = 29,
Network = network.SelfLink,
});
var attachment2 = new Gcp.Compute.InterconnectAttachment("attachment2", new()
{
Name = "test-interconnect-attachment2",
EdgeAvailabilityDomain = "AVAILABILITY_DOMAIN_2",
Type = "PARTNER",
Router = router.Id,
Encryption = "IPSEC",
IpsecInternalAddresses = new[]
{
address2.SelfLink,
},
});
var vpn_gateway = new Gcp.Compute.HaVpnGateway("vpn-gateway", new()
{
Name = "test-ha-vpngw",
Network = network.Id,
VpnInterfaces = new[]
{
new Gcp.Compute.Inputs.HaVpnGatewayVpnInterfaceArgs
{
Id = 0,
InterconnectAttachment = attachment1.SelfLink,
},
new Gcp.Compute.Inputs.HaVpnGatewayVpnInterfaceArgs
{
Id = 1,
InterconnectAttachment = attachment2.SelfLink,
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Address;
import com.pulumi.gcp.compute.AddressArgs;
import com.pulumi.gcp.compute.Router;
import com.pulumi.gcp.compute.RouterArgs;
import com.pulumi.gcp.compute.inputs.RouterBgpArgs;
import com.pulumi.gcp.compute.InterconnectAttachment;
import com.pulumi.gcp.compute.InterconnectAttachmentArgs;
import com.pulumi.gcp.compute.HaVpnGateway;
import com.pulumi.gcp.compute.HaVpnGatewayArgs;
import com.pulumi.gcp.compute.inputs.HaVpnGatewayVpnInterfaceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var network = new Network("network", NetworkArgs.builder()
.name("test-network")
.autoCreateSubnetworks(false)
.build());
var address1 = new Address("address1", AddressArgs.builder()
.name("test-address1")
.addressType("INTERNAL")
.purpose("IPSEC_INTERCONNECT")
.address("192.168.1.0")
.prefixLength(29)
.network(network.selfLink())
.build());
var router = new Router("router", RouterArgs.builder()
.name("test-router")
.network(network.name())
.encryptedInterconnectRouter(true)
.bgp(RouterBgpArgs.builder()
.asn(16550)
.build())
.build());
var attachment1 = new InterconnectAttachment("attachment1", InterconnectAttachmentArgs.builder()
.name("test-interconnect-attachment1")
.edgeAvailabilityDomain("AVAILABILITY_DOMAIN_1")
.type("PARTNER")
.router(router.id())
.encryption("IPSEC")
.ipsecInternalAddresses(address1.selfLink())
.build());
var address2 = new Address("address2", AddressArgs.builder()
.name("test-address2")
.addressType("INTERNAL")
.purpose("IPSEC_INTERCONNECT")
.address("192.168.2.0")
.prefixLength(29)
.network(network.selfLink())
.build());
var attachment2 = new InterconnectAttachment("attachment2", InterconnectAttachmentArgs.builder()
.name("test-interconnect-attachment2")
.edgeAvailabilityDomain("AVAILABILITY_DOMAIN_2")
.type("PARTNER")
.router(router.id())
.encryption("IPSEC")
.ipsecInternalAddresses(address2.selfLink())
.build());
var vpn_gateway = new HaVpnGateway("vpn-gateway", HaVpnGatewayArgs.builder()
.name("test-ha-vpngw")
.network(network.id())
.vpnInterfaces(
HaVpnGatewayVpnInterfaceArgs.builder()
.id(0)
.interconnectAttachment(attachment1.selfLink())
.build(),
HaVpnGatewayVpnInterfaceArgs.builder()
.id(1)
.interconnectAttachment(attachment2.selfLink())
.build())
.build());
}
}
resources:
vpn-gateway:
type: gcp:compute:HaVpnGateway
properties:
name: test-ha-vpngw
network: ${network.id}
vpnInterfaces:
- id: 0
interconnectAttachment: ${attachment1.selfLink}
- id: 1
interconnectAttachment: ${attachment2.selfLink}
attachment1:
type: gcp:compute:InterconnectAttachment
properties:
name: test-interconnect-attachment1
edgeAvailabilityDomain: AVAILABILITY_DOMAIN_1
type: PARTNER
router: ${router.id}
encryption: IPSEC
ipsecInternalAddresses:
- ${address1.selfLink}
attachment2:
type: gcp:compute:InterconnectAttachment
properties:
name: test-interconnect-attachment2
edgeAvailabilityDomain: AVAILABILITY_DOMAIN_2
type: PARTNER
router: ${router.id}
encryption: IPSEC
ipsecInternalAddresses:
- ${address2.selfLink}
address1:
type: gcp:compute:Address
properties:
name: test-address1
addressType: INTERNAL
purpose: IPSEC_INTERCONNECT
address: 192.168.1.0
prefixLength: 29
network: ${network.selfLink}
address2:
type: gcp:compute:Address
properties:
name: test-address2
addressType: INTERNAL
purpose: IPSEC_INTERCONNECT
address: 192.168.2.0
prefixLength: 29
network: ${network.selfLink}
router:
type: gcp:compute:Router
properties:
name: test-router
network: ${network.name}
encryptedInterconnectRouter: true
bgp:
asn: 16550
network:
type: gcp:compute:Network
properties:
name: test-network
autoCreateSubnetworks: false
The vpnInterfaces property maps each gateway interface to an Interconnect attachment, enabling encrypted traffic over your dedicated circuits. Each attachment requires an internal IP address range with IPSEC_INTERCONNECT purpose. The router must have encryptedInterconnectRouter enabled to support this configuration. This setup combines the bandwidth and predictability of Interconnect with the security of IPsec encryption.
Beyond these examples
These snippets focus on specific HA VPN gateway features: basic gateway creation, dual-stack IPv4/IPv6 configuration, and Cloud Interconnect integration. They’re intentionally minimal rather than full VPN solutions.
The examples may reference pre-existing infrastructure such as VPC networks, Cloud Interconnect circuits and attachments, and Cloud Router with BGP configuration. They focus on gateway provisioning rather than complete VPN connectivity.
To keep things focused, common VPN patterns are omitted, including:
- VPN tunnel configuration (separate resource)
- External VPN gateway definitions for peer connectivity
- BGP session setup and route exchange
- Firewall rules for VPN traffic
These omissions are intentional: the goal is to illustrate how each gateway feature is wired, not provide drop-in VPN modules. See the HA VPN Gateway resource reference for all available configuration options.
Let's configure GCP HA VPN Gateways
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Configuration & Naming
a-z?. This means only lowercase letters, digits, and dashes are allowed, and the name cannot end with a dash.name, region, and network. The gateway defaults to IPV4_ONLY stack type if not specified.Immutability & Updates
name, network, region, vpnInterfaces, stackType, gatewayIpVersion, and description. Only labels can be modified after creation.IP Protocol Support
stackType identifies which IP protocols are enabled on the gateway (IPV4_ONLY, IPV4_IPV6, or IPV6_ONLY), while gatewayIpVersion specifies the IP family for the gateway interface IPs (IPV4 or IPV6). Both default to IPv4 if not specified.stackType to IPV4_IPV6 (for dual-stack) or IPV6_ONLY when creating the gateway. This property is immutable, so you must configure it at creation time.stackType is IPV4_ONLY and gatewayIpVersion is IPV4, meaning the gateway only supports IPv4 traffic.Labels & Metadata
labels field is non-authoritative and only manages labels defined in your Pulumi configuration. To see all labels on the resource (including those added by other clients or services), use the effectiveLabels output property.Advanced Use Cases
vpnInterfaces with interconnectAttachment references pointing to your IPSEC-encrypted interconnect attachments. Each interface needs an id and the interconnectAttachment self-link.Using a different cloud?
Explore networking guides for other cloud providers: