l2tp ipsec vpn with npppd on openbsd



If you're even slightly security- or privacy-conscious, which in the present era of Big Data and tech oligarchs is a provident predilection, you should, if not already, be using a VPN (Virtual Private Network). And I don't mean one of the oft-advertised commercial offerings you see plastered about social media—but one that you control. Where every outgoing request that leaves your computer is tunneled through an encrypted private network owned and operated by you. Fortunately, the OpenBSD base tools make this a trivial task—and free if you already have a VPS. But even provisioning a new VM will likely be cheaper than the aforementioned commercial alternatives, with the added benefit of being more secure, and not only private in name but in practice; you can rest assured that only you and the endpoint will be cognizant of your traffic.

The following guide assumes you have a VPS running OpenBSD 6.6, but has also been tested on 6.5, 6.4, 6.3, and 6.2. If you don't already have a server, I highly recommend OpenBSD Amsterdam where a VM can be purchased for €60/year, and comes with OpenBSD preinstalled ready to go. Alternatively, Vultr also provides fast and reliable instances with OpenBSD images for US$5/month.

Enable Daemons

This L2TP over IPSec configuration uses the IKEv1 and IPSec protocols to establish a tunnel for encrypted traffic between the client and server. This requires the isakmpd keying and npppd tunneling daemons, which can be enabled with rcctl as a privileged user.

# rcctl enable ipsec isakmpd npppd
# rcctl set isakmpd flags -K

Alternatively, open /etc/rc.conf.local to add the following lines:


Before configuring these daemons, the system needs some tweaking to facilitate the intended use case.

Kernel Configuration

In order for the machine to act as a VPN gateway and perform traffic routing so that packets can travel between network interfaces, IP forwarding and PPP frame handling need to be enabled to forward IP packets in-kernel. In addition, if the VPS is on a slow link but isn't resource constrained, consider enabling the IP Compression Protocol (IPComp) to reduce datagram size and allow higher throughput. To make these changes persistent across reboots, add the following to /etc/sysctl.conf.


If there's no intent to use IPv6 or the node doesn't have limited bandwidth, omit the inet6.ip6 and inet.ipcomp lines, respectively.

Activate these changes now, by executing the following one liner as a privileged user:

# for c in $(cat /etc/sysctl.conf); do sysctl $c; done
net.inet.ip.forwarding: 0 -> 1
net.inet6.ip6.forwarding: 0 -> 1
net.pipex.enable: 0 -> 1
net.inet.ipcomp.enable: 0 -> 1

Alternatively, two at a time:

# sysctl net.inet.ip.forwarding=1; sysctl net.pipex.enable=1
net.inet.ip.forwarding: 0 -> 1
net.pipex.enable: 0 -> 1
# sysctl net.inet6.ip6.forwarding=1; sysctl net.inet.ipcomp.enable=1
net.inet6.ip6.forwarding: 0 -> 1
net.inet.ipcomp.enable: 0 -> 1

With the system setup and requisite services enabled for IPSec, it's now time to configure the VPN.

Configure isakmpd

To setup a tunnel with a pre-shared key for authentication, isakmpd needs to be configured by adding a few simple lines to /etc/ipsec.conf. Substitute SECRETKEY for the desired pre-shared key, which will need to be provided by all connecting clients.

ike passive esp tunnel from "VPS_IP_ADDRESS" to any \
     main group "modp1024" \
     quick group "modp1024" \
     psk "SECRETKEY"

Due to the sensitive nature of its contents, ipsec requires that ipsec.conf is neither group nor world read/writable; so lock down permissions to root only access. Then parse the file to verify it's syntactically correct.

# chmod 600 /etc/ipsec.conf
# ipsecctl -vnf /etc/ipsec.conf
C set [Phase 1]:Default=peer-default force
C set [peer-default]:Phase=1 force
C set [peer-default]:Authentication=SECRETKEY force
C set [peer-default]:Configuration=phase1-peer-default force
C set [phase1-peer-default]:EXCHANGE_TYPE=ID_PROT force
C add [phase1-peer-default]:Transforms=phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024 force
C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:AUTHENTICATION_METHOD=PRE_SHARED force
C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:HASH_ALGORITHM=SHA force
C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:ENCRYPTION_ALGORITHM=AES_CBC force
C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:KEY_LENGTH=128,128:256 force
C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:GROUP_DESCRIPTION=MODP_1024 force
C set [phase1-transform-peer-default-PRE_SHARED-SHA-AES128-MODP_1024]:Life=LIFE_MAIN_MODE force
C set [from-]:Phase=2 force
C set [from-]:ISAKMP-peer=peer-default force
C set [from-]:Configuration=phase2-from- force
C set [from-]:Local-ID=from- force
C set [from-]:Remote-ID=to- force
C set [phase2-from-]:EXCHANGE_TYPE=QUICK_MODE force
C set [phase2-from-]:Suites=phase2-suite-from- force
C set [phase2-suite-from-]:Protocols=phase2-protocol-from- force
C set [phase2-protocol-from-]:PROTOCOL_ID=IPSEC_ESP force
C set [phase2-protocol-from-]:Transforms=phase2-transform-from- force
C set [phase2-transform-from-]:TRANSFORM_ID=AES force
C set [phase2-transform-from-]:KEY_LENGTH=128,128:256 force
C set [phase2-transform-from-]:ENCAPSULATION_MODE=TUNNEL force
C set [phase2-transform-from-]:AUTHENTICATION_ALGORITHM=HMAC_SHA2_256 force
C set [phase2-transform-from-]:GROUP_DESCRIPTION=MODP_1024 force
C set [phase2-transform-from-]:Life=LIFE_QUICK_MODE force
C set [from-]:ID-type=IPV4_ADDR force
C set [from-]:Address= force
C set [to-]:ID-type=IPV4_ADDR_SUBNET force
C set [to-]:Network= force
C set [to-]:Netmask= force
C add [Phase 2]:Passive-Connections=from-

The ipsec.conf(5) man page comprehensively explains the file format and possible values, which can depend on the connecting client. The above values are applicable to macOS and iOS clients, which require the modp1024 security group. Apart from that and the passive mode, which instructs isakmpd to wait for connection requests, the default options are used. Consult the man page and official documentation for settings suitable to other peers.

Configure npppd

User authentication to the VPN is handled by the new Point-to-Point Protocol daemon npppd. Granting access to individual users requires adding two lines for each user to the /etc/npppd/npppd-users file; for example, to add users johnnyb and tommyg with the passwords twogoatsonewolf and onegoatnone, respectively:


Note that all users will need to provide the abovementioned pre-shared key detailed in the ipsec.conf file, and their unique password listed in the npppd-users file.

That's the extent of configuration for the services managing authentication and packet forwarding of the virtual private network. Changes can be made to npppd by editing /etc/npppd/npppd.conf, and the npppd.conf(5) man page is a concise resource for configuring the PPP daemon. For this particular use case, however, the defaults suffice; a pppx(4) interface and an IP address in the subnet is allocated for each session.

Configure pf

With the VPN now configured, all that remains server side is reconfiguring pf to allow IPSec connections and packet forwarding, and handle the network address translation of VPN traffic across disparate address spaces. The following /etc/pf.conf provides a minimal configuration to satisfy these requirements (with only rules pertinent to the VPN shown).

ext_if = "vio0"
vpn_if = "pppx"
vpn_net = ""

# allow ESP protocol on public interface
pass in on $ext_if proto esp

# allow isakmpd UDP traffic through the public interface on ports 500 and 4500
pass in on $ext_if proto udp to port { isakmp, ipsec-nat-t }

# filter all IPSec traffic on the enc interface
pass on enc0 keep state (if-bound)

# allow all trafic in on and out to the VPN network
pass on $vpn_if from $vpn_net
pass on $vpn_if to $vpn_net

# nat VPN traffic going out on the public interface with the public IP
match out on $ext_if from $vpn_net nat-to ($ext_if) set prio (3,4)

Before reloading the new ruleset, verify the syntax is valid.

# pfctl -vnf /etc/pf.conf
ext_if = "vio0"
vpn_if = "pppx"
vpn_net = ""
pass in on vio0 proto udp from any to any port = 500
pass in on vio0 proto udp from any to any port = 4500
pass in on vio0 proto esp all
pass on enc0 all flags S/SA keep state (if-bound)
pass on pppx inet from to any flags S/SA
pass on pppx inet from any to flags S/SA
match out on vio0 inet from to any set (prio(3, 4)) nat-to (vio0) round-robin
# pfctl -f /etc/pf.conf

With pf reconfigured and reloaded, it's time to start all the daemons.

Starting the VPN

OpenBSD's rcctl makes this a cinch, and having previously enabled and configured the requisite services, they should now start without complaint.

# rcctl start ipsec isakmpd npppd

Lastly, load the IPSec configuration file with ipsecctl.

# ipsecctl -f /etc/ipsec.conf

Now that the VPN is up and running, switch over to the macOS client for some basic configuration before connecting.

macOS Setup

This can be done entirely through the macOS system settings graphical user interface.

  1. Select the Network icon in System Preferences.

    Open Network preferences
    Figure 1: Open Network preferences
  2. Press the bottom left + icon.

    Add new connection
    Figure 2: Add new connection
  3. From the drop down menus, select the VPN interface, L2TP over IPSec VPN type, and provide a descriptive service name. Press Create.

    L2TP over IPSec VPN
    Figure 3: L2TP over IPSec VPN
  4. Enter the IP address (or FQDN equivalent) from ipsec.conf and the username from npppd-users in the Server Address and Account Name fields, respectively. Press Authentication Settings…

    Enter IP and username
    Figure 4: Enter IP and username
  5. Input the npppd-users password and the ipsec.conf psk value in the Password and Shared Secrect fields, respectively. Press OK.

    Enter password and pre-shared key
    Figure 5: Enter password and pre-shared key
  6. Select Advanced…, then check Send all traffic over VPN connection and press OK.

    Advanced options: send all traffic over VPN
    Figure 6: Advanced options: send all traffic over VPN
  7. Press Apply in the bottom right corner, and then Connect.

    Apply settings and connect!
    Figure 7: Apply settings and connect!

The status should now show Connected with the allocated private IP address shown underneath. To the right, two progress bars that turn green when traffic is being sent or received will be displayed (see above screenshot).

On the server side, once a VPN session is established, ifconfig will show the connected client on the pppx interface. Each session will be assigned its own interface; that is, if multiple users are connected at the same time, ifconfig will display pppx0, pppx1, …, pppxN.

# ifconfig
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 32768
	index 3 priority 0 llprio 3
	groups: lo
	inet6 ::1 prefixlen 128
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x3
	inet netmask 0xff000000
	lladdr ab:cd:ef:a1:b2:c3
	index 1 priority 0 llprio 3
	groups: egress
	media: Ethernet autoselect
	status: active
	inet netmask 0xffffff00 broadcast
	inet6 aaaa:bbbb::cccc:dddd:eeee%vio0 prefixlen 64 scopeid 0x1
	inet6 a1a1:b2b2:c3c3::ddd:eee prefixlen 64
enc0: flags=0<>
	index 2 priority 0 llprio 3
	groups: enc
	status: active
pflog0: flags=141<UP,RUNNING,PROMISC> mtu 33136
	index 4 priority 0 llprio 3
	groups: pflog
pppx0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1360
	description: johnnyb
	index 7 priority 0 llprio 3
	groups: pppx
	inet --> netmask 0xffffffff

iOS Setup

iOS makes connecting to the VPN from an iPhone or iPad just as trivial.

  1. Tap the Settings icon, enter General, then scroll down and select VPN.

    Open VPN settings
    Figure 8: Open VPN settings
  2. Tap Add VPN Configuration…

    Add new VPN
    Figure 9: Add new VPN
  3. On the Add Configuration screen, do the following:

    • change Type to L2TP;
    • provide a Description;
    • specify the Server IP address (or FQDN equivalent) from ipsec.conf;
    • enter Account username and Password from npppd-users;
    • input the Secret pre-shared key from ipsec.conf;
    • enable Send All Traffic; then
    • tap Done on the top right.
    Enter VPN settings
    Figure 10: Enter VPN settings
  4. Toggle Status to connect to the VPN.

    Connect to VPN
    Figure 11: Connect to VPN


That concludes this short guide on how to establish an L2TP over IPSec VPN on OpenBSD for macOS and iOS clients. The base tools provided on every default install of OpenBSD make this a trivial undertaking, and the native macOS and iOS network tools are also intuitively simple. While this enables a more secure browsing experience by transmitting all traffic through an encrypted tunnel, DNS requests are still being resolved by Google's nameservers. For a truly private network, follow this guide to setup the local resolving name server unbound on OpenBSD, then check back soon for an upcoming article on how to route all DNS requests through the VPN to be resolved by unbound. This will include how to configure DNSCrypt as another alternative solution for preventing DNS leaks.

Further Reading

Tags: macos openbsd
send comments to mark AT jamsek DOT net

Generated by emacs org mode

Copyright © 2023 Mark Jamsek