Configure site-to-site VPN with VyOS
This guide describes how to connect an external network to instances running on Exoscale, through a site-to-site IPsec VPN terminated on a VyOS gateway instance. The Exoscale side is an Exoscale VPC: the gateway and the workload instance are attached to a VPC Subnet, and a custom Subnet route steers the traffic of the remote network through the gateway.
Note
Prefer WireGuard when you start from scratch
While the procedure below is still valid, we recommend reading in detail the closely related guide “Connect a VPC to an External Network with WireGuard”, which is also built on Exoscale VPC and is simpler to operate.
How It Works
external network ──── external router ──── IPsec (public internet) ──── VyOS gateway ──── VPC Subnet ──── target instance
<WAN-IP-Router> eth0 (public IP) eth1 (VPC IP) <Target-VPC-IP>- The VyOS gateway has one public interface (
eth0) that terminates the tunnel, and one VPC interface (eth1) that faces the internal Subnet. - A custom Subnet route points the remote network’s CIDR at the gateway’s VPC address, so every instance of the Subnet reaches the remote network without any per-instance route.
- The VPC itself has no gateway of its own: traffic leaving the VPC (to the remote network or to the internet) only flows through an instance that a route points at. This VyOS instance plays that role.
Before You Start
You need:
- The Exoscale CLI, configured with
exo config. - An SSH key registered in your Exoscale account.
- The security parameters agreed with the operator of the external router: IKE version, cipher/hash/DH group, lifetimes, and the pre-shared secret.
- A VyOS image from the Exoscale Marketplace (see the listing for the available versions and their cost).
- Instance size: a
tinyinstance with a 10 GB disk is enough for a gateway.
Agree on the following addresses before you start, then export them in your shell — the commands below refer to them:
export ZONE=ch-gva-2
export SSH_KEY=your-ssh-key-in-exoscale
export VPC=my-vpc
export SUBNET=my-subnet
export VPC_CIDR=10.1.0.0/24
export GATEWAY_VPC_IP=10.1.0.10 # requested at attachment time
export TARGET_VPC_IP=10.1.0.20
export WAN_IP_ROUTER=203.0.113.1 # public IP of the external router
export LOCAL_IP_ROUTER=192.0.2.0/24 # remote network behind that router
export PRE_SHARED_SECRET='change-me'
| Placeholder | Meaning |
|---|---|
WAN_IP_ROUTER | Public IP of the external router (IPsec peer) |
LOCAL_IP_ROUTER | CIDR of the remote network behind the external router |
WAN_IP_EXOSCALE | Public IP of the VyOS gateway (eth0), shown after instance creation |
GATEWAY_VPC_IP | VPC address of the VyOS gateway (eth1) |
TARGET_VPC_IP | VPC address of the workload instance |
LOCAL_IP_ROUTER_PREFIX | Remote network CIDR, used as tunnel prefix and route destination |
Step 1: Create the VPC and its Subnet
A VPC is an empty container: addressing lives in its Subnets.
exo networking vpc create $VPC -z $ZONE
exo networking vpc subnet create $VPC $SUBNET -z $ZONE \
--ipv4-block $VPC_CIDR \
--address-family inet4 \
--address-space private
Subnet blocks must be inside the RFC 1918 ranges (10.0.0.0/8, 172.16.0.0/12 or
192.168.0.0/16) and must not overlap the remote network behind the tunnel.
In the console, open Networking > VPCs > Add VPC, then add a Subnet to it with the same IPv4 block.
Step 2: Create the Security Groups and Rules
The tunnel is filtered on the public interface, and the traffic inside the VPC is filtered as well.
# Tunnel: ESP, IKE and NAT-T, plus SSH administration (ssh can be removed afterwards to reduce the attack surface)
exo compute security-group create vpn-tunnel
exo compute security-group rule add vpn-tunnel \
--flow ingress \
--protocol esp \
--network $WAN_IP_ROUTER/32
exo compute security-group rule add vpn-tunnel \
--flow ingress \
--protocol udp \
--port 500 \
--network $WAN_IP_ROUTER/32
exo compute security-group rule add vpn-tunnel \
--flow ingress \
--protocol udp \
--port 4500 \
--network $WAN_IP_ROUTER/32
exo compute security-group rule add vpn-tunnel \
--flow ingress \
--protocol tcp \
--port 22 \
--network <your-admin-ip>/32
# VPC traffic: allow the remote network and the Subnet talk to the gateway
exo compute security-group rule add vpn-tunnel \
--flow ingress \
--protocol all \
--network $LOCAL_IP_ROUTER
# Everything inside the Subnet (gateway <-> target instance)
exo compute security-group create vpc-local
exo compute security-group rule add vpc-local \
--flow ingress \
--protocol all \
--security-group vpc-local
Note
Do not use the default Security Group for a VPN gateway: it is shared by every instance that
does not specify another one, so opening the tunnel ports there exposes them everywhere.
If your router does not support IKEv2 or NAT-Traversal, keep UDP 500 and allow ESP; if a NAT sits in the path, UDP 4500 (NAT-T) is mandatory.
Step 3: Create the VyOS Gateway Instance
--ip-forwarding must be set at creation time and cannot be added later.
exo compute instance create vpn-gateway -z $ZONE \
--template <VyOS-template-name> \
--instance-type tiny \
--disk-size 10 \
--ip-forwarding \
--ssh-key $SSH_KEY \
--security-group vpn-tunnel \
--security-group vpc-local
Note the public IP of the instance. It is WAN_IP_EXOSCALE, the local ID of the IPsec tunnel:
exo compute instance show vpn-gateway -z $ZONE
Step 4: Attach the Instances to the Subnet
Attachment adds a network interface to a running instance; no reboot is required. Request a fixed
address for the gateway with --ipv4, and let the platform pick one for the target instance:
exo compute instance attach-to-subnet vpn-gateway $VPC $SUBNET -z $ZONE \
--ipv4 $GATEWAY_VPC_IP
exo compute instance attach-to-subnet target-vm $VPC $SUBNET -z $ZONE
The new interface is configured through DHCP, so it is usable as soon as the instance supports it. The gateway can already be configured with the address you requested (see Step 5).
Step 5: Route the Remote Network to the Gateway
Without this route, the platform has no reason to deliver the remote network’s traffic to VyOS. Routes are scoped to a Subnet:
exo networking vpc route create $VPC -z $ZONE \
--subnet $SUBNET \
--destination $LOCAL_IP_ROUTER \
--target ip=$GATEWAY_VPC_IP \
--description "Site-to-site VPN via VyOS"
Check it:
exo networking vpc route list -z $ZONE --vpc $VPC
Note
Because of this route, you no longer need a static route on every workload instance: the platform steers the traffic to the gateway for you. This is the main simplification of this design.
Step 6: Configure VyOS
Connect to the gateway with SSH, then enter configuration mode:
ssh vyos@<WAN_IP_EXOSCALE>
configure
Set the VPC interface to the address you requested at attachment time:
set interfaces ethernet eth1 address '<GATEWAY_VPC_IP>/24'
set system host-name 'vpn-endpoint'
IPsec base configuration
set vpn ipsec ipsec-interfaces interface 'eth0'
set vpn ipsec logging log-level '1'
set vpn ipsec logging log-modes 'any'
IPsec phase 1
Replace the values with the ones agreed with the operator of the router. In the following block,
<...> marks a value you must supply:
set vpn ipsec ike-group ike-local-remote close-action 'none'
set vpn ipsec ike-group ike-local-remote dead-peer-detection action 'clear'
set vpn ipsec ike-group ike-local-remote dead-peer-detection interval '30'
set vpn ipsec ike-group ike-local-remote dead-peer-detection timeout '90'
set vpn ipsec ike-group ike-local-remote ikev2-reauth 'no'
set vpn ipsec ike-group ike-local-remote key-exchange '<ikev1|ikev2>'
set vpn ipsec ike-group ike-local-remote lifetime '<seconds, e.g. 86400>'
set vpn ipsec ike-group ike-local-remote proposal 1 dh-group '<group, e.g. 21>'
set vpn ipsec ike-group ike-local-remote proposal 1 encryption '<algo, e.g. aes256gcm128>'
set vpn ipsec ike-group ike-local-remote proposal 1 hash '<algo, e.g. sha512>'
IPsec phase 2
set vpn ipsec esp-group esp-local-remote compression 'disable'
set vpn ipsec esp-group esp-local-remote lifetime '<seconds, e.g. 3600>'
set vpn ipsec esp-group esp-local-remote mode 'tunnel'
set vpn ipsec esp-group esp-local-remote pfs '<dh-group, e.g. dh-group21>'
set vpn ipsec esp-group esp-local-remote proposal 1 encryption '<algo, e.g. aes256gcm128>'
set vpn ipsec esp-group esp-local-remote proposal 1 hash '<algo, e.g. sha1>'
Site-to-site configuration
set vpn ipsec site-to-site peer <WAN_IP_ROUTER> authentication id '<WAN_IP_EXOSCALE>'
set vpn ipsec site-to-site peer <WAN_IP_ROUTER> authentication mode 'pre-shared-secret'
set vpn ipsec site-to-site peer <WAN_IP_ROUTER> authentication pre-shared-secret '<PRE_SHARED_SECRET>'
set vpn ipsec site-to-site peer <WAN_IP_ROUTER> connection-type 'initiate'
set vpn ipsec site-to-site peer <WAN_IP_ROUTER> default-esp-group 'esp-local-remote'
set vpn ipsec site-to-site peer <WAN_IP_ROUTER> ike-group 'ike-local-remote'
set vpn ipsec site-to-site peer <WAN_IP_ROUTER> ikev2-reauth 'inherit'
set vpn ipsec site-to-site peer <WAN_IP_ROUTER> local-address '<WAN_IP_EXOSCALE>'
set vpn ipsec site-to-site peer <WAN_IP_ROUTER> tunnel 0 local prefix '<GATEWAY_VPC_IP/24>'
set vpn ipsec site-to-site peer <WAN_IP_ROUTER> tunnel 0 remote prefix '<LOCAL_IP_ROUTER>'
Enable forwarding and apply
Forwarding has to be allowed inside the operating system too, and it must survive a reboot:
set system option ip-forwarding 'enable'
commit
save
Step 7: Check the Tunnel
show vpn ipsec sa
show vpn ipsec ike-sa
ping <first-address-of-LOCAL_IP_ROUTER>
From the target instance, a ping of an address of the remote network confirms the whole path — VPC route, gateway, tunnel:
ping <first-address-of-LOCAL_IP_ROUTER>

Step 8: Configure the Workload Instances
In most cases there is nothing to do: the VPC route added in Step 5 carries the remote network’s traffic to the gateway, and the reply follows the same path back.
Add an operating-system route only when the instance must reach the remote network through its public interface (if any), or when the VPC route cannot cover the destination:
# Windows (persistent route via the gateway's VPC address)
route add <LOCAL_IP_ROUTER> MASK <REMOTE_NETMASK> <GATEWAY_VPC_IP> -p
# Linux
ip route add <LOCAL_IP_ROUTER> via <GATEWAY_VPC_IP>
Persist the route with the network configuration mechanism of your distribution,see the documentation of your operating system.
Terraform
The same Exoscale side can be described in Terraform. The VPC attachment of an instance is a
vpc block holding an interfaces list — one element per Subnet (provider exoscale
>= 0.76.0):
resource "exoscale_vpc" "this" {
zone = "ch-gva-2"
name = "my-vpc"
}
resource "exoscale_vpc_subnet" "this" {
zone = "ch-gva-2"
vpc_id = exoscale_vpc.this.id
name = "my-subnet"
ipv4_block = "10.1.0.0/24"
}
resource "exoscale_compute_instance" "vpn_gateway" {
zone = "ch-gva-2"
name = "vpn-gateway"
template_id = "<VyOS-template-id>"
type = "tiny"
disk_size = 10
# Required: the gateway forwards packets of other instances.
# Removing the `vpc` block detaches the instance from every Subnet.
vpc {
id = exoscale_vpc.this.id
interfaces = [
{
subnet_id = exoscale_vpc_subnet.this.id
ipv4_address = "10.1.0.10"
}
]
}
}
resource "exoscale_vpc_route" "remote_network" {
zone = "ch-gva-2"
vpc_id = exoscale_vpc.this.id
subnet_id = exoscale_vpc_subnet.this.id
destination = "192.0.2.0/24"
# The target is a string, formatted `ip=<next hop address>`.
target = "ip=10.1.0.10"
description = "Site-to-site VPN via VyOS"
}Important
exoscale_compute_instance does not expose IP forwarding: it can only be requested when the
instance is created, with the CLI flag --ip-forwarding. Create the gateway with
exo compute instance create --ip-forwarding and import it in the state, or keep the whole
gateway out of Terraform and manage only the VPC, Subnets, attachments and routes with it.
Argument reference: exoscale_compute_instance, exoscale_vpc, exoscale_vpc_subnet and exoscale_vpc_route.
VyOS: Useful Commands
configure # enter configuration mode
commit # apply the pending changes
save # persist the configuration
exit discard # leave configuration mode without saving
show configuration commands # show the running configuration as commands
show interfaces # interface status and addresses
show ip route # routing table
clear vpn ipsec-peers # tear the tunnel down to force a renegotiation
Cloud-Init
VyOS images from version 1.2.4 on can be configured with cloud-init user data. Provide the VyOS
commands to run, without the leading set keyword duplication:
#cloud-config
vyos_config_commands:
- set system host-name 'vpn-endpoint'
- set interfaces ethernet eth1 address '10.1.0.10/24'It is also possible to serve the commands over HTTP, from a URL only reachable from the instance:
http://192.0.2.10/vyos-commands.txtWarning
Do not expose a URL containing the pre-shared secret on a public address. Pass the secret from a secret manager or enter it manually in configuration mode.
More Information
- VyOS documentation
- VPC Overview — addressing, routing and isolation model
- Connect a VPC to an External Network with WireGuard
- VyOS cloud-init user data