IKEv2 VPN with Ubuntu and Apple

How to build an IKEv2 VPN server for Apple devices.

IKEv2 VPN with Ubuntu and Apple
Photo by Aaron Burden / Unsplash

The goal of this configuration is minimalism. Using a standard Ubuntu 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.

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.

mkdir /etc/letsencrypt

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/iptables -I INPUT -p tcp --dport 80 -j ACCEPT
post-hook = /sbin/iptables -D INPUT -p tcp --dport 80 -j ACCEPT
pre-hook = /sbin/ip6tables -I INPUT -p tcp --dport 80 -j ACCEPT
post-hook = /sbin/ip6tables -D INPUT -p tcp --dport 80 -j ACCEPT
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/remote.wildarcher.net/cert.pem"    /etc/ipsec.d/certs/cert.pem
ln -f -s "/etc/letsencrypt/live/remote.wildarcher.net/privkey.pem" /etc/ipsec.d/private/privkey.pem
ln -f -s "/etc/letsencrypt/live/remote.wildarcher.net/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/remote.wildarcher.net/* 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 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 limit from any to any port 22 proto tcp
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

This will prevent SSH brute-force guessing. 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>

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'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.

With this configuration, I use Shortcuts and make a new Automation that happens when I leave or connect to certain Wi-Fi networks. That allows me to disconnect and reconnect the VPN as needed so my traffic remains private.