
Deploy an AWS Site-to-Site VPN connection using Terraform in minutes. This guide provides complete IaC code to create a Customer Gateway, Virtual Private Gateway, VPN connection with static routing, and a test EC2 instance - all automated for quick hybrid connectivity.
Introduction
Connecting your AWS environment can be accomplished in multiple ways. One can use Direct Connect, which can be expensive and have some lead times associated with it. Others may choose to use the Marketplace and deploy a vendor-specific solution to connect to the same vendor objects at the data center. To spin up a quick and easy connection, nothing beats the Site-to-Site VPN service AWS offers. With that, I've attempted to demonstrate how easily we can set up a Site-to-Site VPN connection in AWS with Terraform.
Prerequisites
As this will be a Infrastructure as Code demonstration, you should have:
- An Understanding of Terraform
- An AWS Account with the correct privileges to administer a VPC, EC2, and Site to Site VPN Connections and related objects.
Logical Diagram of Final Output

Terraform
The below code will use the default VPC with a pre-determined access key for the IPSec tunnels. We will assume the private on-premise subnet will have a CIDR of 192.168.0.0/16 and the public IP address of the router/firewall on-premise will be 20.163.133.4. We will also create a test EC2 instance in the private subnet we can use to test connectivity from on-premise to AWS.
Never hardcode pre-shared keys in production Terraform code. Use AWS Secrets Manager or Terraform variables with sensitive flag to manage credentials securely. The example below uses a hardcoded key for demonstration purposes only.
AWS
The Terraform module I built wires up these resources, in this order:
aws_customer_gatewaypointing at the on-prem router public IP (BGP ASN 65000, typeipsec.1)aws_vpn_gatewayattached to the default VPC, with route propagation enabled on the main route tableaws_vpn_connectionjoining the two, withstatic_routes_only = trueand a pre-shared key on tunnel 1aws_vpn_connection_routepointing at the on-prem CIDR (192.168.0.0/16)- A test EC2 in the default VPC with a security group allowing the on-prem CIDR and outputs for tunnel IPs plus the EC2 private IP
The shape of the VPN connection resource is what matters:
resource "aws_vpn_connection" "vpn" {
vpn_gateway_id = aws_vpn_gateway.vpngw.id
customer_gateway_id = aws_customer_gateway.cgw.id
type = "ipsec.1"
static_routes_only = true
tunnel1_preshared_key = var.psk # pull from Secrets Manager, do not inline
}
Gotcha worth highlighting: the aws_vpn_gateway_route_propagation resource is what gets the learned routes into your VPC route table. Skip it and the tunnel comes up but nothing routes. Also, don't hardcode the PSK like the snippet above implies. Pull it from Secrets Manager or a sensitive variable.
Use route propagation (as shown in the code) to automatically update route tables when VPN routes change. This eliminates manual route management and reduces configuration errors.
Cleaning Up
Once you are done with the above and/or no longer need the resources created, you should either manually delete all resources or via Terraform run terraform destroy (optionally with the -auto-approve to bypass conformational messages).
Troubleshooting
| Issue | Possible Cause | Solution |
|---|---|---|
| VPN tunnel shows "Down" status | Pre-shared key mismatch or firewall blocking IPSec | Verify the pre-shared key matches on both ends. Ensure UDP 500 and 4500 are allowed, and protocol ESP (50) is permitted. |
| Cannot ping EC2 from on-premise | Security group or route table misconfiguration | Verify the security group allows ICMP from 192.168.0.0/16. Check that route propagation is enabled and routes are populated. |
| Terraform apply fails on VPN connection | Customer Gateway IP already in use | Each Customer Gateway public IP can only be used once per region. Delete existing CGW or use a different public IP. |
| Routes not appearing in route table | Route propagation not enabled | Ensure aws_vpn_gateway_route_propagation resource is applied and the VPN connection is in "available" state. |
| Connection timeout on specific ports | Security group too restrictive | Review security group ingress rules. Consider allowing specific ports rather than all traffic for production environments. |
Want Help With This?
If you're working on something similar and want a second set of eyes, or you'd like to talk through how this applies to your environment, reach out via the contact form. Happy to help.