Lab: Your First Working Site-to-Site VPN
Not a read-along. By the end you'll have a real IPsec tunnel UP between two networks you built, and you'll prove it with a ping that crosses it.
From your mentor
You can't borrow a corporate data centre, so we fake one: a second VPC running strongSwan becomes your 'on-prem' router. Everything else is the real AWS VPN you'd build at work. Follow each step, check the checkpoint, and you'll get there.
What you’ll build
Time
60–90 minutes
Cost
~$0.10 if done in one sitting (2× t3.micro + a VPN connection at ~$0.05/hr)
Difficulty
Intermediate, you’ve done the Networking Foundations + Site-to-Site VPN lessons
What you’ll build
A 'cloud' VPC (10.0.0.0/16) and a separate 'fake on-prem' VPC (172.16.0.0/16) whose public EC2 runs strongSwan as your Customer Gateway. You then build the AWS VPN side by hand and bring a tunnel UP.
Download the CloudFormation scaffold (.yaml)Before you start
You need: an AWS account (your IAM admin user), an existing EC2 key pair in the region you’ll use, and the AWS CLI configured (or you can do every step in the console). We’ll use static routing, the simplest path to a tunnel that actually comes UP.In plain English
Deploy the scaffold
Download the template above, then create the stack (give it your key pair name). This builds both VPCs, the strongSwan host with an Elastic IP, and a cloud instance to ping.
AWS docs: cloudformation create-stack# or upload the .yaml in the CloudFormation console$ aws cloudformation create-stack --stack-name s2s-vpn-lab --template-body file://s2s-vpn-lab-scaffold.yaml --parameters ParameterKey=KeyName,ParameterValue=YOUR_KEY_PAIR --capabilities CAPABILITY_IAM
Checkpoint, what you should see
The stack reaches CREATE_COMPLETE. On the stack’s Outputs tab, note four values you’ll use below:StrongSwanPublicIp, CloudInstancePrivateIp,CloudRouteTableId, and the two CIDRs.Create the Customer Gateway
The Customer Gateway tells AWS where your “on-prem” device is, that’s the strongSwan host’s public IP. Use a private ASN like 65000 (it’s only used by BGP, but the field is required).
$ aws ec2 create-customer-gateway --type ipsec.1 --public-ip STRONGSWAN_PUBLIC_IP --bgp-asn 65000AWS docs: create-customer-gatewayCheckpoint, what you should see
You get aCustomerGatewayId (cgw-…). It shows available in the VPC console under Customer Gateways.Create & attach the Virtual Private Gateway
The VGW is the AWS end of the tunnel. Create it, then attach it to the cloud VPC.
$ aws ec2 attach-vpn-gateway --vpn-gateway-id vgw-XXXX --vpc-id CLOUD_VPC_IDAWS docs: attach-vpn-gatewayCheckpoint, what you should see
The VGW state becomes attached to the cloud VPC.Enable route propagation
This is the step beginners forget, without it the tunnel can be UP and traffic still won’t flow. Turn on route propagation so the VPN’s routes land in the cloud subnet’s route table automatically.
$ aws ec2 enable-vgw-route-propagation --route-table-id CLOUD_ROUTE_TABLE_ID --gateway-id vgw-XXXXAWS docs: enable-vgw-route-propagationCheckpoint, what you should see
On the cloud route table’s Route propagation tab, the VGW shows Yes. (Routes appear once the tunnel is UP.)Create the VPN connection (static)
Now the connection itself, static routing, with the on-prem network (172.16.0.0/16) as the static route. This provisions the two tunnels.
$ aws ec2 create-vpn-connection --type ipsec.1 --customer-gateway-id cgw-XXXX --vpn-gateway-id vgw-XXXX --options StaticRoutesOnly=trueAWS docs: create-vpn-connectionAWS docs: create-vpn-connection-route# tell AWS the on-prem network reachable over this VPN$ aws ec2 create-vpn-connection-route --vpn-connection-id vpn-XXXX --destination-cidr-block 172.16.0.0/16
Checkpoint, what you should see
The VPN connection becomes available. Both tunnels show DOWN for now, expected, because the strongSwan side isn’t configured yet.Configure strongSwan (the customer side)
Pull the two tunnel outside IPs and pre-shared keys AWS generated, then feed them to the helper script the scaffold placed on the strongSwan host. First, grab the values:
AWS docs: describe-vpn-connections# read the XML: two <tunnel_outside_address><ip_address> and two <pre_shared_key>$ aws ec2 describe-vpn-connections --vpn-connection-id vpn-XXXX --query 'VpnConnections[0].CustomerGatewayConfiguration' --output text
Then SSH to the strongSwan host and run the helper with those four values:
$ ssh ubuntu@STRONGSWAN_PUBLIC_IP -i your-key.pem# writes /etc/ipsec.conf + secrets and restarts strongSwan$ sudo /opt/configure-vpn.sh TUNNEL1_AWS_IP TUNNEL1_PSK TUNNEL2_AWS_IP TUNNEL2_PSK
Checkpoint, what you should see
Runsudo ipsec statusall, at least one connection shows ESTABLISHED. Within a minute, the AWS console shows Tunnel 1 UP(and the CloudWatch TunnelState metric flips to 1).If the install/config needs a nudge
This customer-side step is the one most likely to vary by environment. Ifipsec isn’t found, run sudo apt-get install -y strongswan-starter. If a tunnel won’t establish, jump to Troubleshooting below, it’s almost always a PSK/IP mismatch or a blocked UDP port.Prove it, ping across the tunnel
From the strongSwan host (it’s on the 172.16 network), ping the cloud instance’s private IP:
# 10.0.x.x, a private address in the OTHER VPC$ ping -c 4 CLOUD_INSTANCE_PRIVATE_IP
🎉 You did it
172.16.0.0/16, got encrypted, crossed the public internet inside an IPsec tunnel you built, was decrypted by AWS, and reached a private instance on 10.0.0.0/16, which replied the whole way back. That’s a real Site-to-Site VPN. You built it.When it doesn’t work
When it doesn’t work (it happens, work down this list)
Both tunnels stay DOWN
PSKs/IPs must match the describe-vpn-connections output exactly. Confirm the strongSwan SG allows inbound UDP 500 + 4500. Re-run the configure script.
ipsec: command not found
sudo apt-get install -y strongswan strongswan-starter, then re-run the helper script.
Tunnel UP but ping fails
Is route propagation enabled on the cloud route table? Does the cloud SG allow ICMP from 172.16.0.0/16? Is the strongSwan instance’s Source/Dest check OFF (the scaffold sets this)?
ping works one direction only
Confirm the VPN connection’s static route 172.16.0.0/16 exists, and the on-prem route table sends 10.0.0.0/16 to the strongSwan instance (the scaffold adds this).
No replies at all, tunnels look fine
Generate traffic to bring the tunnel fully up (the ping itself does this), and give it ~60 seconds after ESTABLISHED.
Tear it down (so it stops billing)
Do this when you’re done
The VPN connection bills ~$0.05/hour while it exists. Delete the VPN pieces first, then the stack.$ aws ec2 detach-vpn-gateway --vpn-gateway-id vgw-XXXX --vpc-id CLOUD_VPC_ID && aws ec2 delete-vpn-gateway --vpn-gateway-id vgw-XXXX$ aws ec2 delete-customer-gateway --customer-gateway-id cgw-XXXX# removes both VPCs, instances, and the Elastic IP$ aws cloudformation delete-stack --stack-name s2s-vpn-lab
Checkpoint, what you should see
The stack reaches DELETE_COMPLETE and the VPN connection/gateways are gone. No more hourly charges.Next up
Next lab, Make it dynamic (BGP)
Swap static routing for BGP on strongSwan: watch routes auto-propagate and failover happen without you touching a route table.