Site to Site AWS VPN - Using Only Windows Servers

Establish a Site-to-Site VPN between AWS and your on-premise network using a Windows Server as your router - no third-party networking equipment required. Use Terraform to create AWS VPN resources, then configure Windows Server with Routing and Remote Access (RRAS) for IKEv2 VPN connectivity.
Introduction
Do you rely solely on Windows servers in your environment and lack access to third-party networking equipment? Are you looking to establish a site-to-site VPN connection to your AWS environment but unsure how to do so with your existing setup? If so, fear not, because it's possible to achieve this with just a Windows Server acting as a router. In this blog post, we'll guide you through the steps needed to establish a secure connection between your LAN and AWS, enabling bidirectional communication between your servers and the cloud platform.
Prerequisites
- Windows Server to be used as a router
- I am using Server Operating System 2019
- An AWS account
- An AWS IAM object with adequate rights
- An AWS VPC with an EC2 instance for testing connectivity
- I am using Terraform to create all AWS objects
The Windows Server must have a public IP address accessible from the internet. Ensure your firewall allows UDP ports 500 and 4500 for IKE negotiation, as well as ESP protocol (IP protocol 50) for encrypted traffic.
AWS Tasks
Create VPC
resource "aws_vpc" "vpc" {
cidr_block = "192.168.0.0/16"
}
Create Subnets
#Private Subnet in AZ-A
resource "aws_subnet" "prisub1" {
cidr_block = 192.168.0.0/24
vpc_id = aws_vpc.vpc.id
availability_zone = data.aws_availability_zones.available.names[0]
}
#Private Subnet in AZ-B
resource "aws_subnet" "prisub2" {
cidr_block = 192.168.1.0/24
vpc_id = aws_vpc.vpc.id
availability_zone = data.aws_availability_zones.available.names[1]
}
Create and Attach Route Table
resource "aws_route_table" "routetableprivate" {
vpc_id = aws_vpc.vpc.id
}
resource "aws_route_table_association" "prisub1" {
subnet_id = aws_subnet.prisub1.id
route_table_id = aws_route_table.routetableprivate.id
}
resource "aws_route_table_association" "prisub2" {
subnet_id = aws_subnet.prisub2.id
route_table_id = aws_route_table.routetableprivate.id
}
resource "aws_vpn_gateway_route_propagation" "routepropagation" {
vpn_gateway_id = aws_vpn_gateway.vgw.id
route_table_id = aws_route_table.routetableprivate.id
}
# this will enable routing to on-premise CIDR via propagation upon successful connection to the VPN
Create and Attach Virtual Private Gateway
resource "aws_vpn_gateway" "vgw" {
vpc_id = aws_vpc.vpc.id
}
resource "aws_vpn_gateway_attachment" "vgw_attachment" {
vpc_id = aws_vpc.vpc.id
vpn_gateway_id = aws_vpn_gateway.vgw.id
}
Create Customer Gateway
resource "aws_customer_gateway" "cgw" {
bgp_asn = 65000
ip_address = "20.85.247.126" #this is your public IP address you will obtain from your Windows Server
type = "ipsec.1"
}
Create Site to Site VPN
resource "aws_vpn_connection" "vpn" {
vpn_gateway_id = aws_vpn_gateway.vgw.id
customer_gateway_id = aws_customer_gateway.cgw.id
type = "ipsec.1"
static_routes_only = true
tunnel1_preshared_key = "abc123xyz987"
#this can be anything you want and will be used when configuring the VPN on the Windows Server
}
resource "aws_vpn_connection_route" "remote" {
destination_cidr_block = "10.0.0.0/24" #this is your LAN CIDR where your Windows Server sits
vpn_connection_id = aws_vpn_connection.vpn.id
}
output "tunnel1IP" {
value = aws_vpn_connection.vpn.tunnel1_address
#this will be the AWS gateway which will be used when configuring the VPN on the Windows Server
}
Use static routes for Windows Server RRAS since BGP configuration on Windows is more complex. The static_routes_only = true setting simplifies the setup significantly.
Create Test EC2
data "aws_ssm_parameter" "linux" {
name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}
resource "aws_security_group" "sg-private" {
name = "allow_onprem"
vpc_id = aws_vpc.vpc.id
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["10.0.0.0/24"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "privateLinux" {
ami = "${data.aws_ssm_parameter.linux.value}"
instance_type = "t3.medium"
subnet_id = aws_subnet.prisub1.id
vpc_security_group_ids = [aws_security_group.sg-private.id]
}
output "privateLinuxIP" {
value = aws_instance.privateLinux.private_ip
}
Windows Server Tasks
These tasks can be done via PowerShell.
Install Routing Services
Install-WindowsFeature -Name Routing -IncludeManagementTools
Install-RemoteAccess -VpnType VpnS2S
$tunnel1IP = <input the tunnel IP address terraform outputs>
$psk = 'abc123xyz987'
Add-VpnS2SInterface -Name AWS1 -Destination $tunnel1IP -Protocol IKEv2 -AuthenticationMethod PSKOnly -SharedSecret $psk -IPv4Subnet 192.168.0.0/16:100
restart-service RemoteAccess; start-sleep 15; Connect-VpnS2SInterface -Name AWS1 -PassThru
The IPv4Subnet parameter (192.168.0.0/16:100) specifies the AWS VPC CIDR with a metric of 100. This creates the necessary route for traffic destined to AWS to use the VPN tunnel.
Install and Configure Remote Access with IPSec
Install-RemoteAccess -VpnType VpnS2S
$tunnel1IP = <input the tunnel IP address terraform outputs>
$psk = 'abc123xyz987'
Add-VpnS2SInterface -Name AWS1 -Destination $tunnel1IP -Protocol IKEv2 -AuthenticationMethod PSKOnly -SharedSecret $psk -IPv4Subnet 192.168.0.0/16:100
restart-service RemoteAccess; start-sleep 15; Connect-VpnS2SInterface -Name AWS1 -PassThru
Verification
As AWS never initiates a connection, you will have to initiate the connection over the Site to Site VPN. An easy way to achieve this is by pinging the test AWS instance we created via Terraform. The private IP address for the EC2 instance has been outputted when running Terraform.
ping a.b.c.d -t #continuous ping; replace a.b.c.d with the private IP address of the test EC2 instance
AWS VPN tunnels go DOWN after 10 seconds of inactivity. Configure a persistent ping or monitoring tool to keep the tunnel alive, or implement Dead Peer Detection (DPD) on your Windows Server.
Confirm Inbound and Outbound connection
Get-NetIPsecQuickModeSA
You should see both, Inbound and Outbound connections from your server to AWS.
To take this one step further, you can set the default gateway for other machines in your LAN to the IP Address of your Windows Server acting as a router to AWS so that you can communicate from our LAN servers to AWS, and vice versa.
Conclusion
With Windows Server Operating Systems, establishing a Site to Site IPSec VPN tunnel to AWS is easy and allows you to efficiently direct traffic through a Windows Server functioning as a router. This solution is ideal for creating a fast and straightforward lab environment that allows you to test Site to Site VPN connectivity and dependencies that rely on a Site to Site VPN connection.
Troubleshooting
| Issue | Possible Cause | Solution |
|---|---|---|
| VPN connection fails to establish | Firewall blocking IPSec ports | Ensure UDP 500 (IKE), UDP 4500 (NAT-T), and IP protocol 50 (ESP) are allowed through your firewall. Check Windows Firewall rules. |
| Tunnel shows connected but no traffic passes | Route not added or incorrect subnet | Verify the IPv4Subnet in Add-VpnS2SInterface matches the AWS VPC CIDR. Check route table with Get-NetRoute. |
| "The remote server did not respond" error | AWS tunnel IP incorrect or not reachable | Verify the tunnel1_address output from Terraform. Test connectivity with Test-NetConnection $tunnel1IP -Port 500. |
| Connection drops after short period | AWS DPD timeout - no traffic keeping tunnel alive | Configure a persistent ping script to the AWS EC2 or use network monitoring. AWS tunnels timeout after 10 seconds of inactivity. |
| Pre-shared key mismatch error | PSK in Windows doesn't match AWS VPN | Verify tunnel1_preshared_key in Terraform matches the -SharedSecret parameter in PowerShell. Keys are case-sensitive. |