As stated on the StrongSwan Primer wiki page, StrongSwan has gone through an aggressive redesign and you should take care when installing it not to install the older legacy version.
The natural behavior would be to install the StrongSwan meta package. This should not be done
Instead we install charon-systemd and strongswan-swanctl
#Make sure the old StrongSwan versions are not installed or running
sudo systemctl disable strongswan.service
sudo systemctl stop strongswan
sudo apt-get remove strongswan-starter
sudo apt-get remove strongswan-charon
#Install the new style StrongSwan
sudo apt-get install charon-systemd strongswan-swanctl
#Enable its startup
sudo systemctl enable strongswan.service
#This will link the strongswan-swanctl
#Created symlink /etc/systemd/system/strongswan-swanctl.service → /usr/lib/systemd/system/strongswan.service.
#Created symlink /etc/systemd/system/multi-user.target.wants/strongswan.service → /usr/lib/systemd/system/strongswan.service.
#Start it up
sudo systemctl start strongswan-swanctl.service
#=== Or for the same result alternatively ===
sudo systemctl start strongswan
sudo service strongswan start
#Check Its status
sudo systemctl status strongswan-swanctl.service
#=== Or for the same result alternatively ===
sudo systemctl status strongswan
sudo service strongswan status
● strongswan.service - strongSwan IPsec IKEv1/IKEv2 daemon using swanctl
Loaded: loaded (/usr/lib/systemd/system/strongswan.service; enabled; preset: enabled)
Active: active (running) since Sun 2026-01-11 10:17:49 UTC; 11min ago
Main PID: 1777399 (charon-systemd)
Status: "charon-systemd running, strongSwan 5.9.13, Linux 6.8.0-90-generic, x86_64"
Tasks: 17 (limit: 1107)
Memory: 4.6M (peak: 20.0M)
CPU: 2.445s
CGroup: /system.slice/strongswan.service
└─1777399 /usr/sbin/charon-systemd
sudo apt-get install strongswan-pki
mkdir -p ~/pki/{ca,certs,private}
chmod 700 ~/pki
cd ~/pki
#Create the CA Certificate
pki --gen --type rsa --size 4096 --outform pem > ca/ca.key
pki --self --ca --lifetime 3650 --in ca/ca.key --type rsa --dn "CN=VPN Root CA" --outform pem > ca/ca.crt
#Create the server certificate:
pki --gen --type rsa --size 4096 --outform pem > private/server.key
pki --issue --lifetime 825 --in private/server.key --type rsa --cacert ca/ca.crt --cakey ca/ca.key --dn "CN=cloud.radiusdesk.com" --san cloud.radiusdesk.com --flag serverAuth --flag ikeIntermediate --outform pem > certs/server.crt
#Create a client certificate
pki --gen --type rsa --size 4096 --outform pem > private/carol.key
pki --issue --lifetime 825 --in private/carol.key --type rsa --cacert ca/ca.crt --cakey ca/ca.key --dn "CN=Carol" --san carol@strongswan.org --flag clientAuth --outform pem > certs/carolCert.pem
#View it
pki --print --in certs/carolCert.pem
The config file has a JSON like structure.
Please note that it is not valid JSON but rather a JSON like structure.
Below is our demo server's config:
connections {
xfrm-gw {
local_addrs = %any
remote_addrs = %any
pools = rw_pool
# XFRM interface binding - CRITICAL
if_id_in = 100
if_id_out = 100
version = 2
proposals = aes128-sha1-modp2048
local {
auth = pubkey
certs = server.crt
id = cloud.radiusdesk.com
}
remote {
auth = pubkey
}
children {
xfrm-gw {
local_ts = 0.0.0.0/0
remote_ts = 0.0.0.0/0
if_id_in = 100
if_id_out = 100
esp_proposals = aes128-sha1-modp2048
start_action = start
dpd_action = restart
}
}
send_cert = always
dpd_delay = 30s
rekey_time = 1h
#send_certreq = no
}
}
pools {
rw_pool {
addrs = 10.3.1.0/16
}
}
When a client connects establishes a connection to the StrongSwan server, it is referred to as a Security Association (SA).
We can use the swanctl command to see if there are any existing SAs.
sudo swanctl --list-sa
xfrm-gw: #11, ESTABLISHED, IKEv2, 8c4f8f2882625d72_i* f423c783942a5006_r
local 'cloud.radiusdesk.com' @ 164.160.89.129[4500]
remote 'carol@strongswan.org' @ 197.64.146.11[4500] [10.3.1.0]
AES_CBC-128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_2048
established 679s ago, rekeying in 2626s
xfrm-gw: #7, reqid 1, INSTALLED, TUNNEL-in-UDP, ESP:AES_CBC-128/HMAC_SHA1_96/MODP_2048
installed 1372s ago, rekeying in 2128s, expires in 2588s
in c9f9936f (-|0x00000064), 2645 bytes, 29 packets, 139s ago
out c33bfaa6 (-|0x00000064), 22938 bytes, 38 packets, 139s ago
local 0.0.0.0/0
remote 0.0.0.0/0
We implement a route based IPsec VPN (The other option is policy based).
Route based IPsec specify a if_id_in and if_id_out.
This is used to tag traffic inside a SA.
With Wireguard, you can have multiple instances running on different ports.
With StrongSwan there is one instance, but you can have multiple connections defined in the config file each using a unique if_id_in and if_id_out.
The if_id_in and if_if_out in tern have to terminate into a xfrm interface.
We will create a startup script that prepare this interface for us BEFORE we start StrongSwan.
Create the file /usr/local/sbin/xfrm-up.sh with the following contents.
We assume eth0 is the interface name where the server gets it Internet from. Please adapt if your server is different.
#!/bin/sh
set -e
#
IFACE=xfrm0
IF_ID=100
ADDR=10.3.0.1/32
SUBNET=10.3.0.0/24
# Create XFRM interface
ip link show "$IFACE" >/dev/null 2>&1 || \
ip link add "$IFACE" type xfrm if_id "$IF_ID"
# Assign IP
ip addr show "$IFACE" | grep -q "$ADDR" || \
ip addr add "$ADDR" dev "$IFACE"
# Bring interface up
ip link set "$IFACE" up
# Route for remote side
ip route show "$SUBNET" | grep -q "$IFACE" || \
ip route add "$SUBNET" dev "$IFACE"
# ---- NAT via nftables ----
# Create table if missing
nft list table ip nat >/dev/null 2>&1 || \
nft add table ip nat
# Create postrouting chain if missing
nft list chain ip nat postrouting >/dev/null 2>&1 || \
nft add chain ip nat postrouting { type nat hook postrouting priority 100\; }
# Add SNAT/MASQUERADE rule (idempotent)
nft list chain ip nat postrouting | grep -q "$IFACE" || \
nft add rule ip nat postrouting oifname "$IFACE" masquerade
nft add rule ip nat postrouting oifname "eth0" masquerade
[Unit]
Description=XFRM Interface xfrm0
Before=strongswan.service
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/xfrm-up.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
systemctl enable xfrm0
systemctl start xfrm0
Everything is now prepared on the server side.
We can now configure clients in APdesk and MESHdesk to route certain traffic through the IPsec tunnel.
These are covered in a dedicated Wiki page.