Today I’m trying to understand if Wireguard really is over-hyped, if OpenVPN is really worth all the hassle to get the user-side features like client authentication and two factor, and if IPSec has any place in the modern VPN landscape. Specifically, looking at traditional ‘road warrior’ or client access VPNs, where all of your users are dialing in to your enterprise network, not the new-fangled mesh VPNs or zero trust setups.

This page contains all of the configurations I used in testing, feel free to use them on your own. I did all of my testing on Debian 13 (Trixie) which is currently in testing, on Linux 6.12. My clients are also running the same OS/kernel.

Contents

Video

Thumbnail

Beginning

We need to enable IP forwarding on our router for any of these setups. This doesn’t persist across reboots, but most of my configs on this page are not intended to.

# Enable Forwarding
echo 1 > /proc/sys/net/ipv6/conf/all/forwarding

IPIP

No install here. Just copy the commands and change the IPs as necessary. client is given 2001:db8:7:a::/64 in this example with the router taking the first address for itself, Enterprise subnet is 2001:db8:7::/48 in this example, Public IPs must be hardcoded for IPIP (cannot be dynamic).

# IPIP Setup (on Client)
ip link add name ipip6 type ip6tnl local <client IP> remote <server IP> mode any
ip link set up ipip6
ip addr add 2001:db8:7:a::2/64 dev ipip6
ip -6 route add 2001:db8:7::/48 dev ipip6
# IPIP Setup (on Server)
ip link add name ipip6 type ip6tnl local <server IP> remote <client IP> mode any
ip link set up ipip6
ip addr add 2001:db8:7:a::/64 dev ipip6
#addr add implicitly adds a /64 route to that interface, so no other routes required

Wireguard

First we install it (apt install wireguard)

Next, we need to create keys on each nodes, so run this command on both sides:

#Gen private key
wg genkey | tee /etc/wireguard/server.key
#Gen public key from private key
cat /etc/wireguard/server.key | wg pubkey | tee /etc/wireguard/server.pub
# You will need these two keys later, so be ready to copy and paste them

Next, we need a /etc/wireguard/wg0.conf on both nodes:

# Conf for SERVER side
[Interface]
PrivateKey = oBic4cnZOBjmucL10dtiB5QZpUkqPPjEm9rA6E90iUg=
Address = 2001:db8:7:b::/128
ListenPort = 51820
# Peer (CLIENT's public key here)
[Peer]
PublicKey = 1b0J0WcCObEOq0R9zIQoitRa6I4YqLDDL7evmNwN3Go=
AllowedIPs = 2001:db8:7:b::/64
Endpoint = testsrc2.palnet.net:51820
# Conf for CLIENT side
[Interface]
PrivateKey = qKUMygkE+YPWE2MmdCOi5j6Sno78/w9FvXHGTIVpoVU=
Address = 2001:db8:7:b::4/64
ListenPort = 51820
# Peer (SERVER's public key here)
[Peer]
PublicKey = 2VIGy0JS27RjGI9VDug2iNK4dd/Ned9dNDnlCOP1DTQ=
AllowedIPs = 2001:db8:7::/48
Endpoint = wgmetal.palnet.net:51820

Then bring up the interfaces (wg-quick up wg0 on both sides). WG will automatically brign up the route to our enterprise subnet, and it will work.

OpenVPN

First we install it (apt install openvpn).

Certificates

We will be using cert auth here, so we need an x.509 cert for both sides. For simplicity, I’m using self-signed ECDSA certs using prime256v1 for leafs and scep384 for the CA

#Generate private key using scep384:
openssl ecparam -name secp384r1 -genkey -out root.key

#Sign the root certificate
#Pathlen:0 means there can be only one more cert below this CA (no more CAs)
#Make sure you update the subj name with your own names
#C=US is also the country, it's optional
#O= is the organization, also optional
#CN= is the Common Name and it's required
#I also set validity to 69 years, make sure you watch for expiration (manually)
openssl req -new -key root.key -x509 -nodes -days 25202 -out root.pem -subj "/C=US/O=apalrd.net/CN=openvpn" -addext "basicConstraints=critical,CA:TRUE,pathlen:0"

#Now you can view it (for fun)
openssl x509 -in root.pem -text -noout

Next, generate the server’s cert + key

#Change this to the name of your server
export sv=wgmetal.palnet.net
#Generate scep256 key for this client
#If you want 192-bit security, use scep384r1 instead of prime256v1
openssl ecparam -name prime256v1 -genkey -out $sv.key

#Generate a CSR (certificate signing request) for my new key
#again, C and O are optional, CN is the Common Name of the cert
#Allowed only for server auth
openssl req -new -key $sv.key -out $sv.csr -subj "/C=US/O=apalrd.net/CN=$sv" -addext "extendedKeyUsage = serverAuth" --addext "keyUsage = digitalSignature,keyAgreement" -addext "subjectAltName = DNS:$sv"

#Sign the CSR using the root
#Shorter lived at only 365 days
openssl x509 -req -in $sv.csr -CA root.pem -CAkey root.key -CAcreateserial -out $sv.crt -days 365 -sha256 -copy_extensions=copyall

#Now you can view it (for fun)
openssl x509 -in $sv.crt -text -noout

And finally, generate the client cert:

#Change this to the name of your client
export cl=testsrc2.palnet.net
#Generate scep256 key for this client
#If you want 192-bit security, use scep384r1 instead of prime256v1
openssl ecparam -name prime256v1 -genkey -out $cl.key

#Generate a CSR (certificate signing request) for my new key
#again, C and O are optional, CN is the Common Name of the cert
openssl req -new -key $cl.key -out $cl.csr -subj "/C=US/O=apalrd.net/CN=$cl" -addext "extendedKeyUsage = clientAuth" --addext "keyUsage = digitalSignature,keyAgreement"

#Sign the CSR using the root
#Sign it allowing for server and client auth as the key usage
openssl x509 -req -in $cl.csr -CA root.pem -CAkey root.key -CAcreateserial -out $cl.crt -days 365 -sha256 -copy_extensions=copyall

#Now you can view it (for fun)
openssl x509 -in $cl.crt -text -noout

Transfer your .key and .crt files to the server and client respectively. The server will need its client .key and .crt, and the server will need its server .key and .crt, and they both need root.pem

Server

The server setup is quite complex. Here’s my final /etc/openvpn/server.conf:

server
#UDP over IPV6 outside of tunnel
port 1194
proto udp6
dev tun
user nobody
group nogroup
persist-key
persist-tun
keepalive 10 120
topology subnet

#IPv6 inside of tunnel
server-ipv6 2001:db8:7:c::/64
ifconfig-ipv6-pool 2001:db8:7:c::/64
push "route-ipv6 2001:db8:7::/48"
ifconfig-pool-persist ipp.txt

#Crypto parameters
dh none
ecdh-curve prime256v1
tls-crypt tls-crypt.key

#Client verification
ca root.pem
cert server.crt
key server.key
remote-cert-tls client
# no CRL in my setup
#crl-verify root.pem
auth SHA256
cipher AES-128-GCM
data-ciphers AES-128-GCM
tls-server
tls-version-min 1.2
tls-cipher TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256
client-config-dir /etc/openvpn/ccd
status /var/log/openvpn/status.log
verb 3

Also, a few commands to get it started:

openvpn --genkey secret /etc/openvpn/tls-crypt.key
mkdir -p /etc/openvpn/ccd
mkdir -p /var/log/openvpn
systemctl enable --now openvpn@server

Once we’ve generated tls-crypt.key we need to copy that file to the client (it’s our Shared Secret). We don’t * have * to use shared secret auth, but I have here.

Client

For the client we need a /etc/openvpn/client.conf file:

client
remote wgmetal.palnet.net 1194
dev tun
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
verify-x509-name wgmetal.palnet.net name
auth SHA256
cipher AES-128-GCM
data-ciphers AES-128-GCM
auth-nocache
tls-client
tls-version-min 1.2
tls-cipher TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256
verb 3
#Client certs
cert client.crt
key client.key
#Server's CA
ca root.pem
tls-crypt tls-crypt.key
remote-cert-tls server

And a simple command to start:

systemctl enable --now openvpn@client

DCO

The whole reason for this test is to see if DCO is as performant! And we aren’t using DCO just yet. So, here’s how to enable it:

#Make sure you are running a kernel version which is in the repo
apt update
apt upgrade -y
#Reboot if you installed a new kernel here, or linux-headers will not find itself
apt install -y linux-headers-$(uname -r) 
#This will compile the module with DKMS
apt install -y openvpn-dco-dkms
modprobe ovpn-dco-v2

At this point, restarting the server (or client, it works both ways) should enable DCO. Check the logs (journalctl -xeu openvpn@server) to make sure.

IPSec

Oh boy you are in for a fun ride here, we’re gonna install Strongswan (apt install -y strongswan) to get started.

StrongSwan Server

We are gonna need the server’s certificate and key from earlier, and since StrongSwan is stupid, we need to convert it to DER format (openssl ec -inform pem -outform der -in server.key -out server.der). Put the root cert into /etc/ipsec.d/cacerts/root.pem, the server’s cert in /etc/ipsec.d/certs/server.crt, and the server’s DER key in /etc/ipsec.d/private/server.der.

We’re going to completely erase /etc/ipsec.conf, you can replace it with my new one:

config setup
    charondebug="ike 1, knl 1, cfg 0"
    uniqueids=no

# our ikev2 vpn
conn ikev2-vpn
    auto=add
    compress=no
    type=tunnel
    keyexchange=ikev2
    fragmentation=yes
    forceencaps=yes
    dpdaction=clear
    dpddelay=300s
    rekey=no
    #left side (idfk why they name it left and right)
    #ip to bind to locally
    left=%any
    #this must match the SAN of the certificate
    #or, skip the @ and use an IP
    leftid=@wgmetal.palnet.net
    #leftid=2001:db8:6969::420
    leftcert=server.crt
    leftsendcert=always
    #route to tell the clients
    leftsubnet=2001:db8:7::/48
    #again, no idea why they call them left/right
    #anyway clients can be anything
    right=%any
    rightid=%any
    #eap-tls would use client certs, but we will do mschap for now
    rightauth=eap-mschapv2
    #address pool for clients
    rightsourceip=2001:db8:7:d::/64
    #you can assign DNS too if you want
    #rightdns=2620:fe::fe
    #set this to never for mschap or always for eap-tls
    rightsendcert=never
    #??
    eap_identity=%identity
    #crypto
    #yes, these are no longer recommended in favor of AES 256
    #however I tested OpenVPN with aes128gcm so I wanted to make it fair
    ike=aes128gcm16-prfsha256-ecp256
    esp=aes128gcm16-ecp256

For some reason, StrongSwan doesn’t use a file path to a key in the config file. I guess they treat the path itself as secret? Anyway, edit /etc/ipsec.secrets:

# This file holds shared secrets or RSA private keys for authentication.

# Our ECDSA private key
: ECDSA server.der

# Our admin user accounts (these are plaintext passwords)
admin : EAP "admin"
admin2 : EAP "admin"
admin3 : EAP "admin"
admin4 : EAP "admin"
admin5 : EAP "admin"
admin6 : EAP "admin"
admin7 : EAP "admin"

StrongSwan Client

The client side is a bit easier. We again need our root certificate (put it in /etc/ipsec.d/cacerts/root.pem), but not a client cert, since we are doing old fashioned usernames and passwords here.

We’re going to setup the config for the client (/etc/ipsec.conf) also:

config setup

conn ikev2-rw
    right=wgmetal.palnet.net
    # This should match the `leftid` value on your server's configuration
    rightid=%wgmetal.palnet.net
    rightsubnet=::/0
    rightauth=pubkey
    leftsourceip=%config
    leftid=admin2
    leftauth=eap-mschapv2
    eap_identity=admin2
    auto=start

And we need a ipsec.secrets file here too:

# This file holds shared secrets or RSA private keys for authentication.

# Our user account (these are plaintext passwords)
admin2 : EAP "admin"