This guide shows how to configure a simple IPsec site-to-site VPN in Hub & Spoke mode on Ubuntu 24.04 using strongSwan and IKEv2 with a pre-shared key.
The goal is for both remote branches to establish independent IPsec tunnels to the central HQ server and reach the protected HQ address.
Network Topology
HQ
Public IP: 31.13.224.246
Protected IP: 192.168.111.1/32
Branch1
Public IP: 91.92.1.12
Protected IP: 192.168.113.1/32
Branch2
Public IP: 87.120.223.222
Protected IP: 192.168.112.1/32
Both branches communicate with 192.168.111.1 through separate IPsec tunnels. Direct Branch1-to-Branch2 communication is not configured in this example.
The example uses /32 traffic selectors, which means that only the listed protected host addresses are carried through the tunnels.
Install strongSwan
Install strongSwan on HQ, Branch1 and Branch2:
sudo apt update
sudo apt install strongswan -y
Enable and start the strongSwan service:
sudo systemctl enable --now strongswan-starter
The protected /32 addresses used in this example must already exist on the corresponding servers, for example as loopback addresses.
Configure the central strongSwan server:
root@hq:~# cat /etc/ipsec.conf
config setup
uniqueids=no
conn %default
keyexchange=ikev2
type=tunnel
authby=psk
left=31.13.224.246
leftid=31.13.224.246
ike=aes256-sha256-modp2048!
esp=aes256-sha256!
dpdaction=restart
dpddelay=30s
dpdtimeout=120s
auto=start
conn remote1
leftsubnet=192.168.111.1/32
right=87.120.223.222
rightid=87.120.223.222
rightsubnet=192.168.112.1/32
conn remote2
leftsubnet=192.168.111.1/32
right=91.92.1.12
rightid=91.92.1.12
rightsubnet=192.168.113.1/32
Configure the pre-shared keys:
root@hq:~# cat /etc/ipsec.secrets
31.13.224.246 87.120.223.222 : PSK "VeryStrongTestKey123!"
31.13.224.246 91.92.1.12 : PSK "VeryStrongTestKey123!"
Protect the secrets file and restart strongSwan:
chmod 600 /etc/ipsec.secrets
systemctl restart strongswan-starter
Branch1 uses public IP 91.92.1.12 and protected IP 192.168.113.1/32:
root@branch1:~# cat /etc/ipsec.conf
config setup
uniqueids=no
conn remote-hq
keyexchange=ikev2
type=tunnel
auto=start
authby=psk
left=91.92.1.12
leftid=91.92.1.12
leftsubnet=192.168.113.1/32
right=31.13.224.246
rightid=31.13.224.246
rightsubnet=192.168.111.1/32
ike=aes256-sha256-modp2048!
esp=aes256-sha256!
dpdaction=restart
dpddelay=30s
dpdtimeout=120s
Configure the Branch1 pre-shared key:
root@branch1:~# cat /etc/ipsec.secrets
91.92.1.12 31.13.224.246 : PSK "VeryStrongTestKey123!"
Restart strongSwan:
chmod 600 /etc/ipsec.secrets
systemctl restart strongswan-starter
Branch2 uses public IP 87.120.223.222 and protected IP 192.168.112.1/32:
root@branch2:~# cat /etc/ipsec.conf
config setup
uniqueids=no
conn remote-hq
keyexchange=ikev2
type=tunnel
auto=start
authby=psk
left=87.120.223.222
leftid=87.120.223.222
leftsubnet=192.168.112.1/32
right=31.13.224.246
rightid=31.13.224.246
rightsubnet=192.168.111.1/32
ike=aes256-sha256-modp2048!
esp=aes256-sha256!
dpdaction=restart
dpddelay=30s
dpdtimeout=120s
Configure the Branch2 pre-shared key:
root@branch2:~# cat /etc/ipsec.secrets
87.120.223.222 31.13.224.246 : PSK "VeryStrongTestKey123!"
Restart strongSwan:
chmod 600 /etc/ipsec.secrets
systemctl restart strongswan-starter
Check IPsec Status on HQ
The HQ server should show two established Security Associations:
root@hq:~# ipsec status
Security Associations (2 up, 0 connecting):
remote2[2]: ESTABLISHED 59 seconds ago, 31.13.224.246[31.13.224.246]...91.92.1.12[91.92.1.12]
remote2{1}: INSTALLED, TUNNEL, reqid 1, ESP SPIs: ca27a89d_i cb17d398_o
remote2{1}: 192.168.111.1/32 === 192.168.113.1/32
remote1[1]: ESTABLISHED 59 seconds ago, 31.13.224.246[31.13.224.246]...87.120.223.222[87.120.223.222]
remote1{2}: INSTALLED, TUNNEL, reqid 2, ESP SPIs: c6ec388d_i c303d92d_o
remote1{2}: 192.168.111.1/32 === 192.168.112.1/32
Test HQ to Branch1
root@hq:~# ping 192.168.113.1
PING 192.168.113.1 (192.168.113.1) 56(84) bytes of data.
64 bytes from 192.168.113.1: icmp_seq=1 ttl=64 time=13.2 ms
64 bytes from 192.168.113.1: icmp_seq=2 ttl=64 time=13.5 ms
64 bytes from 192.168.113.1: icmp_seq=3 ttl=64 time=13.6 ms
64 bytes from 192.168.113.1: icmp_seq=4 ttl=64 time=13.4 ms
^C
--- 192.168.113.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 13.177/13.414/13.573/0.145 ms
Test HQ to Branch2
root@hq:~# ping 192.168.112.1
PING 192.168.112.1 (192.168.112.1) 56(84) bytes of data.
64 bytes from 192.168.112.1: icmp_seq=1 ttl=64 time=26.0 ms
64 bytes from 192.168.112.1: icmp_seq=2 ttl=64 time=25.6 ms
64 bytes from 192.168.112.1: icmp_seq=3 ttl=64 time=25.6 ms
64 bytes from 192.168.112.1: icmp_seq=4 ttl=64 time=25.9 ms
^C
--- 192.168.112.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 25.620/25.779/25.980/0.156 ms
(SDC22)root@hq:~#
Verify Branch1
root@branch1:~# ipsec status
Security Associations (1 up, 0 connecting):
remote-hq[2]: ESTABLISHED 3 minutes ago, 91.92.1.12[91.92.1.12]...31.13.224.246[31.13.224.246]
remote-hq{2}: INSTALLED, TUNNEL, reqid 1, ESP SPIs: cb17d398_i ca27a89d_o
remote-hq{2}: 192.168.113.1/32 === 192.168.111.1/32
root@branch1:~# ping 192.168.111.1
PING 192.168.111.1 (192.168.111.1) 56(84) bytes of data.
64 bytes from 192.168.111.1: icmp_seq=1 ttl=64 time=13.6 ms
64 bytes from 192.168.111.1: icmp_seq=2 ttl=64 time=13.5 ms
64 bytes from 192.168.111.1: icmp_seq=3 ttl=64 time=13.7 ms
64 bytes from 192.168.111.1: icmp_seq=4 ttl=64 time=13.6 ms
^C
--- 192.168.111.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 13.536/13.615/13.666/0.050 ms
root@branch1:~#
Verify Branch2
root@branch2:~# ipsec status
Security Associations (1 up, 0 connecting):
remote-hq[2]: ESTABLISHED 3 minutes ago, 87.120.223.222[87.120.223.222]...31.13.224.246[31.13.224.246]
remote-hq{2}: INSTALLED, TUNNEL, reqid 1, ESP SPIs: c303d92d_i c6ec388d_o
remote-hq{2}: 192.168.112.1/32 === 192.168.111.1/32
root@branch2:~# ping 192.168.111.1
PING 192.168.111.1 (192.168.111.1) 56(84) bytes of data.
64 bytes from 192.168.111.1: icmp_seq=1 ttl=64 time=25.8 ms
64 bytes from 192.168.111.1: icmp_seq=2 ttl=64 time=26.0 ms
64 bytes from 192.168.111.1: icmp_seq=3 ttl=64 time=26.0 ms
64 bytes from 192.168.111.1: icmp_seq=4 ttl=64 time=25.9 ms
^C
--- 192.168.111.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
rtt min/avg/max/mdev = 25.832/25.943/26.040/0.086 ms
root@branch2:~#
Firewall Requirements
If a firewall is enabled between the VPN peers, allow IKE and IPsec traffic:
- UDP port 500 - IKE
- UDP port 4500 - IPsec NAT Traversal
- IP protocol 50 - ESP
Useful strongSwan Commands
ipsec status
ipsec statusall
systemctl status strongswan-starter
systemctl restart strongswan-starter
journalctl -u strongswan-starter -f