Skip to content

OpenBSD IKEv2, Let's Encrypt and iOS/Windows

OpenBSD, IPsec, IKEv2, VPN, acme, letsencrypt, iOS, Windows, Unbound4 min read

Setting up IKEv2 on OpenBSD is remarkably straightforward--everything you need is included in the base system and easy to configure. By using Let's Encrypt for certificate management, we avoid the complexity of handling certificate authorities and manual distribution. Since IKEv2 is natively supported on all major platforms (unlike WireGuard), client setup requires no additional software or administrative privileges. The only prerequisite, aside from a working OpenBSD installation (version 7.6 was used here), is a domain name such as vpn.yourdomain.com.

Let's Encrypt

Begin by configuring acme-client, OpenBSD's built-in ACME client for automated certificate management. Installed by default with the operating system, it offers a simple and reliable way to obtain and renew Let's Encrypt certificates.

/etc/acme-client.conf
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
domain vpn.yourdomain.com {
domain key "/etc/ssl/private/vpn.key"
domain chain certificate "/etc/ssl/vpn-chain.cert"
domain certificate "/etc/ssl/vpn.cert"
sign with letsencrypt
}

The certificate acquisition process requires validation of domain ownership--essentially proving that you control the domain you're requesting a certificate for. OpenBSD's acme-client supports the HTTP-01 challenge method, where a specific file is placed in a directory that can be accessed via a web server. To accomplish this, we need to configure OpenBSD's httpd (the built-in web server).

/etc/httpd.conf
prefork 1
server "example.com" {
listen on * port 80
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location * {
block return 302 "https://$HTTP_HOST$REQUEST_URI"
}
}

Enable httpd and restart the service.

# rcctl enable httpd
# rcctl restart httpd

Then acquire the certificate.

# acme-client -v vpn.yourdomain.com

OpenIKED

We can now configure OpenIKED to use the newly issued Let's Encrypt certificate.

# cp /etc/ssl/private/vpn.key /etc/iked/private/local.key
# cp /etc/ssl/vpn.cert /etc/iked/certs/vpn.cert
# cp /etc/ssl/vpn-chain.cert /etc/iked/ca/vpn-chain.cert

And install the trusted root CA certificate for Let's Encrypt located on the official website.

# ftp -o /etc/iked/ca/isrgrootx1.pem https://letsencrypt.org/certs/isrgrootx1.pem

Remember Let's Encrypt certificates last only 90 days, so we have to update our certificate periodically. To automate certificate renewal, add the following cron job. This will check and renew the certificate periodically:

# crontab -e

Append this line:

~ * * * * /usr/sbin/acme-client vpn.yourdomain.com && /bin/cp /etc/ssl/vpn.cert /etc/iked/certs/ && /bin/cp /etc/ssl/vpn-chain.cert /etc/iked/ca/ && /usr/sbin/rcctl reload iked

We are now ready to configure OpenIKED itself. Replace the credentials with usernames and passwords of your choice.

/etc/iked.conf
user 'username1' 'password1'
user 'username2' 'password2'
ikev2 "remote_dynamic_client" passive esp \
from 0.0.0.0/0 to dynamic \
peer any \
srcid vpn.yourdomain.com \
eap "mschap-v2" \
config address 192.168.90.128/25 \
config name-server 192.168.90.1 \
tag "ROADW"

Now create a virtual network device that will handle IPsec traffic.

/etc/hostname.enc0
inet 192.168.90.1 0xffffff00

And restart the network.

# sh /etc/netstart enc0

Once the interface is configured, enable and start our OpenIKED daemon.

# chmod 0600 /etc/iked.conf
# rcctl enable iked
# rcctl restart iked

Packet Filter

At this point it should be possible to establish VPN connection and ping the server, but the traffic won't flow through the router. We need to allow traffic to be NATed.

/etc/pf.conf
# SET DEFAULT POLICY
block all
set skip on lo
# ALLOW OUTBOUND CONNECTIONS
pass out on egress
# ALLOW INBOUND SSH
pass in on egress proto tcp from any to self port ssh
# ALLOW INBOUND HTTP TRAFFIC (REQUIRED BY ACME HTTP-01)
pass in on egress proto tcp from any to self port http
# ALLOW INBOUND IPSEC
pass in on egress proto udp from any to self port {isakmp, ipsec-nat-t}
pass in on egress proto esp from any to self
# ACCEPT TAGGED TRAFFIC
pass on enc0 tagged ROADW
# NAT TAGGED TRAFFIC
match out on egress tagged ROADW nat-to (egress:0)

And load the firewall rules.

# pfctl -f /etc/pf.conf

Then allow IP forwarding.

# echo 'net.inet.ip.forwarding=1' >> /etc/sysctl.conf
# sysctl net.inet.ip.forwarding=1

DNS

To complete the setup, I also suggest setting up a local DNS server. This helps minimize SSL mismatch errors when connecting to geo-distributed services.

/var/unbound/etc/unbound.conf
server:
interface: 127.0.0.1
interface: 192.168.90.1
do-ip6: no
access-control: 0.0.0.0/0 refuse
access-control: 127.0.0.0/8 allow
access-control: 192.168.90.0/24 allow
tls-cert-bundle: "/etc/ssl/cert.pem"
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 1.0.0.1@853#cloudflare-dns.com

Enable and restart the DNS daemon.

# rcctl enable unbound
# rcctl restart unbound

With these configurations in place, your OpenBSD system is now ready to accept IKEv2 VPN connections secured with a Let's Encrypt certificate. You can proceed with configuring your iOS or Windows clients to connect. Set up the client (iOS, Mac, Windows) using a built-in interface using the credentials we configured in /etc/iked.conf, no extra software is required. Specify vpn.yourdomain.com as a Remote ID on Apple devices.

Established connections can be checked with the following command:

$ ikectl show sa

Credits