rss logo

Configure a Multi-Site IPsec VPN on Debian 13 with strongSwan 6

strongSwan logo

With each major release, Debian introduces updated software packages. Debian 13 “Trixie” now ships with strongSwan 6, which brings significant changes to the configuration format compared to previous versions. As a result, configuration files from earlier releases such as Debian 12 are no longer compatible, since the syntax has been completely redesigned.

In this guide, I will demonstrate how to configure strongSwan 6 for a multi-site IPsec VPN architecture using three Debian 13 routers. This setup builds upon my previous tutorial for Debian 12 “Bookworm”, which you can find here: Setting Up a Multi-Site IPsec VPN with strongSwan on Debian 12.

💡 Note: it would have been possible to configure a direct IPsec tunnel between Branch Office 1 and Branch Office 2. However, since the traffic between the two sites is minimal (mainly IP phone calls), I chose to route this communication through the Headquarters VPN gateway instead. Moreover, this setup introduces additional complexity and challenges, which makes it even more interesting to see how it can be configured step by step. 🤓

Network Architecture Diagram

Network diagram of a multi-site IPsec VPN on Debian 13 with StrongSwan 6, showing headquarters and two branch offices interconnected, each with VLANs for users, VoIP, WiFi, and servers.
Multi-site IPsec VPN architecture with Debian 13 and StrongSwan 6, connecting headquarters and two branch offices through secure tunnels, with VLANs dedicated to users, VoIP, WiFi, and servers.

Site 1 – Headquarters (VPN Gateway)

Let's start with the configuration of the main site. This site centralizes all VPN connections for the other locations, and it also serves as the authority that allows specific traffic—in this case, the VoIP network—between Branch Office 1 and Branch Office 2.

Headquarters - Prerequisites

  • Install the strongSwan package:
root@HQ:~# apt update && apt install strongswan
  • Enable nftables autostart:
root@HQ:~# systemctl enable nftables.service

Headquarters - nftables Configuration

The configuration of nftables is simplified here, allowing networks to access the Internet and communicate with each other. In a production environment, you may want to configure filtering rules between VLANs.

  • Edit the /etc/nftables.conf file:
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
        chain input {
                type filter hook input priority 0; policy accept;
	}
        chain forward {
                type filter hook forward priority 0; policy accept ;
	}
	chain output {
                type filter hook output priority 0; policy accept;
        }
}

#NAT for outgoing traffic.
table ip my_nat {
        chain my_masquerade {
                type nat hook postrouting priority 100;
                ip daddr != { 10.1.0.1, 192.168.0.0/16 } oifname wan masquerade comment "output nat"
        }
}
  • Reload the nftables configuration:
root@HQ:~# nft -f /etc/nftables.conf

Headquarters - Network Configuration

Configure Headquarters Network Interfaces

Here, we configure our two network interfaces: lan and wan. For details on how to rename network interfaces in Debian, see this guide: Renaming Network Interfaces on Debian. We will configure the system to run the script /usr/local/sbin/ipconf.sh (which we will create later) automatically when the lan interface comes up.

  • Edit the /etc/network/interfaces file:
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
# This is an autoconfigured IPv6 interface
allow-hotplug wan
iface wan inet static
        address 10.1.0.1
        netmask 255.255.255.0
	gateway 10.1.0.254

allow-hotplug lan
iface lan inet static
        address 192.168.1.254
        netmask 255.255.255.0
        up /usr/local/sbin/ipconf.sh
  • Reboot the system or restart the networking service:
root@HQ:~# systemctl restart networking

Enable IP Forwarding for Gateway Mode

Since our Debian server will act as a router, we need to enable IP forwarding.

  • Create a /etc/sysctl.d/99-ipforward.conf file and add the following line:
net.ipv4.ip_forward=1
  • Apply the change immediately with the following command:
root@HQ:~# sysctl -p /etc/sysctl.d/99-ipforward.conf

Network Script

The /usr/local/sbin/ipconf.sh script will be executed at startup. It is used to configure the VLAN interfaces automatically.

  • Create a /usr/local/sbin/ipconf.sh file:
#!/bin/sh

# VLAN configuration script for the lan interface
# This script creates VLAN interfaces and assigns IP addresses
# Run at startup to ensure VLANs are ready before services start

# --- SETTING UP VLANs ON THE lan INTERFACE ---
modprobe 8021q
ip link add link lan name users type vlan id 2
ip link add link lan name voip type vlan id 3
ip link add link lan name wifi type vlan id 4
ip link add link lan name servers type vlan id 5

# Bring VLAN interfaces up
ip link set users up
ip link set voip up
ip link set wifi up
ip link set servers up

# --- VLAN INTERFACE IP ADDRESS SETTINGS ---
ip addr add 192.168.2.254/24 dev users
ip addr add 192.168.3.254/24 dev voip
ip addr add 192.168.4.254/22 dev wifi
ip addr add 192.168.5.254/24 dev servers
  • Modify the permissions so that the /usr/local/sbin/ipconf.sh script is executable:
root@host:~# chmod +x /usr/local/sbin/ipconf.sh

Headquarters - strongSwan Configuration

strongSwan Main Configuration File: /etc/swanctl/swanctl.conf

  • Edit the /etc/swanctl/swanctl.conf file to configure the site-to-site connections. The goal is to allow VoIP networks at all sites to communicate with each other, and to enable users at Branch Office 1 and Branch Office 2 to access the Headquarters server network:
connections {

   #############################################################
   # Tunnel between Headquarters (HQ) and Branch Office 1 (B1) #
   #############################################################
   hq-b1 {
      version = 2            # Use IKEv2
      mobike = no            # Disable MOBIKE (static IPs, no mobility expected)

      local_addrs  = 10.1.0.1            # Public IP (or WAN IP) of HQ
      remote_addrs = 10.10.0.1           # Public IP of Branch Office 1

      proposals = aes128-sha256-x25519  # IKE proposals
      reauth_time = 0            # Disable reauth, rely on rekey instead

      dpd_delay   = 20s          # Send Dead Peer Detection probes every 20s
      dpd_timeout = 60s          # Consider peer dead after 60s

      local {
         auth = psk             # Pre-shared key authentication
         id = 10.1.0.1          # Local identifier (HQ)
      }
      remote {
         auth = psk
         id = 10.10.0.1         # Remote identifier (B1)
      }
      children {
         net-net {
            # Traffic Selectors (subnets allowed through this tunnel)
            # HQ subnets + VoIP VLAN of B2 (for inter-branch VoIP via HQ)
            local_ts  = 192.168.1.0/24, 192.168.3.0/24, 192.168.5.0/24, 192.168.20.0/24, 192.168.23.0/24
            # Subnets from Branch Office 1
            remote_ts = 192.168.10.0/24, 192.168.12.0/24, 192.168.13.0/24

            start_action = start      # Bring up tunnel automatically at startup
            rekey_time = 3600         # Rekey every 60 minutes
            dpd_action  = restart      # Restart connection if peer is dead

            esp_proposals = aes128gcm128-x25519
         }
      }
   }

   #############################################################
   # Tunnel between Headquarters (HQ) and Branch Office 2 (B2) #
   #############################################################
   hq-b2 {
      version = 2            # Use IKEv2
      mobike = no            # Disable MOBIKE (static IPs, no mobility expected)

      local_addrs  = 10.1.0.1            # Public IP (or WAN IP) of HQ
      remote_addrs = 10.20.0.1

      proposals = aes128-sha256-x25519  # IKE proposals
      reauth_time = 0            # Disable reauth, rely on rekey instead

      dpd_delay   = 20s          # Send Dead Peer Detection probes every 20s
      dpd_timeout = 60s          # Consider peer dead after 60s

      local {
         auth = psk             # Pre-shared key authentication
         id = 10.1.0.1          # Local identifier (HQ)
      }
      remote {
         auth = psk
         id = 10.20.0.1         # Remote identifier (B2)
      }
      children {
         net-net {
            # Traffic Selectors (subnets allowed through this tunnel)
            # HQ subnets + VoIP VLAN of B1 (for inter-branch VoIP via HQ)
            local_ts  = 192.168.1.0/24, 192.168.3.0/24, 192.168.5.0/24, 192.168.10.0/24, 192.168.13.0/24
            # Subnets from Branch Office 2
            remote_ts = 192.168.20.0/24, 192.168.22.0/24, 192.168.23.0/24

            start_action = start      # Bring up tunnel automatically at startup
            rekey_time = 3600         # Rekey every 60 minutes
            dpd_action  = restart      # Restart connection if peer is dead

            esp_proposals = aes128gcm128-x25519
         }
      }
   }
}

####################################
# Shared Secrets (Pre-Shared Keys) #
####################################
secrets {
   ike-hq-b1 {
      id = 10.1.0.1
      peer_id = 10.10.0.1
      secret = "JohnWeakPasswd:)"
   }
   ike-hq-b2 {
      id = 10.1.0.1
      peer_id = 10.20.0.1
      secret = "JohnWeakPasswd:)"
   }
}
  • Restart the strongswan service to apply the changes:
root@HQ:~# systemctl restart strongswan

Site 2 – Branch Office 1 VPN Configuration

Let's move on to configuring the first branch office. The setup is similar to that of the headquarters, as it also requires configuring the network interfaces and the strongSwan service.

Branch Office 1 – Prerequisites

  • Install the strongSwan package:
root@B1:~# apt update && apt install strongswan
  • Enable nftables autostart:
root@B1:~# systemctl enable nftables.service

Branch Office 1 – nftables Configuration

As with the headquarters, the nftables configuration here is simplified. No filtering rules are applied in this example.

  • Edit the /etc/nftables.conf file:
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
        chain input {
                type filter hook input priority 0; policy accept;
	}
        chain forward {
                type filter hook forward priority 0; policy accept ;
	}
	chain output {
                type filter hook output priority 0; policy accept;
        }
}

#NAT for outgoing traffic.
table ip my_nat {
        chain my_masquerade {
                type nat hook postrouting priority 100;
                ip daddr != { 10.10.0.1, 192.168.0.0/16 } oifname wan masquerade comment "output nat"
        }
}
  • Reload the nftables configuration:
root@host:~# nft -f /etc/nftables.conf

Branch Office 1 - Network Configuration

Configure Branch Office 1 Network Interfaces

Here, we configure our two network interfaces: lan and wan.

  • Edit the /etc/network/interfaces file:
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
# This is an autoconfigured IPv6 interface
allow-hotplug wan
iface wan inet static
        address 10.10.0.1
        netmask 255.255.255.0
	gateway 10.10.0.254

allow-hotplug lan
iface lan inet static
        address 192.168.10.254
        netmask 255.255.255.0
        up /usr/local/sbin/ipconf.sh

Enable IP Forwarding for Gateway Mode

  • Create a /etc/sysctl.d/99-ipforward.conf file and add the following line:
net.ipv4.ip_forward=1
  • Apply the change immediately with the following command:
root@B1:~# sysctl -p /etc/sysctl.d/99-ipforward.conf

Network Script

The /usr/local/sbin/ipconf.sh script will be executed at startup to configure the VLAN interfaces automatically.

  • Create a /usr/local/sbin/ipconf.sh file:
#!/bin/sh

# VLAN configuration script for the lan interface
# This script creates VLAN interfaces and assigns IP addresses
# Run at startup to ensure VLANs are ready before services start

# --- SETTING UP VLANs ON THE lan INTERFACE ---
modprobe 8021q
ip link add link lan name users type vlan id 2
ip link add link lan name voip type vlan id 3
ip link add link lan name wifi type vlan id 4

# Bring VLAN interfaces up
ip link set users up
ip link set voip up
ip link set wifi up

# --- VLAN INTERFACE IP ADDRESS SETTINGS ---
ip addr add 192.168.12.254/24 dev users
ip addr add 192.168.13.254/24 dev voip
ip addr add 192.168.14.254/22 dev wifi
  • Modify the permissions so that the /usr/local/sbin/ipconf.sh script is executable:
root@B1:~# chmod +x /usr/local/sbin/ipconf.sh

Branch Office 1 - strongSwan Configuration

strongSwan Main Configuration File: /etc/swanctl/swanctl.conf

  • Edit the /etc/swanctl/swanctl.conf file:
connections {

   #############################################################
   # Tunnel between Branch Office 1 (B1) and Headquarters (HQ) #
   #############################################################
   b1-hq {
      version = 2            # Use IKEv2
      mobike = no            # Disable MOBIKE (static IPs, no mobility expected)

      local_addrs  = 10.10.0.1           # Public IP of Branch Office 1
      remote_addrs = 10.1.0.1            # Public IP (or WAN IP) of HQ

      proposals = aes128-sha256-x25519  # IKE proposals
      reauth_time = 0            # Disable reauth, rely on rekey instead

      dpd_delay   = 20s          # Send Dead Peer Detection probes every 20s
      dpd_timeout = 60s          # Consider peer dead after 60s

      local {
         auth = psk             # Pre-shared key authentication
         id = 10.10.0.1         # Remote identifier (B1)
      }
      remote {
         auth = psk             # Pre-shared key authentication
         id = 10.1.0.1          # Local identifier (HQ)
      }
      children {
         net-net {
            # Traffic Selectors (subnets allowed through this tunnel)
            # B1 subnets
            local_ts  = 192.168.10.0/24, 192.168.12.0/24, 192.168.13.0/24
            # HQ subnets + VoIP VLAN of B2 (for inter-branch VoIP via HQ)
            remote_ts = 192.168.1.0/24, 192.168.3.0/24, 192.168.5.0/24, 192.168.20.0/24, 192.168.23.0/24

            start_action = start      # Bring up tunnel automatically at startup
            rekey_time = 3600         # Rekey every 60 minutes
            dpd_action  = restart     # Restart connection if peer is dead

            esp_proposals = aes128gcm128-x25519
         }
      }
   }
}

secrets {
   ike-b1-hq {
      id = 10.10.0.1
      peer_id = 10.1.0.1
      secret = "JohnWeakPasswd:)"
   }
}
  • Restart the strongswan service to apply the changes:
root@B1:~# systemctl restart strongswan

Site 3 – Branch Office 2 VPN Configuration

Branch Office 2 – Prerequisites

  • Install the strongSwan package:
root@B2:~# apt update && apt install strongswan
  • Enable nftables autostart:
root@B2:~# systemctl enable nftables.service

Branch Office 2 – nftables Configuration

As with the headquarters, the nftables configuration here is simplified. No filtering rules are applied in this example.

  • Edit the /etc/nftables.conf file:
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
        chain input {
                type filter hook input priority 0; policy accept;
	}
        chain forward {
                type filter hook forward priority 0; policy accept ;
	}
	chain output {
                type filter hook output priority 0; policy accept;
        }
}

#NAT for outgoing traffic.
table ip my_nat {
        chain my_masquerade {
                type nat hook postrouting priority 100;
                ip daddr != { 10.20.0.1, 192.168.0.0/16 } oifname wan masquerade comment "output nat"
        }
}
  • Reload the nftables configuration:
root@B2:~# nft -f /etc/nftables.conf

Branch Office 2 - Network Configuration

Configure Branch Office 2 Network Interfaces

Here, we configure our two network interfaces: lan and wan.

  • Edit the /etc/network/interfaces file:
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug wan
iface wan inet static
        address 10.20.0.1
        netmask 255.255.255.0

allow-hotplug lan
iface lan inet static
        address 192.168.21.254
        netmask 255.255.255.0
        up /usr/local/sbin/ipconf.sh

Enable IP Forwarding for Gateway Mode

  • Create a /etc/sysctl.d/99-ipforward.conf file and add the following line:
net.ipv4.ip_forward=1
  • Apply the change immediately with the following command:
root@B2:~# sysctl -p /etc/sysctl.d/99-ipforward.conf

Network Script

The /usr/local/sbin/ipconf.sh script will be executed at startup to configure the VLAN interfaces automatically.

  • Create a /usr/local/sbin/ipconf.sh file:
#!/bin/sh

# VLAN configuration script for the lan interface
# This script creates VLAN interfaces and assigns IP addresses
# Run at startup to ensure VLANs are ready before services start

# --- SETTING UP VLANs ON THE lan INTERFACE ---
modprobe 8021q
ip link add link lan name users type vlan id 2
ip link add link lan name voip type vlan id 3
ip link add link lan name wifi type vlan id 4

# Bring VLAN interfaces up
ip link set users up
ip link set voip up
ip link set wifi up

# --- VLAN INTERFACE IP ADDRESS SETTINGS ---
ip addr add 192.168.22.254/24 dev users
ip addr add 192.168.23.254/24 dev voip
ip addr add 192.168.24.254/22 dev wifi
  • Modify the permissions so that the /usr/local/sbin/ipconf.sh script is executable:
root@host:~# chmod +x /usr/local/sbin/ipconf.sh

Branch Office 2 - strongSwan Configuration

strongSwan Main Configuration File: /etc/swanctl/swanctl.conf

  • Edit the /etc/swanctl/swanctl.conf file:
connections {

   #############################################################
   # Tunnel between Branch Office 2 (B2) and Headquarters (HQ) #
   #############################################################
   b2-hq {
      version = 2            # Use IKEv2
      mobike = no            # Disable MOBIKE (static IPs, no mobility expected)

      local_addrs  = 10.20.0.1           # Public IP of Branch Office 2
      remote_addrs = 10.1.0.1            # Public IP (or WAN IP) of HQ

      proposals = aes128-sha256-x25519  # IKE proposals
      reauth_time = 0            # Disable reauth, rely on rekey instead

      dpd_delay   = 20s          # Send Dead Peer Detection probes every 20s
      dpd_timeout = 60s          # Consider peer dead after 60s

      local {
         auth = psk             # Pre-shared key authentication
         id = 10.20.0.1         # Remote identifier (B2)
      }
      remote {
         auth = psk             # Pre-shared key authentication
         id = 10.1.0.1          # Local identifier (HQ)
      }
      children {
         net-net {
            # Traffic Selectors (subnets allowed through this tunnel)
            # B2 subnets
            local_ts  = 192.168.20.0/24, 192.168.22.0/24, 192.168.23.0/24
            # HQ subnets + VoIP VLAN of B2 (for inter-branch VoIP via HQ)
            remote_ts = 192.168.1.0/24, 192.168.3.0/24, 192.168.5.0/24, 192.168.10.0/24, 192.168.13.0/24

            start_action = start      # Bring up tunnel automatically at startup
            rekey_time = 3600         # Rekey every 60 minutes
            dpd_action  = restart     # Restart connection if peer is dead

            esp_proposals = aes128gcm128-x25519
         }
      }
   }
}

secrets {
   ike-b2-hq {
      id = 10.20.0.1
      peer_id = 10.1.0.1
      secret = "JohnWeakPasswd:)"
   }
}
  • Restart the strongswan service to apply the changes:
root@B2:~# systemctl restart strongswan

Troubleshooting

  • Ping from Headquarters:
root@HQ:~# ping 192.168.20.254 -I 192.168.1.254
  • Ping from Branch Office 1:
root@B1:~# ping 192.168.5.254 -I 192.168.12.254
  • Ping from Branch Office 2:
root@B2:~# ping 192.168.13.254 -I 192.168.23.254
  • Check logs:
root@HQ:~# journalctl -u strongswan
  • List strongSwan loaded configurations:
root@HQ:~# swanctl --list-conns
hq-b1: IKEv2, no reauthentication, no rekeying, dpd delay 20s
  local:  10.1.0.1
  remote: 10.10.0.1
  local pre-shared key authentication:
    id: 10.1.0.1
  remote pre-shared key authentication:
    id: 10.10.0.1
  net-net: TUNNEL, rekeying every 3600s, dpd action is start
    local:  192.168.1.0/24 192.168.3.0/24 192.168.5.0/24 192.168.20.0/24 192.168.23.0/24
    remote: 192.168.10.0/24 192.168.12.0/24 192.168.13.0/24
hq-b2: IKEv2, no reauthentication, no rekeying, dpd delay 20s
  local:  10.1.0.1
  remote: 10.20.0.1
  local pre-shared key authentication:
    id: 10.1.0.1
  remote pre-shared key authentication:
    id: 10.20.0.1
  net-net: TUNNEL, rekeying every 3600s, dpd action is none
    local:  192.168.1.0/24 192.168.3.0/24 192.168.5.0/24 192.168.10.0/24 192.168.13.0/24
    remote: 192.168.20.0/24 192.168.22.0/24 192.168.23.0/24
  • List strongSwan currently active IKE_SAs:
root@HQ:~# swanctl --list-sas
hq-b2: #6, ESTABLISHED, IKEv2, d0f895ea384c5d55_i 3b76bf7604c051fd_r*
  local  '10.1.0.1' @ 10.1.0.1[500]
  remote '10.20.0.1' @ 10.20.0.1[500]
  AES_CBC-128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/CURVE_25519
  established 433s ago
  net-net: #7, reqid 2, INSTALLED, TUNNEL, ESP:AES_GCM_16-128
    installed 433s ago, rekeying in 2974s, expires in 3527s
    in  c19f2fff,    672 bytes,     8 packets,   231s ago
    out ca67ea94,    672 bytes,     8 packets,   231s ago
    local  192.168.1.0/24 192.168.3.0/24 192.168.5.0/24 192.168.10.0/24 192.168.13.0/24
    remote 192.168.20.0/24 192.168.22.0/24 192.168.23.0/24
hq-b1: #5, ESTABLISHED, IKEv2, 7c785e920c3a199a_i fddac3562b3129fe_r*
  local  '10.1.0.1' @ 10.1.0.1[500]
  remote '10.10.0.1' @ 10.10.0.1[500]
  AES_CBC-128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/CURVE_25519
  established 1274s ago
  net-net: #5, reqid 1, INSTALLED, TUNNEL, ESP:AES_GCM_16-128
    installed 1274s ago, rekeying in 2179s, expires in 2686s
    in  c8d03828,  56952 bytes,   678 packets,   236s ago
    out cc5e72e5,  56952 bytes,   678 packets,   236s ago
    local  192.168.1.0/24 192.168.3.0/24 192.168.5.0/24 192.168.20.0/24 192.168.23.0/24
    remote 192.168.10.0/24 192.168.12.0/24 192.168.13.0/24