IKEv2 VPN with Ubuntu and Apple
How to build an IKEv2 VPN server for Apple devices.
The goal of this configuration is minimalism. Using a standard Ubuntu 24.04 server and a configuration profile, you can use the built in VPN services on iOS, iPadOS, and macOS. This is an IPv4 & IPv6 IPSec IKEv2 VPN configuration. Let's get started.
For context, my Ubuntu Server I'm using is 24.04 and located on a VPS with one network interface that is configured for IPv4 and IPv6. I already have my DNS entry for this server on both IPv4 and IPv6.
Lockdown
Let's first lock down the VPS you just created.
Start by limiting SSH connections to prevent brute forcing.
ufw limit from [YOUR IPv4] to any port 22 proto tcp
ufw limit from [YOUR IPv6] to any port 22 proto tcp
ufw enable
If you need a more detailed explanation, you can read the post below to fill in the gaps.
Next, add your SSH Public Key to the server. If you don't know how to generate SSH keys, please check out the post below.
mkdir ~/.ssh
nano ~/.ssh/authorized_keys
Then copy and paste your public key.
Now we have to edit SSH to only allow key authentication.
nano /etc/ssh/sshd_config
Find the line talking about PasswordAuthentication
and remove the #
if it's there and set the value to no
.
Restart the service so the configuration takes.
systemctl restart ssh
Now change the root password with passwd
, if you're logged in as root.
Packages
Install the required packages.
apt install -y certbot strongswan libstrongswan-standard-plugins strongswan-libcharon libcharon-extra-plugins libcharon-extauth-plugins
Certificate
Create the Let's Encrypt configuration profile to be used for the first certificate and renewal.
nano /etc/letsencrypt/cli.ini
standalone = true
agree-tos = true
non-interactive = true
preferred-challenges = http
rsa-key-size = 4096
email = [YOUR EMAIL]
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
renew-hook = /usr/sbin/ipsec reload && /usr/sbin/ipsec secrets
The purpose of the pre-hook
and post-hook
is so you don't keep 80/TCP open all the time. Just open it for the request & renewal then shut it afterwards.
Generate the certificate
certbot certonly --key-type rsa -d [YOUR DOMAIN]
Link the new certificate to where StrongSwan will find it.
ln -f -s "/etc/letsencrypt/live/[YOUR DOMAIN]/cert.pem" /etc/ipsec.d/certs/cert.pem
ln -f -s "/etc/letsencrypt/live/[YOUR DOMAIN]/privkey.pem" /etc/ipsec.d/private/privkey.pem
ln -f -s "/etc/letsencrypt/live/[YOUR DOMAIN]/chain.pem" /etc/ipsec.d/cacerts/chain.pem
AppArmor
Create the configuration file
nano /etc/apparmor.d/local/usr.lib.ipsec.charon
Enter this in the configuration file.
/etc/letsencrypt/archive/[YOUR DOMAIN]/* r,
Then run AppArmor to use it.
aa-status --enabled && invoke-rc.d apparmor reload
Networking
We're going to use the built in ufw
and a few configuration changes to /etc/sysctl.conf
to make this work.
sysctl.conf
nano /etc/sysctl.conf
Enter this at the end of the configuration profile.
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
Now implement the changes
sysctl -p
/etc/default/ufw
In the /etc/default/ufw
configuration file we have to change the forward policy. You'll find this line: DEFAULT_FORWARD_POLICY="DROP"
and you will need to change it to DEFAULT_FORWARD_POLICY="ACCEPT"
.
/etc/ufw/before.rules
In the /etc/ufw/before.rules
file you will need to add this after COMMIT
.
# NAT for IPSEC
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s [IPv4 VPN NETWORK] -o [INTERFACE] -j MASQUERADE
# don't delete the 'COMMIT' line or these rules won't be processed
COMMIT
The [IPv4 VPN NETWORK]
needs to be any RFC1918 network that is not in use. I'd recommend staying away from 192.168.1.0/24 or similar common networks.
The [INTERFACE]
is whatever the common name for your network interface is for your VPN. If you don't know the common name of your network interface you need to use, you can run ls /sys/class/net
and look at the output. It's not going to be lo
and if you're using a VPS there might only be one more option. Usually it's eth0
, ens6
or enp1s0
.
/etc/ufw/before6.rules
In the /etc/ufw/before6.rules
you will need to add this after COMMIT
.
# NAT Rules for IPSec
*nat
:POSTROUTING ACCEPT [0:0]
# Forward traffic through eth0 - Change to match you out-interface
-A POSTROUTING -s [IPv6 VPN NETWORK] -o [INTERFACE] -j MASQUERADE
# don't delete the 'COMMIT' line or these rules won't be processed
COMMIT
The [IPv6 VPN NETWORK]
needs to be a Unique Local Address (ULA) network with /64 per RFC 4193. For example fd:1:2:3::/64
.
UFW commands
Here is where we'll leave the ip[6]tables type commands and enter the commands to ufw.
ufw allow from any to any port 500 proto udp
ufw allow from any to any port 4500 proto udp
ufw allow from [IPv4 VPN NETWORK] to any
ufw allow from [IPv6 VPN NETWORK] to any
It will allow IKEv2 traffic. And allow your VPN traffic anywhere.
IKEv2 Configuration
We will need to configure /etc/ipsec.conf
to tell it how to configure the IKEv2 tunnel. Then we'll need to configure our usernames and passwords in /etc/ipsec.secrets
file.
/etc/ipsec.conf
You will need to enter this and replace the obvious.
config setup
strictcrlpolicy=yes
uniqueids=never
conn roadwarrior
auto=add
compress=no
type=tunnel
keyexchange=ikev2
fragmentation=yes
forceencaps=yes
ike=aes256gcm16-prfsha384-ecp384,aes256gcm16-prfsha256-ecp256!
esp=aes256gcm16-ecp384!
dpdaction=clear
dpddelay=900s
rekey=no
left=%any
leftid=@[YOUR DOMAIN]
leftcert=cert.pem
leftsendcert=always
leftsubnet=::/0,0.0.0.0/0
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=[IPv6 VPN NETWORK],[IPv4 VPN NETWORK]
rightsendcert=never
I am using Cloudflare's Malware-Prevention DNS.
/etc/ipsec.secrets
This is the configuration that you enter usernames and passwords.
[YOUR DOMAIN] : RSA "privkey.pem"
[USER1] : EAP "[PASSWORD]"
[USER2] : EAP "[PASSWORD]"
You can add as many users as you'd like. You should be entering different per-device-accounts if you're going to use this VPN with multiple devices.
Now we make everything take with a restart.
ipsec restart
If you want to see if anything is connected you can issue the following command
ipsec status
.
Configuration Profile
This is how you configure the built-in VPN settings for all apple devices. You're going to need to replace a couple parts with a truly unique string of characters. To do this I usually enter the command uuidgen
and I get the HEX string I can use in these spots.
To make it work on macOS 15 (Sequoia) you will need to make sure each [UNIQUE ID]
section is different. In this configuration profile, you'll have to generate four of them.
The contents of this section below have to be changed replacing [YOUR DOMAIN]
and [UNIQUE ID]
to be your own. After that, save it as vpn.mobileconfig
. The vpn
part doesn't matter, but you must end it with .mobileconfig
.
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'>
<plist version='1.0'>
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>IKEv2</key>
<dict>
<key>AuthenticationMethod</key>
<string>None</string>
<key>ChildSecurityAssociationParameters</key>
<dict>
<key>EncryptionAlgorithm</key>
<string>AES-256-GCM</string>
<key>IntegrityAlgorithm</key>
<string>SHA2-384</string>
<key>DiffieHellmanGroup</key>
<integer>20</integer>
<key>LifeTimeInMinutes</key>
<integer>1440</integer>
</dict>
<key>DeadPeerDetectionRate</key>
<string>Medium</string>
<key>DisableMOBIKE</key>
<integer>0</integer>
<key>DisableRedirect</key>
<integer>0</integer>
<key>EnableCertificateRevocationCheck</key>
<integer>0</integer>
<key>EnablePFS</key>
<true/>
<key>ExtendedAuthEnabled</key>
<true/>
<key>IKESecurityAssociationParameters</key>
<dict>
<key>EncryptionAlgorithm</key>
<string>AES-256-GCM</string>
<key>IntegrityAlgorithm</key>
<string>SHA2-384</string>
<key>DiffieHellmanGroup</key>
<integer>20</integer>
<key>LifeTimeInMinutes</key>
<integer>1440</integer>
</dict>
<key>RemoteAddress</key>
<string>[YOUR DOMAIN]</string>
<key>RemoteIdentifier</key>
<string>[YOUR DOMAIN]</string>
<key>UseConfigurationAttributeInternalIPSubnet</key>
<integer>0</integer>
</dict>
<key>IPv4</key>
<dict>
<key>OverridePrimary</key>
<integer>1</integer>
</dict>
<key>PayloadDescription</key>
<string>Configures VPN settings</string>
<key>PayloadDisplayName</key>
<string>[YOUR DOMAIN]</string>
<key>PayloadIdentifier</key>
<string>com.apple.vpn.managed.[UNIQUE ID]</string>
<key>PayloadType</key>
<string>com.apple.vpn.managed</string>
<key>PayloadUUID</key>
<string>[UNIQUE ID]</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>Proxies</key>
<dict>
<key>HTTPEnable</key>
<integer>0</integer>
<key>HTTPSEnable</key>
<integer>0</integer>
</dict>
<key>UserDefinedName</key>
<string>[YOUR DOMAIN]</string>
<key>VPNType</key>
<string>IKEv2</string>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>IKEv2 VPN configuration ([YOUR DOMAIN])</string>
<key>PayloadIdentifier</key>
<string>[UNIQUE ID]</string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>[UNIQUE ID]</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
If you want this to be a persistent always-on VPN you can add the following before <key>RemoteAddress</key>
<key>OnDemandEnabled</key>
<integer>1</integer>
<key>OnDemandRules</key>
<array>
<dict>
<key>Action</key>
<string>Connect</string>
</dict>
</array>
Now that you have the .mobileconfig
file created, you can get it setup on any apple device.
For macOS, I just double click on it. Then I go to Settings and click on "Profile Downloaded" to install it. Double click on the configuration profile and click "Install".
After installing it on macOS, you will have to define the login. Go to Settings > VPN > and click on the little "i" for your connection. Under "Authentication" click on "Certificate" and change it to "Username". Enter your username and password.
Now you can turn it on and off and use it. In macOS you can also enable it in the menu bar (control center) by going to Settings > Control Center and going to "Menu Bar Only" section and changing VPN to "Show in Menu Bar."
For iOS or iPadOS, we AirDrop it or email it to ourselves and open in the email. You can also now open it from your iCloud Drive directly in the Files app. You'll be prompted to go to Settings. You'll see where it says "Profile Downloaded" near the top of settings. Choose to install it and follow the prompts. You will be asked for your device passcode, your username, and your password for the VPN.
Complete
Now you should be good to use the built-in VPN client and not need to install another app on the phone to secure your traffic.