Configure AWS VPC Peering Connections

The aws:ec2/vpcPeeringConnection:VpcPeeringConnection resource, part of the Pulumi AWS provider, creates a VPC peering connection that enables private IP routing between two VPCs. This guide focuses on three capabilities: same-account peering, DNS resolution configuration, and cross-region connections.

VPC peering connections reference existing VPCs with non-overlapping CIDR blocks. After creating the peering connection, you must update route tables separately to enable traffic flow. The examples are intentionally small. Combine them with your own VPC infrastructure and routing configuration.

Connect two VPCs in the same account

Most peering connections link two VPCs within the same AWS account, allowing resources to communicate using private IP addresses.

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

const foo = new aws.ec2.VpcPeeringConnection("foo", {
    peerOwnerId: peerOwnerId,
    peerVpcId: bar.id,
    vpcId: fooAwsVpc.id,
});
import pulumi
import pulumi_aws as aws

foo = aws.ec2.VpcPeeringConnection("foo",
    peer_owner_id=peer_owner_id,
    peer_vpc_id=bar["id"],
    vpc_id=foo_aws_vpc["id"])
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 {
		_, err := ec2.NewVpcPeeringConnection(ctx, "foo", &ec2.VpcPeeringConnectionArgs{
			PeerOwnerId: pulumi.Any(peerOwnerId),
			PeerVpcId:   pulumi.Any(bar.Id),
			VpcId:       pulumi.Any(fooAwsVpc.Id),
		})
		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 foo = new Aws.Ec2.VpcPeeringConnection("foo", new()
    {
        PeerOwnerId = peerOwnerId,
        PeerVpcId = bar.Id,
        VpcId = fooAwsVpc.Id,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.VpcPeeringConnection;
import com.pulumi.aws.ec2.VpcPeeringConnectionArgs;
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 foo = new VpcPeeringConnection("foo", VpcPeeringConnectionArgs.builder()
            .peerOwnerId(peerOwnerId)
            .peerVpcId(bar.id())
            .vpcId(fooAwsVpc.id())
            .build());

    }
}
resources:
  foo:
    type: aws:ec2:VpcPeeringConnection
    properties:
      peerOwnerId: ${peerOwnerId}
      peerVpcId: ${bar.id}
      vpcId: ${fooAwsVpc.id}

The vpcId property identifies the requester VPC, while peerVpcId identifies the accepter VPC. The peerOwnerId specifies the AWS account that owns the accepter VPC. Without autoAccept, the connection enters a pending state and requires manual acceptance via the AWS console or the VpcPeeringConnectionAccepter resource.

Enable DNS resolution across peered VPCs

Applications that use DNS names to reference resources across VPCs need DNS resolution enabled on both sides.

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

const foo = new aws.ec2.VpcPeeringConnection("foo", {
    peerOwnerId: peerOwnerId,
    peerVpcId: bar.id,
    vpcId: fooAwsVpc.id,
    accepter: {
        allowRemoteVpcDnsResolution: true,
    },
    requester: {
        allowRemoteVpcDnsResolution: true,
    },
});
import pulumi
import pulumi_aws as aws

foo = aws.ec2.VpcPeeringConnection("foo",
    peer_owner_id=peer_owner_id,
    peer_vpc_id=bar["id"],
    vpc_id=foo_aws_vpc["id"],
    accepter={
        "allow_remote_vpc_dns_resolution": True,
    },
    requester={
        "allow_remote_vpc_dns_resolution": 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 {
		_, err := ec2.NewVpcPeeringConnection(ctx, "foo", &ec2.VpcPeeringConnectionArgs{
			PeerOwnerId: pulumi.Any(peerOwnerId),
			PeerVpcId:   pulumi.Any(bar.Id),
			VpcId:       pulumi.Any(fooAwsVpc.Id),
			Accepter: &ec2.VpcPeeringConnectionAccepterTypeArgs{
				AllowRemoteVpcDnsResolution: pulumi.Bool(true),
			},
			Requester: &ec2.VpcPeeringConnectionRequesterArgs{
				AllowRemoteVpcDnsResolution: 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 foo = new Aws.Ec2.VpcPeeringConnection("foo", new()
    {
        PeerOwnerId = peerOwnerId,
        PeerVpcId = bar.Id,
        VpcId = fooAwsVpc.Id,
        Accepter = new Aws.Ec2.Inputs.VpcPeeringConnectionAccepterArgs
        {
            AllowRemoteVpcDnsResolution = true,
        },
        Requester = new Aws.Ec2.Inputs.VpcPeeringConnectionRequesterArgs
        {
            AllowRemoteVpcDnsResolution = true,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.VpcPeeringConnection;
import com.pulumi.aws.ec2.VpcPeeringConnectionArgs;
import com.pulumi.aws.ec2.inputs.VpcPeeringConnectionAccepterArgs;
import com.pulumi.aws.ec2.inputs.VpcPeeringConnectionRequesterArgs;
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 foo = new VpcPeeringConnection("foo", VpcPeeringConnectionArgs.builder()
            .peerOwnerId(peerOwnerId)
            .peerVpcId(bar.id())
            .vpcId(fooAwsVpc.id())
            .accepter(VpcPeeringConnectionAccepterArgs.builder()
                .allowRemoteVpcDnsResolution(true)
                .build())
            .requester(VpcPeeringConnectionRequesterArgs.builder()
                .allowRemoteVpcDnsResolution(true)
                .build())
            .build());

    }
}
resources:
  foo:
    type: aws:ec2:VpcPeeringConnection
    properties:
      peerOwnerId: ${peerOwnerId}
      peerVpcId: ${bar.id}
      vpcId: ${fooAwsVpc.id}
      accepter:
        allowRemoteVpcDnsResolution: true
      requester:
        allowRemoteVpcDnsResolution: true

The accepter and requester blocks configure peering options for each VPC. Setting allowRemoteVpcDnsResolution to true on both sides allows DNS queries in one VPC to resolve to private IP addresses in the peered VPC. This enables hostname-based communication across the peering connection.

Auto-accept peering within the same region

When both VPCs are in the same AWS account and region, you can skip manual acceptance.

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

const fooVpc = new aws.ec2.Vpc("foo", {cidrBlock: "10.1.0.0/16"});
const bar = new aws.ec2.Vpc("bar", {cidrBlock: "10.2.0.0/16"});
const foo = new aws.ec2.VpcPeeringConnection("foo", {
    peerOwnerId: peerOwnerId,
    peerVpcId: bar.id,
    vpcId: fooVpc.id,
    autoAccept: true,
    tags: {
        Name: "VPC Peering between foo and bar",
    },
});
import pulumi
import pulumi_aws as aws

foo_vpc = aws.ec2.Vpc("foo", cidr_block="10.1.0.0/16")
bar = aws.ec2.Vpc("bar", cidr_block="10.2.0.0/16")
foo = aws.ec2.VpcPeeringConnection("foo",
    peer_owner_id=peer_owner_id,
    peer_vpc_id=bar.id,
    vpc_id=foo_vpc.id,
    auto_accept=True,
    tags={
        "Name": "VPC Peering between foo and bar",
    })
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 {
		fooVpc, err := ec2.NewVpc(ctx, "foo", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.1.0.0/16"),
		})
		if err != nil {
			return err
		}
		bar, err := ec2.NewVpc(ctx, "bar", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.2.0.0/16"),
		})
		if err != nil {
			return err
		}
		_, err = ec2.NewVpcPeeringConnection(ctx, "foo", &ec2.VpcPeeringConnectionArgs{
			PeerOwnerId: pulumi.Any(peerOwnerId),
			PeerVpcId:   bar.ID(),
			VpcId:       fooVpc.ID(),
			AutoAccept:  pulumi.Bool(true),
			Tags: pulumi.StringMap{
				"Name": pulumi.String("VPC Peering between foo and bar"),
			},
		})
		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 fooVpc = new Aws.Ec2.Vpc("foo", new()
    {
        CidrBlock = "10.1.0.0/16",
    });

    var bar = new Aws.Ec2.Vpc("bar", new()
    {
        CidrBlock = "10.2.0.0/16",
    });

    var foo = new Aws.Ec2.VpcPeeringConnection("foo", new()
    {
        PeerOwnerId = peerOwnerId,
        PeerVpcId = bar.Id,
        VpcId = fooVpc.Id,
        AutoAccept = true,
        Tags = 
        {
            { "Name", "VPC Peering between foo and bar" },
        },
    });

});
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.VpcPeeringConnection;
import com.pulumi.aws.ec2.VpcPeeringConnectionArgs;
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 fooVpc = new Vpc("fooVpc", VpcArgs.builder()
            .cidrBlock("10.1.0.0/16")
            .build());

        var bar = new Vpc("bar", VpcArgs.builder()
            .cidrBlock("10.2.0.0/16")
            .build());

        var foo = new VpcPeeringConnection("foo", VpcPeeringConnectionArgs.builder()
            .peerOwnerId(peerOwnerId)
            .peerVpcId(bar.id())
            .vpcId(fooVpc.id())
            .autoAccept(true)
            .tags(Map.of("Name", "VPC Peering between foo and bar"))
            .build());

    }
}
resources:
  foo:
    type: aws:ec2:VpcPeeringConnection
    properties:
      peerOwnerId: ${peerOwnerId}
      peerVpcId: ${bar.id}
      vpcId: ${fooVpc.id}
      autoAccept: true
      tags:
        Name: VPC Peering between foo and bar
  fooVpc:
    type: aws:ec2:Vpc
    name: foo
    properties:
      cidrBlock: 10.1.0.0/16
  bar:
    type: aws:ec2:Vpc
    properties:
      cidrBlock: 10.2.0.0/16

The autoAccept property automatically accepts the peering connection when both VPCs belong to the same account and region. This streamlines setup for same-account deployments. The tags property adds metadata for organization and cost tracking.

Peer VPCs across AWS regions

Multi-region architectures connect VPCs in different regions for disaster recovery or global deployments.

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

const fooVpc = new aws.ec2.Vpc("foo", {cidrBlock: "10.1.0.0/16"});
const bar = new aws.ec2.Vpc("bar", {cidrBlock: "10.2.0.0/16"});
const foo = new aws.ec2.VpcPeeringConnection("foo", {
    peerOwnerId: peerOwnerId,
    peerVpcId: bar.id,
    vpcId: fooVpc.id,
    peerRegion: "us-east-1",
});
import pulumi
import pulumi_aws as aws

foo_vpc = aws.ec2.Vpc("foo", cidr_block="10.1.0.0/16")
bar = aws.ec2.Vpc("bar", cidr_block="10.2.0.0/16")
foo = aws.ec2.VpcPeeringConnection("foo",
    peer_owner_id=peer_owner_id,
    peer_vpc_id=bar.id,
    vpc_id=foo_vpc.id,
    peer_region="us-east-1")
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 {
		fooVpc, err := ec2.NewVpc(ctx, "foo", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.1.0.0/16"),
		})
		if err != nil {
			return err
		}
		bar, err := ec2.NewVpc(ctx, "bar", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.2.0.0/16"),
		})
		if err != nil {
			return err
		}
		_, err = ec2.NewVpcPeeringConnection(ctx, "foo", &ec2.VpcPeeringConnectionArgs{
			PeerOwnerId: pulumi.Any(peerOwnerId),
			PeerVpcId:   bar.ID(),
			VpcId:       fooVpc.ID(),
			PeerRegion:  pulumi.String("us-east-1"),
		})
		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 fooVpc = new Aws.Ec2.Vpc("foo", new()
    {
        CidrBlock = "10.1.0.0/16",
    });

    var bar = new Aws.Ec2.Vpc("bar", new()
    {
        CidrBlock = "10.2.0.0/16",
    });

    var foo = new Aws.Ec2.VpcPeeringConnection("foo", new()
    {
        PeerOwnerId = peerOwnerId,
        PeerVpcId = bar.Id,
        VpcId = fooVpc.Id,
        PeerRegion = "us-east-1",
    });

});
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.VpcPeeringConnection;
import com.pulumi.aws.ec2.VpcPeeringConnectionArgs;
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 fooVpc = new Vpc("fooVpc", VpcArgs.builder()
            .cidrBlock("10.1.0.0/16")
            .build());

        var bar = new Vpc("bar", VpcArgs.builder()
            .cidrBlock("10.2.0.0/16")
            .build());

        var foo = new VpcPeeringConnection("foo", VpcPeeringConnectionArgs.builder()
            .peerOwnerId(peerOwnerId)
            .peerVpcId(bar.id())
            .vpcId(fooVpc.id())
            .peerRegion("us-east-1")
            .build());

    }
}
resources:
  foo:
    type: aws:ec2:VpcPeeringConnection
    properties:
      peerOwnerId: ${peerOwnerId}
      peerVpcId: ${bar.id}
      vpcId: ${fooVpc.id}
      peerRegion: us-east-1
  fooVpc:
    type: aws:ec2:Vpc
    name: foo
    properties:
      cidrBlock: 10.1.0.0/16
  bar:
    type: aws:ec2:Vpc
    properties:
      cidrBlock: 10.2.0.0/16

The peerRegion property specifies the AWS region of the accepter VPC. Cross-region peering connections cannot use autoAccept; they require manual acceptance via the VpcPeeringConnectionAccepter resource or AWS console. This two-step process ensures both sides explicitly approve the connection.

Beyond these examples

These snippets focus on specific peering connection features: same-account and cross-region peering, DNS resolution configuration, and automatic acceptance for same-region connections. They’re intentionally minimal rather than full network architectures.

The examples reference pre-existing infrastructure such as VPCs with non-overlapping CIDR blocks, and route tables that must be updated separately to route traffic through the peering connection. They focus on creating the peering connection rather than configuring the surrounding network.

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

  • Cross-account peering (requires VpcPeeringConnectionAccepter resource)
  • Route table updates to enable traffic flow
  • Security group rules for cross-VPC access
  • VPC Peering Connection Options resource (standalone configuration)

These omissions are intentional: the goal is to illustrate how each peering feature is wired, not provide drop-in network modules. See the VPC Peering Connection resource reference for all available configuration options.

Let's configure AWS VPC Peering Connections

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Cross-Account & Inter-Region Setup
How do I set up cross-account or inter-region VPC peering?
Use aws.ec2.VpcPeeringConnection on the requester side with autoAccept set to false, then use aws.ec2.VpcPeeringConnectionAccepter on the accepter side to manage acceptance and options.
When can I use autoAccept to automatically accept peering?
Only when both VPCs are in the same AWS account and region. For cross-account or inter-region peering, set autoAccept to false and use aws.ec2.VpcPeeringConnectionAccepter on the accepter side.
Connection Options & Configuration
How do I enable DNS resolution across peered VPCs?
Set allowRemoteVpcDnsResolution to true in the accepter and/or requester configuration blocks.
Can I manage peering options in both VpcPeeringConnection and VpcPeeringConnectionOptions?
No, managing options in both resources causes conflicts and overwrites. Use VpcPeeringConnectionOptions for cross-account scenarios, or use the accepter/requester attributes directly in VpcPeeringConnection.
Why can't I modify peering connection options?
Modifying accepter or requester options requires the peering connection to be active. Use autoAccept for automatic activation, or manually accept the connection first.
Common Pitfalls & Gotchas
What happens if I create duplicate peering connections with the same VPCs?
AWS returns the existing connection ID instead of creating a new one or throwing an error. This results in multiple aws.ec2.VpcPeeringConnection resources managing the same connection.
What properties can't be changed after creation?
The peerOwnerId, peerRegion, peerVpcId, and vpcId properties are immutable and require resource replacement if changed.

Using a different cloud?

Explore networking guides for other cloud providers: