Configure RRAS as a NAT Gateway on EC2 Windows Server 2022
Configure RRAS as a NAT Gateway on EC2 Windows Server 2022
Private-subnet EC2 instances without a public IP, where you don't want to pay for the managed AWS NAT Gateway. You can use a Windows Server 2022 instance with RRAS NAT for egress. This article records the full setup and the pitfalls.
Architecture
The NAT-GW instance has two ENIs:
- ENI1 (Public): public subnet, has an IGW route, with an EIP attached
- ENI2 (Private): private subnet, receives client egress traffic
Traffic path: Client → VPC route table → ENI2 (Private) → RRAS NAT (SNAT to EIP) → ENI1 (Public) → IGW → Internet
Prerequisites
- Both ENIs of the NAT-GW have Source/Dest Check disabled
- The private subnet route table has
0.0.0.0/0 → NAT-GW ENI2 - Security groups allow traffic from the client subnet to the NAT-GW
Steps
1. Create a dual-ENI instance
# Create instance in public subnet with auto-assigned public IP
aws ec2 run-instances --image-id ami-0668ff427a68f0066 `
--instance-type t3.medium `
--subnet-id subnet-public `
--iam-instance-profile Name=EC2SSM `
--associate-public-ip-address `
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=NAT-GW}]"
# Disable source/dest check
aws ec2 modify-instance-attribute --instance-id i-xxxxxxxxx --no-source-dest-check
# Create the second ENI (private subnet)
aws ec2 create-network-interface --subnet-id subnet-private `
--groups sg-xxxxxx --description "NAT-GW internal ENI"
# Attach to instance
aws ec2 attach-network-interface --network-interface-id eni-xxxxxxxxx `
--instance-id i-xxxxxxxxx --device-index 1
# Disable source/dest check on the second ENI too
aws ec2 modify-network-interface-attribute `
--network-interface-id eni-xxxxxxxxx --no-source-dest-check2. Configure the private subnet route table
aws ec2 create-route --route-table-id rtb-xxxxxxxxx `
--destination-cidr-block 0.0.0.0/0 `
--network-interface-id eni-xxxxxxxxx3. Install the RRAS role
Administrator PowerShell:
Install-WindowsFeature -Name Routing -IncludeManagementTools
Install-RemoteAccess -VpnType RoutingOnly
Get-Service RemoteAccess
# Status: Running4. Rename adapters and remove the internal default route
Rename-NetAdapter -Name 'Ethernet 3' -NewName 'Public'
Rename-NetAdapter -Name 'Ethernet 4' -NewName 'Private'
# Remove the default route on the Private adapter
Remove-NetRoute -InterfaceAlias 'Private' -DestinationPrefix '0.0.0.0/0' -Confirm:$false
# Prevent DHCP lease renewal from restoring the default gateway
$adapter = Get-NetAdapter -Name 'Private'
$guid = $adapter.InterfaceGuid
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\$guid"
Set-ItemProperty -Path $regPath -Name 'DhcpDefaultGateway' -Value @('') -Type MultiStringWithout this registry change, DHCP renews the lease hourly and re-adds the default route, which conflicts with the Public adapter's default route and causes intermittent egress packet loss.
5. Stop the ICS service
netsh routing ip nat install refuses to install if the SharedAccess service is running. Just stop ICS; leave the firewall on:
Stop-Service SharedAccess -Force -ErrorAction SilentlyContinue
Set-Service SharedAccess -StartupType DisabledOnce NAT is configured, having all three Windows Firewall profiles enabled does not affect NAT forwarding — RRAS NAT runs in the kernel via ipnat.sys and bypasses the WFP rule chain.
6. Enable IP forwarding
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' `
-Name IPEnableRouter -Value 1
Set-NetIPInterface -InterfaceAlias 'Public' -Forwarding Enabled
Set-NetIPInterface -InterfaceAlias 'Private' -Forwarding Enabled
Restart-Service RemoteAccess -Force
Start-Sleep -Seconds 5
ipconfig /all | Select-String 'IP Routing'
# IP Routing Enabled. . . . . . . . : Yes7. Configure RRAS NAT
netsh routing ip nat install
# Public interface as NAT egress (full = address and port translation)
netsh routing ip nat add interface name="Public" mode=full
# Private interface as internal (private = forward only)
netsh routing ip nat add interface name="Private" mode=private
netsh routing ip nat show interface8. Verify
On the client instance:
curl.exe -s http://checkip.amazonaws.com
# Egress IP should be the NAT-GW EIP
ping 1.1.1.1Pitfalls
Dual default route (most common)
The second ENI gets a default route via DHCP with the same metric (20) as the primary adapter. Windows does ECMP load balancing, so half the egress traffic goes out the Private adapter — which has no IGW route and drops the packets. Symptom: intermittent ping/curl failures, occasional TCP timeouts.
Fix: remove the Private adapter's default route + clear DhcpDefaultGateway in the registry, or set the Private adapter InterfaceMetric very high (e.g., 1000).
New-NetNat does not work on physical adapters
New-NetNat only works with Hyper-V Internal Virtual Switch. On EC2 adapters it shows Active=True but performs no SNAT on forwarded traffic.
ICS hijacks the IP address
When ICS is enabled, it automatically changes the Private interface IP to 192.168.137.1, overriding the DHCP-assigned VPC address. Packets with mismatched IPs are dropped.
RRAS does not recognize a hot-attached ENI
After attaching the second ENI, you must run Restart-Service RemoteAccess, otherwise netsh routing ip show interface will not see the new adapter.
