Configure AWS Site-to-Site VPN Connections

The aws:ec2/vpnConnection:VpnConnection resource, part of the Pulumi AWS provider, defines a Site-to-Site VPN connection that establishes IPsec tunnels between AWS and on-premises networks. This guide focuses on three capabilities: 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 and routing configuration.

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 establishes two IPsec tunnels between your Customer Gateway and the Transit Gateway. The customerGatewayId identifies your on-premises endpoint, while transitGatewayId specifies the AWS hub. The type property must be “ipsec.1”, the only VPN type AWS currently supports.

Connect a single VPC with static routing

Teams connecting a single VPC to on-premises networks often use Virtual Private Gateway with static routes when their customer gateway device doesn’t support BGP.

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 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.

Route VPN traffic over Direct Connect

Enterprises with Direct Connect can route VPN traffic over private connections instead of the public internet, improving security and reducing latency.

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 after associating your Direct Connect Gateway with the 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 full 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 scenarios. 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 VPN connection type is wired, not provide drop-in 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 & Architecture
What's the difference between using a Transit Gateway and a Virtual Private Gateway?
Transit Gateway supports advanced features like IPv6 tunnels, large bandwidth (up to 5 Gbps), and acceleration. Virtual Private Gateway is the traditional option for connecting VPCs to on-premises networks but has more limitations.
How do I create a private VPN over AWS Direct Connect?
Set outsideIpAddressType to PrivateIpv4 and provide transportTransitGatewayAttachmentId pointing to your Direct Connect Gateway attachment. This creates a private S2S VPN instead of a public one.
When should I use static routes versus BGP?
Use staticRoutesOnly: true for devices that don’t support BGP. Otherwise, BGP is recommended for dynamic routing. Note that this setting is immutable after creation.
Why does a VPN connection have two tunnels?
AWS VPN connections include two tunnels for redundancy and high availability. You configure both tunnel1 and tunnel2 properties separately, each with its own IP addresses and settings.
Tunnel Configuration & Requirements
What are the CIDR requirements for VPN 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 and can I change them after creation?
You can choose standard (up to 1.25 Gbps per tunnel) or large (up to 5 Gbps per tunnel). The tunnelBandwidth property is immutable and cannot be changed after creation.
Can I use large bandwidth with a Virtual Private Gateway?
No, large bandwidth is not supported when vpnGatewayId is specified or when enableAcceleration is true. Use a Transit Gateway without acceleration to enable large bandwidth.
What properties can't be changed after creating a VPN connection?
The following properties are immutable: enableAcceleration, staticRoutesOnly, tunnel1InsideCidr, tunnel1InsideIpv6Cidr, tunnel2InsideCidr, tunnel2InsideIpv6Cidr, tunnelBandwidth, tunnelInsideIpVersion, type, and vpnConcentratorId.
Security & Authentication
What are the requirements for VPN tunnel preshared keys?
Preshared keys must be 8-64 characters long, cannot start with zero (0), and can only contain alphanumeric characters, periods (.), and underscores (_).
Can I store preshared keys in AWS Secrets Manager?
Yes, set presharedKeyStorage to SecretsManager to store keys in AWS Secrets Manager instead of the default Standard storage in the Site-to-Site VPN service. The presharedKeyArn output will only contain a valid ARN when using Secrets Manager storage.
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.
Routing & Advanced Settings
Who initiates the VPN tunnel connection by default?
By default, your customer gateway device must initiate the IKE negotiation (equivalent to tunnelStartupAction: "add"). Set tunnelStartupAction to start to have AWS initiate the IKE negotiation instead.
What's the minimum DPD timeout I can configure?
The Dead Peer Detection (DPD) timeout must be at least 30 seconds. Configure this using tunnel1DpdTimeoutSeconds or tunnel2DpdTimeoutSeconds.

Using a different cloud?

Explore networking guides for other cloud providers: