Happy Dussehra! May good always win. Happy Diwali! Wishing you a bright year ahead. Merry Christmas! Warm wishes from Vizag. Happy New Year! Here’s to a great year ahead.

Archive · AWS

AWS to On-Premises Tunnel: Site-to-Site VPN with strongSwan

Connect a VPC to an on-premises network with an AWS Site-to-Site VPN, using a Ubuntu 22.04 box running strongSwan as the customer gateway, with route propagation and tests.

4 min read

  • AWS Site-to-Site VPN
  • Virtual Private Gateway
  • strongSwan 5.9
  • Ubuntu 22.04 LTS
  • IKEv2

Sooner or later a VPC has to talk to servers in an office or a data centre: a database that has not moved yet, a licence server, an internal API. An IPsec VPN is the quickest way to join the two networks while a proper Direct Connect link is still being planned. This post builds an AWS Site-to-Site VPN with a plain Ubuntu server as the on-premises device.

Versions in use on this date. Ubuntu 22.04 LTS ships strongSwan 5.9.5. AWS Site-to-Site VPN supports IKEv2 and gives you two tunnels per connection, each with its own public endpoint, so you can survive an AWS-side maintenance event.

What gets built

flowchart TB
  subgraph Onprem[On-premises 192.168.10.0/24]
    SRV[Servers] --> CGW[Customer gateway<br/>Ubuntu with strongSwan]
  end
  CGW ==>|IPsec tunnel 1| VGW[Virtual private gateway]
  CGW -.->|IPsec tunnel 2 standby| VGW
  subgraph VPC[VPC 10.20.0.0/16]
    VGW --> RT[Route table<br/>route propagation on]
    RT --> EC2[EC2 and RDS in private subnets]
  end

The customer gateway (CGW) is AWS’s name for your side of the link: a record that holds the public IP of the on-premises device. The virtual private gateway (VGW) is the AWS end, attached to the VPC. The VPN connection joins the two.

Plan the addresses first

The two networks must not overlap. Here the VPC is 10.20.0.0/16 and the office is 192.168.10.0/24. Overlapping ranges are the most common reason a tunnel comes up and traffic still does not flow, and they are painful to fix later. The on-premises device also needs a static public IP, either directly on the box or through a 1:1 NAT from the firewall (open UDP 500 and 4500 and ESP to it).

Create the AWS side

aws ec2 create-customer-gateway --type ipsec.1 --public-ip 203.0.113.10 --bgp-asn 65000
aws ec2 create-vpn-gateway --type ipsec.1
aws ec2 attach-vpn-gateway --vpn-gateway-id vgw-0abc --vpc-id vpc-0abc
aws ec2 create-vpn-connection --type ipsec.1 \
  --customer-gateway-id cgw-0abc --vpn-gateway-id vgw-0abc \
  --options '{"StaticRoutesOnly":true,"TunnelOptions":[{"IKEVersion":"ikev2"},{"IKEVersion":"ikev2"}]}'
aws ec2 create-vpn-connection-route --vpn-connection-id vpn-0abc --destination-cidr-block 192.168.10.0/24
aws ec2 enable-vgw-route-propagation --route-table-id rtb-0abc --gateway-id vgw-0abc

Static routing is the simplest start. BGP is better once you have several networks, because routes then follow tunnel health automatically. Download the configuration from the console (Generic vendor) to read the tunnel IPs and pre-shared keys. Treat those keys like passwords: store them in a secrets manager, not in a chat.

Configure strongSwan on the on-premises server

sudo apt update && sudo apt install -y strongswan strongswan-pki
echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-vpn.conf
echo 'net.ipv4.conf.all.rp_filter=2'  | sudo tee -a /etc/sysctl.d/99-vpn.conf
sudo sysctl --system

/etc/ipsec.conf (tunnel 1; repeat with a different mark for tunnel 2):

conn aws-tunnel1
    auto=start
    keyexchange=ikev2
    type=tunnel
    authby=secret
    left=%defaultroute
    leftid=203.0.113.10
    leftsubnet=0.0.0.0/0
    right=198.51.100.21
    rightsubnet=0.0.0.0/0
    ike=aes256-sha2_256-modp2048!
    esp=aes256-sha2_256-modp2048!
    ikelifetime=8h
    lifetime=1h
    dpddelay=10s
    dpdtimeout=30s
    dpdaction=restart
    mark=100
    leftupdown=/etc/strongswan.d/aws-updown.sh

/etc/ipsec.secrets: 203.0.113.10 198.51.100.21 : PSK "<pre-shared key from AWS>". With leftsubnet and rightsubnet at 0.0.0.0/0 the tunnel is route-based: strongSwan builds a VTI interface (vti1) and the kernel routing table decides what goes in. The up/down script creates it with ip tunnel add vti1 mode vti local ... remote ... key 100, sets disable_policy=1 and adds the route ip route add 10.20.0.0/16 dev vti1. AWS publishes a ready-made script for this in the generic configuration file; use that rather than writing your own.

sudo systemctl restart strongswan-starter
sudo ipsec statusall

Test it

ping -c3 10.20.1.15          # from an on-premises server (needs a route to the CGW as gateway)
ssh ec2-user@10.20.1.15

From the AWS side, the instance’s security group must allow the office range (192.168.10.0/24), and the network ACLs must not block it. In the console the tunnel status should show UP and the instances should be reachable by private IP. If a tunnel is down, check in order: the pre-shared key, the IKE and ESP proposals (they must match exactly), the firewall letting UDP 500 and 4500 through, and the clock.

Symptom Likely cause
Tunnel never comes up Wrong PSK or leftid, UDP 500/4500 blocked
Tunnel UP, no traffic Missing route or propagation, security group, overlapping CIDR
One direction only Return route missing on the office router
Drops every hour Rekey mismatch, set dpdaction=restart and matching lifetimes

Operating it

Set a CloudWatch alarm on the TunnelState metric, because AWS replaces tunnel endpoints during maintenance and a quiet standby tunnel can hide an outage. Plan for the tunnel’s throughput limit of about 1.25 Gbps and keep the CGW patched. For anything business critical, add a second device on-premises and move to BGP.