Alright folks, gather around. Today I’m gonna share my adventure setting up a Site-to-Site VPN between AWS and a client’s data center. If you’ve ever been asked to connect your AWS infrastructure to some corporate network with strict security requirements, this one’s for you.
The Background Story
So there I was, Friday afternoon (why is it always Friday?), when my manager drops this bomb: “Hey, we need to connect our AWS VPC to the client’s data center. They’re using a Palo Alto firewall and they want IPSec with specific parameters. Can you handle it?”
Sure, I said. How hard could it be? (Spoiler: It wasn’t that bad, but there were some gotchas)
The client had sent over this formal document with all these requirements – IKEv2, AES-256, SHA-256, DH Group 14, the works. They were basically saying “this is how we roll, take it or leave it.” Fair enough.
First Things First – What Are We Even Building?
Before diving into commands, let me break down what we’re actually doing here.
Imagine you have two houses on different streets. One house (AWS VPC) has all your apps and databases. The other house (client’s data center) has their systems. Now, you want people to walk between these houses securely without going through the public street. That’s basically what a Site-to-Site VPN does – it creates a secure tunnel through the internet.
Here’s a simple diagram of what we’re building:
Client Data Center Your AWS VPC
┌─────────────────┐ ┌──────────────────┐
│ Palo Alto FW │ │ Virtual Private │
│ │ │ Gateway │
│ 203.0.113.12 │◄──── INTERNET ──────►│ │
│ │ │ Tunnel IPs: │
│ │ Secure IPSec │ 52.10.10.1 │
│ │◄─────────────────────►│ 52.10.10.2 │
└─────────────────┘ Tunnels └──────────────────┘
Getting Started – The AWS CLI Setup
First thing’s first – you need AWS CLI on your machine. If you’re on Windows like me (don’t judge), here’s the quickest way:
- Download the MSI installer from AWS website
- Install it (next, next, next, you know the drill)
- Open a fresh CMD window (important – gotta refresh that PATH)
Now comes the fun part – connecting AWS CLI to your account:
aws configure
It’ll ask you for:
- Access Key ID (get this from IAM console)
- Secret Access Key (copy-paste carefully, you only see it once!)
- Default region (I used ap-southeast-1 because, you know, Southeast Asia)
- Output format (just hit enter for default)
Pro tip: If you’re managing multiple AWS accounts, use profiles. Trust me, you don’t wanna accidentally create resources in the wrong account. Been there, done that, got the awkward email from finance.
Step 1: Checking What We Have
Before creating anything, let’s see what we’re working with:
aws ec2 describe-vpcs --query "Vpcs[*].[VpcId,CidrBlock,Tags[?Key=='Name']|[0].Value]" --output table
In my case, I just had the default VPC. Nothing fancy. Your mileage may vary.
aws ec2 describe-route-tables --filters "Name=vpc-id,Values=vpc-0a1b2c3d4e5f6g7h8" --query "RouteTables[*].[RouteTableId,Main]" --output table
Note down these IDs. You’ll need them. I learned this the hard way after scrolling through terminal history for 10 minutes looking for that VPC ID I “just saw somewhere.”
Step 2: Creating the Customer Gateway
This is basically telling AWS “hey, the client has a firewall at this IP address.”
aws ec2 create-customer-gateway --bgp-asn 65000 --public-ip 203.0.113.12 --type ipsec.1 --tag-specifications "ResourceType=customer-gateway,Tags=[{Key=Name,Value=Client-Palo-Alto}]"
AWS will spit out a JSON response with a CustomerGatewayId. Copy that bad boy. You’ll need it.
Step 3: Creating the Virtual Private Gateway
This is AWS’s side of the connection:
aws ec2 create-vpn-gateway --type ipsec.1 --amazon-side-asn 64512 --tag-specifications "ResourceType=vpn-gateway,Tags=[{Key=Name,Value=Client-VGW}]"
Again, grab that VpnGatewayId from the output.
Step 4: Attaching the VGW to Your VPC
Now we connect the VGW to our VPC:
aws ec2 attach-vpn-gateway --vpn-gateway-id vgw-xxxxxxxxx --vpc-id vpc-xxxxxxxxx
Here’s where I hit my first snag. After running this, I immediately tried to enable route propagation and got an error. Turns out, the attachment takes a minute or two. So grab a coffee, check Slack, whatever. Then verify it’s attached:
aws ec2 describe-vpn-gateways --vpn-gateway-ids vgw-xxxxxxxxx --query "VpnGateways[0].State"
Wait for it to say “attached” before moving on. Patience, young padawan.
Step 5: The Route Propagation Dance
Once attached, enable route propagation so routes from the VPN automatically show up in your route table:
aws ec2 enable-vgw-route-propagation --gateway-id vgw-xxxxxxxxx --route-table-id rtb-xxxxxxxxx
No output? That’s good! AWS is weird like that. No news is good news.
Step 6: Creating the Actual VPN Connection (The Tricky Part)
Okay, this is where things got interesting. The client had specific IPSec parameters they wanted. AWS is picky about these. Really picky.
First attempt – I tried to specify everything including integrity algorithms:
{
"StaticRoutesOnly": true,
"TunnelOptions": [
{
"PreSharedKey": "MySuperSecretKey123!",
"Phase1IntegrityAlgorithms": [{"Value": "SHA256"}],
// ... more stuff
}
]
}
Error. AWS didn’t like “SHA256”.
Second attempt – tried “SHA2-256”:
Error again.
Third attempt – I gave up on integrity algorithms and let AWS use defaults:
{
"StaticRoutesOnly": true,
"TunnelInsideIpVersion": "ipv4",
"TunnelOptions": [
{
"PreSharedKey": "MySuperSecretKey123!",
"Phase1EncryptionAlgorithms": [{"Value": "AES256"}],
"Phase2EncryptionAlgorithms": [{"Value": "AES256"}],
"Phase1DHGroupNumbers": [{"Value": 14}],
"Phase2DHGroupNumbers": [{"Value": 14}],
"IKEVersions": [{"Value": "ikev2"}]
},
{
"PreSharedKey": "MySuperSecretKey123!",
"Phase1EncryptionAlgorithms": [{"Value": "AES256"}],
"Phase2EncryptionAlgorithms": [{"Value": "AES256"}],
"Phase1DHGroupNumbers": [{"Value": 14}],
"Phase2DHGroupNumbers": [{"Value": 14}],
"IKEVersions": [{"Value": "ikev2"}]
}
]
}
Success!
Save this as vpn-options.json and create the connection:
aws ec2 create-vpn-connection --type ipsec.1 --customer-gateway-id cgw-xxxxxxxxx --vpn-gateway-id vgw-xxxxxxxxx --options file://vpn-options.json --tag-specifications "ResourceType=vpn-connection,Tags=[{Key=Name,Value=Client-VPN}]"
Step 7: Don’t Forget the Routes!
Static routes won’t add themselves. I learned this after wondering why traffic wasn’t flowing even though tunnels were up.
aws ec2 create-vpn-connection-route --vpn-connection-id vpn-xxxxxxxxx --destination-cidr-block 10.20.30.0/24
aws ec2 create-vpn-connection-route --vpn-connection-id vpn-xxxxxxxxx --destination-cidr-block 10.20.31.0/24
These tell AWS “hey, to reach the client’s subnets, use this VPN.”
Step 8: Security Groups (Don’t Forget These!)
Create a security group for the VPN traffic:
aws ec2 create-security-group --group-name Client-VPN-SG --description "SG for client VPN traffic" --vpc-id vpc-xxxxxxxxx
Add rules to allow traffic from the client’s subnets:
aws ec2 authorize-security-group-ingress --group-id sg-xxxxxxxxx --protocol tcp --port 443 --cidr 10.20.30.0/24
aws ec2 authorize-security-group-ingress --group-id sg-xxxxxxxxx --protocol tcp --port 443 --cidr 10.20.31.0/24
Step 9: Getting Info for the Client
The client’s network team will need info from your side. Here’s how to get it:
aws ec2 describe-vpn-connections --vpn-connection-ids vpn-xxxxxxxxx --query "VpnConnections[0].Options.TunnelOptions[*].OutsideIpAddress"
This gives you the two public IPs AWS assigned for the tunnels. The client needs these to configure their firewall.
Also, export the full config:
aws ec2 describe-vpn-connections --vpn-connection-ids vpn-xxxxxxxxx --query "VpnConnections[0].CustomerGatewayConfiguration" --output text > client-vpn-config.xml
Step 10: The Waiting Game
After sending the config to the client, it’s basically a waiting game. Check tunnel status:
aws ec2 describe-vpn-connections --vpn-connection-ids vpn-xxxxxxxxx --query "VpnConnections[0].VgwTelemetry"
You’ll see “DOWN” until they configure their end. Once they do, magic happens – status changes to “UP”!
Troubleshooting Tips (Because Nothing Works First Time)
Tunnel won’t come up?
- Check if the client whitelisted your AWS tunnel IPs
- Verify the PSK matches exactly (spaces matter!)
- Make sure IKE versions match
Traffic not flowing even though tunnel is up?
- Check your route tables
- Verify security groups
- Don’t forget NACLs if you use them
- Check if your EC2 instances have the right security group attached
Getting weird negotiation errors?
- AWS is limited in what parameters it supports
- Sometimes you gotta compromise (like letting AWS use default integrity algorithms)
- Check AWS documentation for supported combinations
Cost Considerations
Nobody told me this, but VPN connections aren’t free:
- Connection fee: ~$36/month (running 24/7)
- Data transfer: ~$0.09/GB going out
We estimated about 500GB/month of traffic, so roughly $81/month total. Not bad for enterprise-grade connectivity, but worth budgeting for.
Lessons Learned
- AWS has limitations – Not all IPSec parameters are supported. Sometimes you gotta work with what AWS gives you.
- Documentation is your friend – That config XML file you export? Send it to the client team. It has everything they need.
- Patience with propagation – AWS operations aren’t always instant. Attachment, route propagation, all take time.
- Test with less first – Start with minimal config, get it working, then add complexity.
- Keep the PSK secure – I used a dummy one here, but in production, use AWS Secrets Manager. Don’t hardcode it anywhere.
The Happy Ending
After about 2 hours of back-and-forth (mostly waiting for the client to configure their end), we got both tunnels up and running. Traffic was flowing, applications were talking, and everyone was happy.
The best part? Once it’s set up, it just works. AWS handles failover between tunnels automatically. If one tunnel goes down, traffic switches to the other. Pretty neat.
Quick Reference Commands
Here’s a cheat sheet for future you (or me):
# Check VPN status
aws ec2 describe-vpn-connections --vpn-connection-ids vpn-xxx --query "VpnConnections[0].State"
# Check tunnel status
aws ec2 describe-vpn-connections --vpn-connection-ids vpn-xxx --query "VpnConnections[0].VgwTelemetry"
# Get tunnel IPs
aws ec2 describe-vpn-connections --vpn-connection-ids vpn-xxx --query "VpnConnections[0].Options.TunnelOptions[*].OutsideIpAddress"
# Delete everything (careful!)
aws ec2 delete-vpn-connection --vpn-connection-id vpn-xxx
aws ec2 detach-vpn-gateway --vpn-gateway-id vgw-xxx --vpc-id vpc-xxx
aws ec2 delete-vpn-gateway --vpn-gateway-id vgw-xxx
aws ec2 delete-customer-gateway --customer-gateway-id cgw-xxx
Final Thoughts
Setting up Site-to-Site VPN on AWS isn’t rocket science, but it does have its quirks. The AWS CLI makes it pretty straightforward once you know the flow. The biggest challenges are usually coordination with the other side and dealing with AWS’s parameter limitations.
Would I do it again? Absolutely. It’s way easier than managing your own VPN servers (been there, done that with StrongSwan, never again for production). AWS handles the heavy lifting – redundancy, failover, monitoring. You just configure and forget.
Hope this helps someone out there avoid the same gotchas I hit. Remember – when in doubt, check the tunnel status, verify your routes, and make sure those security groups are configured right.
Happy VPN-ing, folks!
P.S. – If you’re reading this at 3 AM because your VPN is down and your manager is breathing down your neck, hang in there. Check the basics first: Is the tunnel up? Are routes correct? Is the client’s firewall blocking you? 90% of the time, it’s one of these three.