Configure Azure Virtual Network Peering

The azure-native:network:VirtualNetworkPeering resource, part of the Pulumi Azure Native provider, establishes network connectivity between two Azure virtual networks, enabling resources to communicate privately without traversing the public internet. This guide focuses on three capabilities: full VNet peering, subnet-level peering, and address space synchronization.

Peerings connect existing virtual networks and require reciprocal configuration on both sides for bidirectional communication. The examples are intentionally small. Combine them with your own VNets, subnets, and gateway infrastructure.

Connect two virtual networks for full bidirectional access

Most deployments connect two VNets to enable private communication between resources in different networks or regions.

import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";

const virtualNetworkPeering = new azure_native.network.VirtualNetworkPeering("virtualNetworkPeering", {
    allowForwardedTraffic: true,
    allowGatewayTransit: false,
    allowVirtualNetworkAccess: true,
    remoteVirtualNetwork: {
        id: "/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2",
    },
    resourceGroupName: "peerTest",
    useRemoteGateways: false,
    virtualNetworkName: "vnet1",
    virtualNetworkPeeringName: "peer",
});
import pulumi
import pulumi_azure_native as azure_native

virtual_network_peering = azure_native.network.VirtualNetworkPeering("virtualNetworkPeering",
    allow_forwarded_traffic=True,
    allow_gateway_transit=False,
    allow_virtual_network_access=True,
    remote_virtual_network={
        "id": "/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2",
    },
    resource_group_name="peerTest",
    use_remote_gateways=False,
    virtual_network_name="vnet1",
    virtual_network_peering_name="peer")
package main

import (
	network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := network.NewVirtualNetworkPeering(ctx, "virtualNetworkPeering", &network.VirtualNetworkPeeringArgs{
			AllowForwardedTraffic:     pulumi.Bool(true),
			AllowGatewayTransit:       pulumi.Bool(false),
			AllowVirtualNetworkAccess: pulumi.Bool(true),
			RemoteVirtualNetwork: &network.SubResourceArgs{
				Id: pulumi.String("/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2"),
			},
			ResourceGroupName:         pulumi.String("peerTest"),
			UseRemoteGateways:         pulumi.Bool(false),
			VirtualNetworkName:        pulumi.String("vnet1"),
			VirtualNetworkPeeringName: pulumi.String("peer"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;

return await Deployment.RunAsync(() => 
{
    var virtualNetworkPeering = new AzureNative.Network.VirtualNetworkPeering("virtualNetworkPeering", new()
    {
        AllowForwardedTraffic = true,
        AllowGatewayTransit = false,
        AllowVirtualNetworkAccess = true,
        RemoteVirtualNetwork = new AzureNative.Network.Inputs.SubResourceArgs
        {
            Id = "/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2",
        },
        ResourceGroupName = "peerTest",
        UseRemoteGateways = false,
        VirtualNetworkName = "vnet1",
        VirtualNetworkPeeringName = "peer",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.VirtualNetworkPeering;
import com.pulumi.azurenative.network.VirtualNetworkPeeringArgs;
import com.pulumi.azurenative.network.inputs.SubResourceArgs;
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 virtualNetworkPeering = new VirtualNetworkPeering("virtualNetworkPeering", VirtualNetworkPeeringArgs.builder()
            .allowForwardedTraffic(true)
            .allowGatewayTransit(false)
            .allowVirtualNetworkAccess(true)
            .remoteVirtualNetwork(SubResourceArgs.builder()
                .id("/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2")
                .build())
            .resourceGroupName("peerTest")
            .useRemoteGateways(false)
            .virtualNetworkName("vnet1")
            .virtualNetworkPeeringName("peer")
            .build());

    }
}
resources:
  virtualNetworkPeering:
    type: azure-native:network:VirtualNetworkPeering
    properties:
      allowForwardedTraffic: true
      allowGatewayTransit: false
      allowVirtualNetworkAccess: true
      remoteVirtualNetwork:
        id: /subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2
      resourceGroupName: peerTest
      useRemoteGateways: false
      virtualNetworkName: vnet1
      virtualNetworkPeeringName: peer

The remoteVirtualNetwork property references the target VNet by resource ID. The allowVirtualNetworkAccess property enables VM-to-VM communication across the peering. The allowForwardedTraffic property permits traffic from other networks to flow through this peering. Note that peerings are unidirectional; you must create a reciprocal peering from vnet2 to vnet1 for bidirectional connectivity.

Peer specific subnets instead of entire networks

When limiting connectivity scope, subnet-level peering restricts communication to specific address ranges.

import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";

const virtualNetworkPeering = new azure_native.network.VirtualNetworkPeering("virtualNetworkPeering", {
    allowForwardedTraffic: true,
    allowGatewayTransit: false,
    allowVirtualNetworkAccess: true,
    enableOnlyIPv6Peering: false,
    localSubnetNames: [
        "Subnet1",
        "Subnet4",
    ],
    peerCompleteVnets: false,
    remoteSubnetNames: ["Subnet2"],
    remoteVirtualNetwork: {
        id: "/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2",
    },
    resourceGroupName: "peerTest",
    useRemoteGateways: false,
    virtualNetworkName: "vnet1",
    virtualNetworkPeeringName: "peer",
});
import pulumi
import pulumi_azure_native as azure_native

virtual_network_peering = azure_native.network.VirtualNetworkPeering("virtualNetworkPeering",
    allow_forwarded_traffic=True,
    allow_gateway_transit=False,
    allow_virtual_network_access=True,
    enable_only_i_pv6_peering=False,
    local_subnet_names=[
        "Subnet1",
        "Subnet4",
    ],
    peer_complete_vnets=False,
    remote_subnet_names=["Subnet2"],
    remote_virtual_network={
        "id": "/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2",
    },
    resource_group_name="peerTest",
    use_remote_gateways=False,
    virtual_network_name="vnet1",
    virtual_network_peering_name="peer")
package main

import (
	network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := network.NewVirtualNetworkPeering(ctx, "virtualNetworkPeering", &network.VirtualNetworkPeeringArgs{
			AllowForwardedTraffic:     pulumi.Bool(true),
			AllowGatewayTransit:       pulumi.Bool(false),
			AllowVirtualNetworkAccess: pulumi.Bool(true),
			EnableOnlyIPv6Peering:     pulumi.Bool(false),
			LocalSubnetNames: pulumi.StringArray{
				pulumi.String("Subnet1"),
				pulumi.String("Subnet4"),
			},
			PeerCompleteVnets: pulumi.Bool(false),
			RemoteSubnetNames: pulumi.StringArray{
				pulumi.String("Subnet2"),
			},
			RemoteVirtualNetwork: &network.SubResourceArgs{
				Id: pulumi.String("/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2"),
			},
			ResourceGroupName:         pulumi.String("peerTest"),
			UseRemoteGateways:         pulumi.Bool(false),
			VirtualNetworkName:        pulumi.String("vnet1"),
			VirtualNetworkPeeringName: pulumi.String("peer"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;

return await Deployment.RunAsync(() => 
{
    var virtualNetworkPeering = new AzureNative.Network.VirtualNetworkPeering("virtualNetworkPeering", new()
    {
        AllowForwardedTraffic = true,
        AllowGatewayTransit = false,
        AllowVirtualNetworkAccess = true,
        EnableOnlyIPv6Peering = false,
        LocalSubnetNames = new[]
        {
            "Subnet1",
            "Subnet4",
        },
        PeerCompleteVnets = false,
        RemoteSubnetNames = new[]
        {
            "Subnet2",
        },
        RemoteVirtualNetwork = new AzureNative.Network.Inputs.SubResourceArgs
        {
            Id = "/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2",
        },
        ResourceGroupName = "peerTest",
        UseRemoteGateways = false,
        VirtualNetworkName = "vnet1",
        VirtualNetworkPeeringName = "peer",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.VirtualNetworkPeering;
import com.pulumi.azurenative.network.VirtualNetworkPeeringArgs;
import com.pulumi.azurenative.network.inputs.SubResourceArgs;
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 virtualNetworkPeering = new VirtualNetworkPeering("virtualNetworkPeering", VirtualNetworkPeeringArgs.builder()
            .allowForwardedTraffic(true)
            .allowGatewayTransit(false)
            .allowVirtualNetworkAccess(true)
            .enableOnlyIPv6Peering(false)
            .localSubnetNames(            
                "Subnet1",
                "Subnet4")
            .peerCompleteVnets(false)
            .remoteSubnetNames("Subnet2")
            .remoteVirtualNetwork(SubResourceArgs.builder()
                .id("/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2")
                .build())
            .resourceGroupName("peerTest")
            .useRemoteGateways(false)
            .virtualNetworkName("vnet1")
            .virtualNetworkPeeringName("peer")
            .build());

    }
}
resources:
  virtualNetworkPeering:
    type: azure-native:network:VirtualNetworkPeering
    properties:
      allowForwardedTraffic: true
      allowGatewayTransit: false
      allowVirtualNetworkAccess: true
      enableOnlyIPv6Peering: false
      localSubnetNames:
        - Subnet1
        - Subnet4
      peerCompleteVnets: false
      remoteSubnetNames:
        - Subnet2
      remoteVirtualNetwork:
        id: /subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2
      resourceGroupName: peerTest
      useRemoteGateways: false
      virtualNetworkName: vnet1
      virtualNetworkPeeringName: peer

The peerCompleteVnets property set to false activates subnet filtering. The localSubnetNames and remoteSubnetNames properties specify which subnets can communicate across the peering. The enableOnlyIPv6Peering property controls whether only IPv6 address space is peered; set to false for dual-stack or IPv4-only scenarios.

Synchronize address space changes automatically

Virtual networks evolve as teams add or modify address ranges. The sync property ensures peerings automatically reflect these changes.

import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";

const virtualNetworkPeering = new azure_native.network.VirtualNetworkPeering("virtualNetworkPeering", {
    allowForwardedTraffic: true,
    allowGatewayTransit: false,
    allowVirtualNetworkAccess: true,
    remoteVirtualNetwork: {
        id: "/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2",
    },
    resourceGroupName: "peerTest",
    syncRemoteAddressSpace: "true",
    useRemoteGateways: false,
    virtualNetworkName: "vnet1",
    virtualNetworkPeeringName: "peer",
});
import pulumi
import pulumi_azure_native as azure_native

virtual_network_peering = azure_native.network.VirtualNetworkPeering("virtualNetworkPeering",
    allow_forwarded_traffic=True,
    allow_gateway_transit=False,
    allow_virtual_network_access=True,
    remote_virtual_network={
        "id": "/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2",
    },
    resource_group_name="peerTest",
    sync_remote_address_space="true",
    use_remote_gateways=False,
    virtual_network_name="vnet1",
    virtual_network_peering_name="peer")
package main

import (
	network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := network.NewVirtualNetworkPeering(ctx, "virtualNetworkPeering", &network.VirtualNetworkPeeringArgs{
			AllowForwardedTraffic:     pulumi.Bool(true),
			AllowGatewayTransit:       pulumi.Bool(false),
			AllowVirtualNetworkAccess: pulumi.Bool(true),
			RemoteVirtualNetwork: &network.SubResourceArgs{
				Id: pulumi.String("/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2"),
			},
			ResourceGroupName:         pulumi.String("peerTest"),
			SyncRemoteAddressSpace:    pulumi.String("true"),
			UseRemoteGateways:         pulumi.Bool(false),
			VirtualNetworkName:        pulumi.String("vnet1"),
			VirtualNetworkPeeringName: pulumi.String("peer"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;

return await Deployment.RunAsync(() => 
{
    var virtualNetworkPeering = new AzureNative.Network.VirtualNetworkPeering("virtualNetworkPeering", new()
    {
        AllowForwardedTraffic = true,
        AllowGatewayTransit = false,
        AllowVirtualNetworkAccess = true,
        RemoteVirtualNetwork = new AzureNative.Network.Inputs.SubResourceArgs
        {
            Id = "/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2",
        },
        ResourceGroupName = "peerTest",
        SyncRemoteAddressSpace = "true",
        UseRemoteGateways = false,
        VirtualNetworkName = "vnet1",
        VirtualNetworkPeeringName = "peer",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.VirtualNetworkPeering;
import com.pulumi.azurenative.network.VirtualNetworkPeeringArgs;
import com.pulumi.azurenative.network.inputs.SubResourceArgs;
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 virtualNetworkPeering = new VirtualNetworkPeering("virtualNetworkPeering", VirtualNetworkPeeringArgs.builder()
            .allowForwardedTraffic(true)
            .allowGatewayTransit(false)
            .allowVirtualNetworkAccess(true)
            .remoteVirtualNetwork(SubResourceArgs.builder()
                .id("/subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2")
                .build())
            .resourceGroupName("peerTest")
            .syncRemoteAddressSpace("true")
            .useRemoteGateways(false)
            .virtualNetworkName("vnet1")
            .virtualNetworkPeeringName("peer")
            .build());

    }
}
resources:
  virtualNetworkPeering:
    type: azure-native:network:VirtualNetworkPeering
    properties:
      allowForwardedTraffic: true
      allowGatewayTransit: false
      allowVirtualNetworkAccess: true
      remoteVirtualNetwork:
        id: /subscriptions/subid/resourceGroups/peerTest/providers/Microsoft.Network/virtualNetworks/vnet2
      resourceGroupName: peerTest
      syncRemoteAddressSpace: 'true'
      useRemoteGateways: false
      virtualNetworkName: vnet1
      virtualNetworkPeeringName: peer

The syncRemoteAddressSpace property set to “true” triggers automatic synchronization when the remote VNet’s address space changes. Without this property, you must manually update the peering configuration when address ranges are added or removed.

Beyond these examples

These snippets focus on specific peering features: full VNet and subnet-level peering, gateway transit and forwarded traffic, and address space synchronization. They’re intentionally minimal rather than complete network architectures.

The examples reference pre-existing infrastructure such as both local and remote virtual networks, subnets within those VNets for subnet peering, and VPN or ExpressRoute gateways for gateway transit scenarios. They focus on configuring the peering rather than provisioning the underlying networks.

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

  • Reciprocal peering creation (peerings are unidirectional)
  • IPv6-only peering (enableOnlyIPv6Peering)
  • Gateway verification controls (doNotVerifyRemoteGateways)
  • BGP community configuration (remoteBgpCommunities)

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

Let's configure Azure Virtual Network Peering

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Gateway Configuration
Can I use remote gateways if my VNet already has a gateway?
No, you cannot set useRemoteGateways to true if your local virtual network already has a gateway configured. Remove the local gateway first or use it instead of the remote gateway.
Can multiple peerings use remote gateways simultaneously?
No, only one peering can have useRemoteGateways set to true at a time.
What's the difference between allowGatewayTransit and useRemoteGateways?
allowGatewayTransit allows the remote VNet to use your gateway for transit, while useRemoteGateways allows your VNet to use the remote VNet’s gateway. Both must be configured correctly for gateway transit to work: set allowGatewayTransit to true on the VNet with the gateway, and useRemoteGateways to true on the VNet without one.
Peering Scope & Subnets
What's the difference between peering complete VNets vs specific subnets?
Set peerCompleteVnets to true to peer entire virtual network address spaces, or false to peer only specific subnets. When false, specify which subnets to peer using localSubnetNames and remoteSubnetNames.
How do I peer specific subnets instead of entire VNets?
Set peerCompleteVnets to false and specify the subnet names in localSubnetNames and remoteSubnetNames arrays.
How do I create an IPv6-only peering between subnets?
Set enableOnlyIPv6Peering to true, peerCompleteVnets to false, and specify the subnets using localSubnetNames and remoteSubnetNames.
Traffic & Access Control
What do the traffic control flags do?

Three flags control traffic behavior:

  • allowVirtualNetworkAccess enables VMs in the local VNet to access VMs in the remote VNet
  • allowForwardedTraffic allows forwarded traffic from local VMs to pass to the remote VNet
  • allowGatewayTransit allows the remote VNet to use your VNet’s gateway for transit
Address Space Management
How do I sync my peering when the remote VNet's address space changes?
Set syncRemoteAddressSpace to “true” to sync the peering with the current address space on the remote VNet after it’s updated.
What properties can't I change after creating a peering?
The resourceGroupName, virtualNetworkName, and virtualNetworkPeeringName properties are immutable after creation. To change these, you must delete and recreate the peering.

Using a different cloud?

Explore networking guides for other cloud providers: