DEV Community

iapilgrim
iapilgrim

Posted on

Bridging the Cloud: A Step-by-Step Guide to Azure VNet-to-VNet VPNs

This consolidated guide is designed for a single long-form technical blog post. It follows the narrative of Planning → Building → Connecting → Verifying.


When building multi-tier applications or connecting different business units in Azure, you often need a secure, encrypted path for data. While VNet Peering is fast, VNet-to-VNet VPN connections provide an IPSec/IKE encrypted tunnel that stays entirely on the Azure backbone network.

In this guide, we will build a complete lab from scratch using PowerShell.


The Architecture

We will create two separate virtual networks in the East US 2 region. To ensure routing works, we must use non-overlapping address spaces:

  • VNet-A (Hub): 10.0.0.0/16
  • VNet-B (Branch): 10.1.0.0/16

Phase 1: The Foundation

First, we wipe any old configurations and build our networks, subnets, and test virtual machines.

# 1. Setup Variables
$rgName = "Azure-VPN-Lab-RG"
$location = "eastus2"
$adminUser = "azureuser"
$adminPass = ConvertTo-SecureString "P@ssw0rd1234!!" -AsPlainText -Force
$myCred = New-Object System.Management.Automation.PSCredential($adminUser, $adminPass)

# 2. Create Resource Group
New-AzResourceGroup -Name $rgName -Location $location

# 3. Create VNet-A (Hub) with GatewaySubnet
$subA = New-AzVirtualNetworkSubnetConfig -Name "workload-subnet" -AddressPrefix "10.0.1.0/24"
$gwSubA = New-AzVirtualNetworkSubnetConfig -Name "GatewaySubnet" -AddressPrefix "10.0.255.0/27"
$vnetA = New-AzVirtualNetwork -ResourceGroupName $rgName -Location $location -Name "VNet-A" `
    -AddressPrefix "10.0.0.0/16" -Subnet $subA, $gwSubA

# 4. Create VNet-B (Branch) with GatewaySubnet
$subB = New-AzVirtualNetworkSubnetConfig -Name "workload-subnet" -AddressPrefix "10.1.1.0/24"
$gwSubB = New-AzVirtualNetworkSubnetConfig -Name "GatewaySubnet" -AddressPrefix "10.1.255.0/27"
$vnetB = New-AzVirtualNetwork -ResourceGroupName $rgName -Location $location -Name "VNet-B" `
    -AddressPrefix "10.1.0.0/16" -Subnet $subB, $gwSubB

# 5. Deploy Test VMs
New-AzVm -ResourceGroupName $rgName -Name "VM-Alpha" -Location $location -VirtualNetworkName "VNet-A" `
    -SubnetName "workload-subnet" -Credential $myCred -Size "Standard_B2ts_v2" -OpenPorts 22 -Image "Canonical:0001-com-ubuntu-server-jammy:22_04-lts:latest"

New-AzVm -ResourceGroupName $rgName -Name "VM-Beta" -Location $location -VirtualNetworkName "VNet-B" `
    -SubnetName "workload-subnet" -Credential $myCred -Size "Standard_B2ts_v2" -OpenPorts 22 -Image "Canonical:0001-com-ubuntu-server-jammy:22_04-lts:latest"

Enter fullscreen mode Exit fullscreen mode

Phase 2: Deploying the VPN Gateways

This is the "heavy lifting" phase. We are deploying managed VPN instances into each VNet.

Warning: This step typically takes 25 to 45 minutes. We use the -AsJob parameter to run them in parallel.

# 1. Create Public IPs for Gateways
$pip1 = New-AzPublicIpAddress -Name "VNet-A-GW-IP" -ResourceGroupName $rgName -Location $location -AllocationMethod Static -Sku Standard
$pip2 = New-AzPublicIpAddress -Name "VNet-B-GW-IP" -ResourceGroupName $rgName -Location $location -AllocationMethod Static -Sku Standard

# 2. Define IP Configurations
$gwIpConfig1 = New-AzVirtualNetworkGatewayIpConfig -Name "gw1Config" -SubnetId (Get-AzVirtualNetworkSubnetConfig -Name "GatewaySubnet" -VirtualNetwork $vnetA).Id -PublicIpAddressId $pip1.Id
$gwIpConfig2 = New-AzVirtualNetworkGatewayIpConfig -Name "gw2Config" -SubnetId (Get-AzVirtualNetworkSubnetConfig -Name "GatewaySubnet" -VirtualNetwork $vnetB).Id -PublicIpAddressId $pip2.Id

# 3. Provision Gateways
New-AzVirtualNetworkGateway -Name "VNet-A-GW" -ResourceGroupName $rgName -Location $location `
    -IpConfigurations $gwIpConfig1 -GatewayType Vpn -VpnType RouteBased -GatewaySku VpnGw1 -AsJob

New-AzVirtualNetworkGateway -Name "VNet-B-GW" -ResourceGroupName $rgName -Location $location `
    -IpConfigurations $gwIpConfig2 -GatewayType Vpn -VpnType RouteBased -GatewaySku VpnGw1 -AsJob

Enter fullscreen mode Exit fullscreen mode

Phase 3: The Handshake (Connection)

Once the gateways are Succeeded, we must link them. A VPN connection requires a Shared Key (pre-shared key) that acts as the password for the tunnel.

$sharedKey = "AzureSecureKey2026!"
$gw1 = Get-AzVirtualNetworkGateway -Name "VNet-A-GW" -ResourceGroupName $rgName
$gw2 = Get-AzVirtualNetworkGateway -Name "VNet-B-GW" -ResourceGroupName $rgName

# Bidirectional Link
New-AzVirtualNetworkGatewayConnection -Name "A-to-B" -ResourceGroupName $rgName -Location $location `
    -VirtualNetworkGateway1 $gw1 -VirtualNetworkGateway2 $gw2 -ConnectionType Vnet2Vnet -SharedKey $sharedKey

New-AzVirtualNetworkGatewayConnection -Name "B-to-A" -ResourceGroupName $rgName -Location $location `
    -VirtualNetworkGateway1 $gw2 -VirtualNetworkGateway2 $gw1 -ConnectionType Vnet2Vnet -SharedKey $sharedKey

Enter fullscreen mode Exit fullscreen mode

Phase 4: Verification

How do we know it works? We ping the Private IP of VM-Beta from VM-Alpha.

$betaIp = (Get-AzNetworkInterface -ResourceId (Get-AzVM -Name "VM-Beta" -ResourceGroupName $rgName).NetworkInterfaces[0].Id).IpConfigurations[0].PrivateIpAddress

# Run the test
Invoke-AzVMRunCommand -ResourceGroupName $rgName -VMName "VM-Alpha" -CommandId "RunShellScript" -ScriptString "ping -c 4 $betaIp"

Enter fullscreen mode Exit fullscreen mode

Summary & Cleanup

You now have a secure, encrypted tunnel between two cloud environments. This architecture is the baseline for geo-redundant databases and secure multi-tier apps.

Don't forget to clean up to avoid costs:

Remove-AzResourceGroup -Name "Azure-VPN-Lab-RG" -Force

Enter fullscreen mode Exit fullscreen mode

Top comments (0)