Configure AWS Site-to-Site VPN Connections

The aws:ec2/vpnConnection:VpnConnection resource, part of the Pulumi AWS provider, establishes IPsec VPN tunnels between AWS and on-premises networks. This guide focuses on three connection configurations: Transit Gateway attachment for hub-and-spoke topologies, Virtual Private Gateway attachment for single-VPC connectivity, and private VPN routing over Direct Connect.

VPN connections require Customer Gateways representing on-premises endpoints and either a Transit Gateway or Virtual Private Gateway on the AWS side. The examples are intentionally small. Combine them with your own gateway infrastructure, routing configuration, and tunnel parameters.

Connect on-premises networks via Transit Gateway

Organizations building hub-and-spoke network topologies use Transit Gateway to centralize VPN connections, allowing multiple VPCs to share a single VPN connection to on-premises infrastructure.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.ec2transitgateway.TransitGateway("example", {});
const exampleCustomerGateway = new aws.ec2.CustomerGateway("example", {
    bgpAsn: "65000",
    ipAddress: "172.0.0.1",
    type: "ipsec.1",
});
const exampleVpnConnection = new aws.ec2.VpnConnection("example", {
    customerGatewayId: exampleCustomerGateway.id,
    transitGatewayId: example.id,
    type: exampleCustomerGateway.type,
});
import pulumi
import pulumi_aws as aws

example = aws.ec2transitgateway.TransitGateway("example")
example_customer_gateway = aws.ec2.CustomerGateway("example",
    bgp_asn="65000",
    ip_address="172.0.0.1",
    type="ipsec.1")
example_vpn_connection = aws.ec2.VpnConnection("example",
    customer_gateway_id=example_customer_gateway.id,
    transit_gateway_id=example.id,
    type=example_customer_gateway.type)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2transitgateway"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := ec2transitgateway.NewTransitGateway(ctx, "example", nil)
		if err != nil {
			return err
		}
		exampleCustomerGateway, err := ec2.NewCustomerGateway(ctx, "example", &ec2.CustomerGatewayArgs{
			BgpAsn:    pulumi.String("65000"),
			IpAddress: pulumi.String("172.0.0.1"),
			Type:      pulumi.String("ipsec.1"),
		})
		if err != nil {
			return err
		}
		_, err = ec2.NewVpnConnection(ctx, "example", &ec2.VpnConnectionArgs{
			CustomerGatewayId: exampleCustomerGateway.ID(),
			TransitGatewayId:  example.ID(),
			Type:              exampleCustomerGateway.Type,
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.Ec2TransitGateway.TransitGateway("example");

    var exampleCustomerGateway = new Aws.Ec2.CustomerGateway("example", new()
    {
        BgpAsn = "65000",
        IpAddress = "172.0.0.1",
        Type = "ipsec.1",
    });

    var exampleVpnConnection = new Aws.Ec2.VpnConnection("example", new()
    {
        CustomerGatewayId = exampleCustomerGateway.Id,
        TransitGatewayId = example.Id,
        Type = exampleCustomerGateway.Type,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2transitgateway.TransitGateway;
import com.pulumi.aws.ec2.CustomerGateway;
import com.pulumi.aws.ec2.CustomerGatewayArgs;
import com.pulumi.aws.ec2.VpnConnection;
import com.pulumi.aws.ec2.VpnConnectionArgs;
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 example = new TransitGateway("example");

        var exampleCustomerGateway = new CustomerGateway("exampleCustomerGateway", CustomerGatewayArgs.builder()
            .bgpAsn("65000")
            .ipAddress("172.0.0.1")
            .type("ipsec.1")
            .build());

        var exampleVpnConnection = new VpnConnection("exampleVpnConnection", VpnConnectionArgs.builder()
            .customerGatewayId(exampleCustomerGateway.id())
            .transitGatewayId(example.id())
            .type(exampleCustomerGateway.type())
            .build());

    }
}
resources:
  example:
    type: aws:ec2transitgateway:TransitGateway
  exampleCustomerGateway:
    type: aws:ec2:CustomerGateway
    name: example
    properties:
      bgpAsn: 65000
      ipAddress: 172.0.0.1
      type: ipsec.1
  exampleVpnConnection:
    type: aws:ec2:VpnConnection
    name: example
    properties:
      customerGatewayId: ${exampleCustomerGateway.id}
      transitGatewayId: ${example.id}
      type: ${exampleCustomerGateway.type}

The connection links your Customer Gateway to a Transit Gateway using the customerGatewayId and transitGatewayId properties. The type property must be “ipsec.1”, the only VPN type AWS currently supports. Transit Gateway automatically propagates routes to attached VPCs, enabling centralized connectivity.

Attach VPN to a single VPC with static routes

Single-VPC deployments often attach VPN connections directly to a Virtual Private Gateway, particularly when the on-premises device doesn’t support BGP dynamic routing.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const vpc = new aws.ec2.Vpc("vpc", {cidrBlock: "10.0.0.0/16"});
const vpnGateway = new aws.ec2.VpnGateway("vpn_gateway", {vpcId: vpc.id});
const customerGateway = new aws.ec2.CustomerGateway("customer_gateway", {
    bgpAsn: "65000",
    ipAddress: "172.0.0.1",
    type: "ipsec.1",
});
const main = new aws.ec2.VpnConnection("main", {
    vpnGatewayId: vpnGateway.id,
    customerGatewayId: customerGateway.id,
    type: "ipsec.1",
    staticRoutesOnly: true,
});
import pulumi
import pulumi_aws as aws

vpc = aws.ec2.Vpc("vpc", cidr_block="10.0.0.0/16")
vpn_gateway = aws.ec2.VpnGateway("vpn_gateway", vpc_id=vpc.id)
customer_gateway = aws.ec2.CustomerGateway("customer_gateway",
    bgp_asn="65000",
    ip_address="172.0.0.1",
    type="ipsec.1")
main = aws.ec2.VpnConnection("main",
    vpn_gateway_id=vpn_gateway.id,
    customer_gateway_id=customer_gateway.id,
    type="ipsec.1",
    static_routes_only=True)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		vpc, err := ec2.NewVpc(ctx, "vpc", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.0.0.0/16"),
		})
		if err != nil {
			return err
		}
		vpnGateway, err := ec2.NewVpnGateway(ctx, "vpn_gateway", &ec2.VpnGatewayArgs{
			VpcId: vpc.ID(),
		})
		if err != nil {
			return err
		}
		customerGateway, err := ec2.NewCustomerGateway(ctx, "customer_gateway", &ec2.CustomerGatewayArgs{
			BgpAsn:    pulumi.String("65000"),
			IpAddress: pulumi.String("172.0.0.1"),
			Type:      pulumi.String("ipsec.1"),
		})
		if err != nil {
			return err
		}
		_, err = ec2.NewVpnConnection(ctx, "main", &ec2.VpnConnectionArgs{
			VpnGatewayId:      vpnGateway.ID(),
			CustomerGatewayId: customerGateway.ID(),
			Type:              pulumi.String("ipsec.1"),
			StaticRoutesOnly:  pulumi.Bool(true),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var vpc = new Aws.Ec2.Vpc("vpc", new()
    {
        CidrBlock = "10.0.0.0/16",
    });

    var vpnGateway = new Aws.Ec2.VpnGateway("vpn_gateway", new()
    {
        VpcId = vpc.Id,
    });

    var customerGateway = new Aws.Ec2.CustomerGateway("customer_gateway", new()
    {
        BgpAsn = "65000",
        IpAddress = "172.0.0.1",
        Type = "ipsec.1",
    });

    var main = new Aws.Ec2.VpnConnection("main", new()
    {
        VpnGatewayId = vpnGateway.Id,
        CustomerGatewayId = customerGateway.Id,
        Type = "ipsec.1",
        StaticRoutesOnly = true,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.VpnGateway;
import com.pulumi.aws.ec2.VpnGatewayArgs;
import com.pulumi.aws.ec2.CustomerGateway;
import com.pulumi.aws.ec2.CustomerGatewayArgs;
import com.pulumi.aws.ec2.VpnConnection;
import com.pulumi.aws.ec2.VpnConnectionArgs;
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 vpc = new Vpc("vpc", VpcArgs.builder()
            .cidrBlock("10.0.0.0/16")
            .build());

        var vpnGateway = new VpnGateway("vpnGateway", VpnGatewayArgs.builder()
            .vpcId(vpc.id())
            .build());

        var customerGateway = new CustomerGateway("customerGateway", CustomerGatewayArgs.builder()
            .bgpAsn("65000")
            .ipAddress("172.0.0.1")
            .type("ipsec.1")
            .build());

        var main = new VpnConnection("main", VpnConnectionArgs.builder()
            .vpnGatewayId(vpnGateway.id())
            .customerGatewayId(customerGateway.id())
            .type("ipsec.1")
            .staticRoutesOnly(true)
            .build());

    }
}
resources:
  vpc:
    type: aws:ec2:Vpc
    properties:
      cidrBlock: 10.0.0.0/16
  vpnGateway:
    type: aws:ec2:VpnGateway
    name: vpn_gateway
    properties:
      vpcId: ${vpc.id}
  customerGateway:
    type: aws:ec2:CustomerGateway
    name: customer_gateway
    properties:
      bgpAsn: 65000
      ipAddress: 172.0.0.1
      type: ipsec.1
  main:
    type: aws:ec2:VpnConnection
    properties:
      vpnGatewayId: ${vpnGateway.id}
      customerGatewayId: ${customerGateway.id}
      type: ipsec.1
      staticRoutesOnly: true

The vpnGatewayId property attaches the VPN connection to a specific VPC through its Virtual Private Gateway. Setting staticRoutesOnly to true disables BGP and requires you to define routes manually using companion VpnConnectionRoute resources. This mode works with devices that don’t support BGP.

Route VPN traffic over Direct Connect

Enterprises with Direct Connect can route VPN traffic over private connectivity instead of the public internet, improving security and reducing latency for hybrid cloud workloads.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const exampleGateway = new aws.directconnect.Gateway("example", {
    name: "example_ipsec_vpn_example",
    amazonSideAsn: "64512",
});
const exampleTransitGateway = new aws.ec2transitgateway.TransitGateway("example", {
    amazonSideAsn: 64513,
    description: "example_ipsec_vpn_example",
    transitGatewayCidrBlocks: ["10.0.0.0/24"],
});
const exampleCustomerGateway = new aws.ec2.CustomerGateway("example", {
    bgpAsn: "64514",
    ipAddress: "10.0.0.1",
    type: "ipsec.1",
    tags: {
        Name: "example_ipsec_vpn_example",
    },
});
const exampleGatewayAssociation = new aws.directconnect.GatewayAssociation("example", {
    dxGatewayId: exampleGateway.id,
    associatedGatewayId: exampleTransitGateway.id,
    allowedPrefixes: ["10.0.0.0/8"],
});
const example = aws.ec2transitgateway.getDirectConnectGatewayAttachmentOutput({
    transitGatewayId: exampleTransitGateway.id,
    dxGatewayId: exampleGateway.id,
});
const exampleVpnConnection = new aws.ec2.VpnConnection("example", {
    customerGatewayId: exampleCustomerGateway.id,
    outsideIpAddressType: "PrivateIpv4",
    transitGatewayId: exampleTransitGateway.id,
    transportTransitGatewayAttachmentId: example.apply(example => example.id),
    type: "ipsec.1",
    tags: {
        Name: "example_ipsec_vpn_example",
    },
});
import pulumi
import pulumi_aws as aws

example_gateway = aws.directconnect.Gateway("example",
    name="example_ipsec_vpn_example",
    amazon_side_asn="64512")
example_transit_gateway = aws.ec2transitgateway.TransitGateway("example",
    amazon_side_asn=64513,
    description="example_ipsec_vpn_example",
    transit_gateway_cidr_blocks=["10.0.0.0/24"])
example_customer_gateway = aws.ec2.CustomerGateway("example",
    bgp_asn="64514",
    ip_address="10.0.0.1",
    type="ipsec.1",
    tags={
        "Name": "example_ipsec_vpn_example",
    })
example_gateway_association = aws.directconnect.GatewayAssociation("example",
    dx_gateway_id=example_gateway.id,
    associated_gateway_id=example_transit_gateway.id,
    allowed_prefixes=["10.0.0.0/8"])
example = aws.ec2transitgateway.get_direct_connect_gateway_attachment_output(transit_gateway_id=example_transit_gateway.id,
    dx_gateway_id=example_gateway.id)
example_vpn_connection = aws.ec2.VpnConnection("example",
    customer_gateway_id=example_customer_gateway.id,
    outside_ip_address_type="PrivateIpv4",
    transit_gateway_id=example_transit_gateway.id,
    transport_transit_gateway_attachment_id=example.id,
    type="ipsec.1",
    tags={
        "Name": "example_ipsec_vpn_example",
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/directconnect"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2transitgateway"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		exampleGateway, err := directconnect.NewGateway(ctx, "example", &directconnect.GatewayArgs{
			Name:          pulumi.String("example_ipsec_vpn_example"),
			AmazonSideAsn: pulumi.String("64512"),
		})
		if err != nil {
			return err
		}
		exampleTransitGateway, err := ec2transitgateway.NewTransitGateway(ctx, "example", &ec2transitgateway.TransitGatewayArgs{
			AmazonSideAsn: pulumi.Int(64513),
			Description:   pulumi.String("example_ipsec_vpn_example"),
			TransitGatewayCidrBlocks: pulumi.StringArray{
				pulumi.String("10.0.0.0/24"),
			},
		})
		if err != nil {
			return err
		}
		exampleCustomerGateway, err := ec2.NewCustomerGateway(ctx, "example", &ec2.CustomerGatewayArgs{
			BgpAsn:    pulumi.String("64514"),
			IpAddress: pulumi.String("10.0.0.1"),
			Type:      pulumi.String("ipsec.1"),
			Tags: pulumi.StringMap{
				"Name": pulumi.String("example_ipsec_vpn_example"),
			},
		})
		if err != nil {
			return err
		}
		_, err = directconnect.NewGatewayAssociation(ctx, "example", &directconnect.GatewayAssociationArgs{
			DxGatewayId:         exampleGateway.ID(),
			AssociatedGatewayId: exampleTransitGateway.ID(),
			AllowedPrefixes: pulumi.StringArray{
				pulumi.String("10.0.0.0/8"),
			},
		})
		if err != nil {
			return err
		}
		example := ec2transitgateway.GetDirectConnectGatewayAttachmentOutput(ctx, ec2transitgateway.GetDirectConnectGatewayAttachmentOutputArgs{
			TransitGatewayId: exampleTransitGateway.ID(),
			DxGatewayId:      exampleGateway.ID(),
		}, nil)
		_, err = ec2.NewVpnConnection(ctx, "example", &ec2.VpnConnectionArgs{
			CustomerGatewayId:    exampleCustomerGateway.ID(),
			OutsideIpAddressType: pulumi.String("PrivateIpv4"),
			TransitGatewayId:     exampleTransitGateway.ID(),
			TransportTransitGatewayAttachmentId: pulumi.String(example.ApplyT(func(example ec2transitgateway.GetDirectConnectGatewayAttachmentResult) (*string, error) {
				return &example.Id, nil
			}).(pulumi.StringPtrOutput)),
			Type: pulumi.String("ipsec.1"),
			Tags: pulumi.StringMap{
				"Name": pulumi.String("example_ipsec_vpn_example"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var exampleGateway = new Aws.DirectConnect.Gateway("example", new()
    {
        Name = "example_ipsec_vpn_example",
        AmazonSideAsn = "64512",
    });

    var exampleTransitGateway = new Aws.Ec2TransitGateway.TransitGateway("example", new()
    {
        AmazonSideAsn = 64513,
        Description = "example_ipsec_vpn_example",
        TransitGatewayCidrBlocks = new[]
        {
            "10.0.0.0/24",
        },
    });

    var exampleCustomerGateway = new Aws.Ec2.CustomerGateway("example", new()
    {
        BgpAsn = "64514",
        IpAddress = "10.0.0.1",
        Type = "ipsec.1",
        Tags = 
        {
            { "Name", "example_ipsec_vpn_example" },
        },
    });

    var exampleGatewayAssociation = new Aws.DirectConnect.GatewayAssociation("example", new()
    {
        DxGatewayId = exampleGateway.Id,
        AssociatedGatewayId = exampleTransitGateway.Id,
        AllowedPrefixes = new[]
        {
            "10.0.0.0/8",
        },
    });

    var example = Aws.Ec2TransitGateway.GetDirectConnectGatewayAttachment.Invoke(new()
    {
        TransitGatewayId = exampleTransitGateway.Id,
        DxGatewayId = exampleGateway.Id,
    });

    var exampleVpnConnection = new Aws.Ec2.VpnConnection("example", new()
    {
        CustomerGatewayId = exampleCustomerGateway.Id,
        OutsideIpAddressType = "PrivateIpv4",
        TransitGatewayId = exampleTransitGateway.Id,
        TransportTransitGatewayAttachmentId = example.Apply(getDirectConnectGatewayAttachmentResult => getDirectConnectGatewayAttachmentResult.Id),
        Type = "ipsec.1",
        Tags = 
        {
            { "Name", "example_ipsec_vpn_example" },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.directconnect.Gateway;
import com.pulumi.aws.directconnect.GatewayArgs;
import com.pulumi.aws.ec2transitgateway.TransitGateway;
import com.pulumi.aws.ec2transitgateway.TransitGatewayArgs;
import com.pulumi.aws.ec2.CustomerGateway;
import com.pulumi.aws.ec2.CustomerGatewayArgs;
import com.pulumi.aws.directconnect.GatewayAssociation;
import com.pulumi.aws.directconnect.GatewayAssociationArgs;
import com.pulumi.aws.ec2transitgateway.Ec2transitgatewayFunctions;
import com.pulumi.aws.ec2transitgateway.inputs.GetDirectConnectGatewayAttachmentArgs;
import com.pulumi.aws.ec2.VpnConnection;
import com.pulumi.aws.ec2.VpnConnectionArgs;
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 exampleGateway = new Gateway("exampleGateway", GatewayArgs.builder()
            .name("example_ipsec_vpn_example")
            .amazonSideAsn("64512")
            .build());

        var exampleTransitGateway = new TransitGateway("exampleTransitGateway", TransitGatewayArgs.builder()
            .amazonSideAsn(64513)
            .description("example_ipsec_vpn_example")
            .transitGatewayCidrBlocks("10.0.0.0/24")
            .build());

        var exampleCustomerGateway = new CustomerGateway("exampleCustomerGateway", CustomerGatewayArgs.builder()
            .bgpAsn("64514")
            .ipAddress("10.0.0.1")
            .type("ipsec.1")
            .tags(Map.of("Name", "example_ipsec_vpn_example"))
            .build());

        var exampleGatewayAssociation = new GatewayAssociation("exampleGatewayAssociation", GatewayAssociationArgs.builder()
            .dxGatewayId(exampleGateway.id())
            .associatedGatewayId(exampleTransitGateway.id())
            .allowedPrefixes("10.0.0.0/8")
            .build());

        final var example = Ec2transitgatewayFunctions.getDirectConnectGatewayAttachment(GetDirectConnectGatewayAttachmentArgs.builder()
            .transitGatewayId(exampleTransitGateway.id())
            .dxGatewayId(exampleGateway.id())
            .build());

        var exampleVpnConnection = new VpnConnection("exampleVpnConnection", VpnConnectionArgs.builder()
            .customerGatewayId(exampleCustomerGateway.id())
            .outsideIpAddressType("PrivateIpv4")
            .transitGatewayId(exampleTransitGateway.id())
            .transportTransitGatewayAttachmentId(example.applyValue(_example -> _example.id()))
            .type("ipsec.1")
            .tags(Map.of("Name", "example_ipsec_vpn_example"))
            .build());

    }
}
resources:
  exampleGateway:
    type: aws:directconnect:Gateway
    name: example
    properties:
      name: example_ipsec_vpn_example
      amazonSideAsn: '64512'
  exampleTransitGateway:
    type: aws:ec2transitgateway:TransitGateway
    name: example
    properties:
      amazonSideAsn: '64513'
      description: example_ipsec_vpn_example
      transitGatewayCidrBlocks:
        - 10.0.0.0/24
  exampleCustomerGateway:
    type: aws:ec2:CustomerGateway
    name: example
    properties:
      bgpAsn: 64514
      ipAddress: 10.0.0.1
      type: ipsec.1
      tags:
        Name: example_ipsec_vpn_example
  exampleGatewayAssociation:
    type: aws:directconnect:GatewayAssociation
    name: example
    properties:
      dxGatewayId: ${exampleGateway.id}
      associatedGatewayId: ${exampleTransitGateway.id}
      allowedPrefixes:
        - 10.0.0.0/8
  exampleVpnConnection:
    type: aws:ec2:VpnConnection
    name: example
    properties:
      customerGatewayId: ${exampleCustomerGateway.id}
      outsideIpAddressType: PrivateIpv4
      transitGatewayId: ${exampleTransitGateway.id}
      transportTransitGatewayAttachmentId: ${example.id}
      type: ipsec.1
      tags:
        Name: example_ipsec_vpn_example
variables:
  example:
    fn::invoke:
      function: aws:ec2transitgateway:getDirectConnectGatewayAttachment
      arguments:
        transitGatewayId: ${exampleTransitGateway.id}
        dxGatewayId: ${exampleGateway.id}

The outsideIpAddressType property set to “PrivateIpv4” routes VPN traffic over Direct Connect rather than the public internet. The transportTransitGatewayAttachmentId references the Direct Connect Gateway attachment, obtained through a data source. This configuration requires a Direct Connect Gateway associated with your Transit Gateway.

Beyond these examples

These snippets focus on specific VPN connection features: Transit Gateway and Virtual Private Gateway attachment, static routing and BGP configuration, and private VPN over Direct Connect. They’re intentionally minimal rather than complete hybrid network architectures.

The examples reference pre-existing infrastructure such as Customer Gateways (on-premises VPN endpoints), Transit Gateways or Virtual Private Gateways, and VPCs or Direct Connect Gateways for private VPN. They focus on establishing the VPN connection rather than provisioning the surrounding network infrastructure.

To keep things focused, common VPN patterns are omitted, including:

  • Tunnel-specific configuration (inside CIDR blocks, pre-shared keys)
  • IKE and IPsec parameters (phase 1/2 encryption, DH groups, lifetimes)
  • Dead Peer Detection and tunnel lifecycle controls
  • Tunnel bandwidth selection (standard vs large)
  • CloudWatch logging configuration (tunnel1LogOptions, tunnel2LogOptions)

These omissions are intentional: the goal is to illustrate how each connection type is wired, not provide drop-in hybrid network modules. See the VPN Connection resource reference for all available configuration options.

Let's configure AWS Site-to-Site VPN Connections

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Gateway Types & Setup
What's the difference between using Transit Gateway and VPN Gateway for my VPN connection?
Transit Gateway supports IPv6 tunnels, acceleration, and bandwidth selection (tunnelBandwidth). VPN Gateway is the traditional option but doesn’t support these features. Choose Transit Gateway for modern deployments requiring higher throughput or IPv6.
How do I create a private VPN connection over Direct Connect?
Set outsideIpAddressType to PrivateIpv4 and provide transportTransitGatewayAttachmentId pointing to your Direct Connect Gateway attachment. This requires a Transit Gateway with an associated Direct Connect Gateway.
When should I use static routes instead of BGP?
Set staticRoutesOnly to true for devices that don’t support BGP. Note that this property is immutable after creation.
Tunnel Configuration & CIDR Requirements
What CIDR ranges can I use for tunnel inside addresses?
For IPv4, use a /30 CIDR block from the 169.254.0.0/16 range for tunnel1InsideCidr and tunnel2InsideCidr. For IPv6 (Transit Gateway only), use a /126 CIDR block from the fd00::/8 range.
What bandwidth options are available for VPN tunnels?
Choose standard (up to 1.25 Gbps per tunnel) or large (up to 5 Gbps per tunnel) for tunnelBandwidth. This option is only available with Transit Gateway and cannot be used with VPN Gateway or when enableAcceleration is true.
What properties can't I change after creating the VPN connection?
These properties are immutable: enableAcceleration, staticRoutesOnly, tunnelBandwidth, tunnelInsideIpVersion, type, and vpnConcentratorId. Plan these carefully during initial setup.
Security & Authentication
What are the requirements for VPN tunnel preshared keys?
Preshared keys must be 8-64 characters long, cannot start with zero, and can only contain alphanumeric characters, periods (.), and underscores (_). Configure presharedKeyStorage as SecretsManager to store them in AWS Secrets Manager instead of the default Standard storage.
Why does presharedKeyArn return a value even though my keys aren't in Secrets Manager?
The presharedKeyArn output always returns a Secrets Manager ARN, but keys are only actually stored there if you set presharedKeyStorage to SecretsManager. Without this setting, keys remain in the Site-to-Site VPN service.
Tunnel Lifecycle & Behavior
Can I have AWS initiate the VPN tunnel instead of my customer gateway?
Yes, set tunnel1StartupAction or tunnel2StartupAction to start. By default (value add), your customer gateway device must initiate the IKE negotiation.
What are the valid ranges for IKE phase lifetimes?
Phase 1 lifetime (tunnel1Phase1LifetimeSeconds) ranges from 900 to 28,800 seconds. Phase 2 lifetime (tunnel1Phase2LifetimeSeconds) ranges from 900 to 3,600 seconds. These apply to both tunnels.

Using a different cloud?

Explore networking guides for other cloud providers: