Build a Secure IKEv2 VPN Server on Linux
Build a private IKEv2 VPN server you can use to protect your traffic from inspection by a local provider.
Introduction
This guide walks you through building a production-ready IKEv2 VPN server using strongSwan on Ubuntu/Debian. IKEv2 (Internet Key Exchange version 2) is a modern VPN protocol that offers excellent security, native support on most devices, and automatic reconnection when switching networks—perfect for mobile devices.
By the end of this guide, you'll have a VPN server with:
- Modern cryptographic algorithms (AES-256-GCM, ECC P-384)
- Let's Encrypt SSL certificates with automatic renewal
- Dual-stack IPv4 and IPv6 support
- Cloudflare DNS for privacy & malware prevention
- MSCHAPv2 authentication (compatible with all major platforms)
Prerequisites:
- A Linux server (Ubuntu 24.04 or Debian 11+ recommended) with root access
- A registered domain name pointing to your server
- Basic understanding of Linux command line and networking
Architecture Overview
Before diving in, let's understand what we're building:
- strongSwan acts as our IKEv2 server
- Let's Encrypt provides trusted SSL certificates
- UFW (Uncomplicated Firewall) handles firewall rules and NAT
- AppArmor is configured to allow strongSwan to read certificates
- Clients authenticate using username/password (EAP-MSCHAPv2)
Step 1: Install Required Packages
apt install -y certbot strongswan libstrongswan-standard-plugins \
strongswan-libcharon libcharon-extra-plugins \
libcharon-extauth-plugins
What this does:
- certbot: Let's Encrypt client for obtaining SSL certificates
- strongswan: The core IKEv2/IPsec VPN server
- libstrongswan-standard-plugins: Essential cryptographic algorithms
- libcharon-extra-plugins: Additional features including EAP authentication
- libcharon-extauth-plugins: External authentication methods like MSCHAPv2
Step 2: Configure Certbot
Create Let's Encrypt configuration file:
nano /etc/letsencrypt/cli.ini
Add the following configuration:
# Because we are using logrotate for greater flexibility, disable the
# internal certbot logrotation.
max-log-backups = 0
# Adjust interactive output regarding automated renewal
preconfigured-renewal = True
standalone = true
agree-tos = true
non-interactive = true
preferred-challenges = http
# Use ECDSA keys (smaller, faster than RSA)
key-type = ecdsa
elliptic-curve = secp384r1
# Your email for renewal notifications
email = admin@example.com
# Automatically open/close port 80 for certificate challenges
pre-hook = /sbin/ufw allow from any to any port 80 proto tcp
post-hook = /sbin/ufw delete allow from any to any port 80 proto tcp
# Reload strongSwan when certificates renew
renew-hook = /usr/sbin/ipsec reload && /usr/sbin/ipsec secrets
What this does:
- Configures certbot to run non-interactively (important for automatic renewals)
- Uses ECDSA with P-384 curve for certificates (better performance than RSA-4096)
- The
pre-hooktemporarily opens port 80 for HTTP challenges - The
post-hookcloses port 80 immediately after verification - The
renew-hookreloads strongSwan when certificates are renewed (every 60-90 days)
Step 3: Obtain SSL Certificate
certbot certonly --key-type ecdsa -d vpn.example.com
Replace vpn.example.com with your actual domain. This obtains a certificate using the HTTP-01 challenge method.
Why ECDSA? ECDSA keys with P-384 provide equivalent security to RSA-7680 but are much smaller and faster. Perfect for mobile VPN clients.
Step 4: Link Certificates to strongSwan
strongSwan expects certificates in specific directories:
ln -f -s "/etc/letsencrypt/live/vpn.example.com/cert.pem" \
/etc/ipsec.d/certs/cert.pem
ln -f -s "/etc/letsencrypt/live/vpn.example.com/privkey.pem" \
/etc/ipsec.d/private/privkey.pem
ln -f -s "/etc/letsencrypt/live/vpn.example.com/fullchain.pem" \
/etc/ipsec.d/cacerts/chain.pem
What this does:
- Creates symbolic links from Let's Encrypt certificates to strongSwan directories
cert.pem: Your server's certificateprivkey.pem: Your server's private keyfullchain.pem: Your certificate + intermediate CA certificates (needed for trust)
Using symlinks means certificate renewals automatically take effect without manual copying.
Step 5: Configure AppArmor
AppArmor is a Linux security module that restricts what programs can access. We need to allow strongSwan to read Let's Encrypt certificates:
nano /etc/apparmor.d/local/usr.lib.ipsec.charon
Add this line:
/etc/letsencrypt/archive/vpn.example.com/* r,
Then reload AppArmor:
aa-status --enabled && invoke-rc.d apparmor reload
What this does:
- Grants strongSwan read access to Let's Encrypt certificate archives
- Without this, strongSwan would be denied access by AppArmor's mandatory access control
Step 6: Enable IP Forwarding
For a VPN to work, your server needs to forward packets between the VPN tunnel and the internet:
nano /etc/sysctl.conf
Add or uncomment these lines:
net.ipv4.ip_forward = 1
net.ipv4.ip_no_pmtu_disc = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv6.conf.all.forwarding = 1
Apply the changes:
sysctl -p
What each setting does:
net.ipv4.ip_forward = 1: Enables IPv4 packet forwarding (essential for VPN)net.ipv4.ip_no_pmtu_disc = 1: Disables Path MTU Discovery (helps prevent fragmentation issues)net.ipv4.conf.all.rp_filter = 1: Enables reverse path filtering (prevents IP spoofing)net.ipv4.conf.all.accept_redirects = 0: Ignores ICMP redirects (security)net.ipv4.conf.all.send_redirects = 0: Doesn't send ICMP redirects (security)net.ipv6.conf.all.forwarding = 1: Enables IPv6 packet forwarding
Step 7: Configure UFW Firewall
Allow Packet Forwarding
nano /etc/default/ufw
Change the forward policy:
- DEFAULT_FORWARD_POLICY="DROP"
+ DEFAULT_FORWARD_POLICY="ACCEPT"
Configure NAT for IPv4
nano /etc/ufw/before.rules
Add these lines at the top of the file (before the *filter section):
# NAT for IPSec
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.1.60.0/24 -o eth0 -j MASQUERADE
COMMIT
Important: Replace eth0 with your actual network interface name. Find it with ip link show.
Configure NAT for IPv6
nano /etc/ufw/before6.rules
Add these lines at the top of the file:
# NAT for IPSec
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s fd:10:1:60::/64 -o eth0 -j MASQUERADE
COMMIT
What NAT/MASQUERADE does:
- Translates VPN client private IPs (10.1.60.0/24 and fd:10:1:60::/64) to your server's public IP
- Without this, replies from the internet wouldn't know how to reach VPN clients
- Think of it like home router NAT, but for VPN traffic
Apply Firewall Rules
# Allow IKEv2 traffic
ufw allow from any to any port 500 proto udp
ufw allow from any to any port 4500 proto udp
# Allow VPN client traffic to reach the internet
ufw allow from 10.1.60.0/24 to any
ufw allow from fd:10:1:60::/64 to any
# Limit SSH access to your home IP (optional but recommended)
ufw limit from YOUR_HOME_IP to any port 22 proto tcp
ufw limit from YOUR_IPv6_HOME_NETWORK to any port 22 proto tcp
# Enable firewall
ufw enable
What each rule does:
- Port 500/UDP: IKE negotiation (initial handshake)
- Port 4500/UDP: NAT-T (NAT traversal for clients behind NAT)
- 10.1.60.0/24 and fd:10:1:60::/64: Allow VPN clients to access any destination
- SSH limits: Using
limitinstead ofallowrate-limits connections (prevents brute force)
Security tip: Replace YOUR_HOME_IP with your actual IP address to restrict SSH access. You can find it with curl ifconfig.me.
Step 8: Configure strongSwan
Create IPsec Configuration
rm /etc/ipsec.conf
nano /etc/ipsec.conf
Add this configuration:
config setup
strictcrlpolicy=yes
uniqueids=never
conn roadwarrior
auto=add
compress=no
type=tunnel
keyexchange=ikev2
fragmentation=yes
forceencaps=yes
# Cryptographic algorithms (modern, secure)
ike=aes256gcm16-prfsha384-ecp384,aes256gcm16-prfsha256-ecp256!
esp=aes256gcm16-ecp384!
# Dead Peer Detection
dpdaction=clear
dpddelay=900s
rekey=no
# Server configuration
left=%any
leftid=@vpn.example.com
leftcert=cert.pem
leftsendcert=always
leftsubnet=::/0,0.0.0.0/0
# Client configuration
right=%any
rightid=%any
rightauth=eap-mschapv2
eap_identity=%any
rightdns=2606:4700:4700::1112,2606:4700:4700::1002,1.1.1.2,1.0.0.2
rightsourceip=fd:10:1:60::/64,10.1.60.0/24
rightsendcert=never
Configuration breakdown:
Global settings:
strictcrlpolicy=yes: Enforce certificate revocation checks (security)uniqueids=never: Allow same user on multiple devices simultaneously
Connection "roadwarrior":
auto=add: Load connection at startup but don't initiatetype=tunnel: Full tunnel mode (all traffic goes through VPN)keyexchange=ikev2: Use IKEv2 protocolfragmentation=yes: Handle large packets gracefullyforceencaps=yes: Always use UDP encapsulation (helps with NAT)
Cryptography:
ike=: Algorithms for IKE handshakeaes256gcm16: AES-256 in GCM mode (authenticated encryption)prfsha384: PRF using SHA-384ecp384: Elliptic curve P-384 (very strong)
esp=: Algorithms for data encryptionaes256gcm16-ecp384: Strong authenticated encryption with PFS
Dead Peer Detection:
dpdaction=clear: Drop connection if peer diesdpddelay=900s: Check every 15 minutesrekey=no: Don't automatically rekey (saves battery on mobile)
Server side (left):
left=%any: Accept connections on any interfaceleftid=@vpn.example.com: Server's identityleftcert=cert.pem: Server's certificateleftsubnet=::/0,0.0.0.0/0: Route all IPv4 and IPv6 traffic through VPN
Client side (right):
right=%any: Accept any client IPrightauth=eap-mschapv2: Clients authenticate with username/passwordrightdns=: Cloudflare DNS servers (privacy-focused, supports malware blocking)rightsourceip=: IP pools to assign to clients (both IPv4 and IPv6)
Why these DNS servers?
2606:4700:4700::1112, 2606:4700:4700::100, 1.1.1.2,and1.0.0.2are Cloudflare's malware-blocking DNS
Step 9: Configure User Credentials
nano /etc/ipsec.secrets
Add your credentials:
# This file holds shared secrets or RSA private keys for authentication.
# Server's private key
vpn.example.com : ECDSA "privkey.pem"
# User credentials (username : EAP "password")
user1 : EAP "StrongRandomPassword123"
user2 : EAP "AnotherSecurePassword456"
alice-iphone : EAP "7xK9mPq2NzE5rTy8"
bob-laptop : EAP "vL3nQ8wX6jH2sM9c"
Security best practices:
- Use unique, strong passwords for each user.
- Consider using device-specific accounts (easier to revoke if device is lost)
- Generate passwords with:
openssl rand -base64 12
Step 10: Start and Enable strongSwan
ipsec enable
ipsec restart
ipsec status
What this does:
ipsec enable: Configure strongSwan to start on bootipsec restart: Start the VPN server nowipsec status: Verify the "roadwarrior" connection is loaded
You should see output like:
Security Associations (0 up, 0 connecting):
roadwarrior: 10.1.60.0/24,fd:10:1:60::/64...%any IKEv2
This means the server is ready to accept connections (no clients connected yet).