A
Arun's Blog
All Posts

AWS Site-to-Site VPN via Terraform

|4 min read|
VPNTerraformNetworking
TL;DR

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:

  1. An Understanding of Terraform
  2. 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.

Important Security Note

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_gateway pointing at the on-prem router public IP (BGP ASN 65000, type ipsec.1)
  • aws_vpn_gateway attached to the default VPC, with route propagation enabled on the main route table
  • aws_vpn_connection joining the two, with static_routes_only = true and a pre-shared key on tunnel 1
  • aws_vpn_connection_route pointing 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.

Pro Tip

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.

Related Articles