OpenBSD IKEv2, Let's Encrypt and iOS/Windows
— OpenBSD, IPsec, IKEv2, VPN, acme, letsencrypt, iOS, Windows, Unbound — 4 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.
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).
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 httpdThen acquire the certificate.
# acme-client -v vpn.yourdomain.comOpenIKED
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.certAnd 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.pemRemember 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 -eAppend 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 ikedWe are now ready to configure OpenIKED itself. Replace the credentials with usernames and passwords of your choice.
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.
inet 192.168.90.1 0xffffff00And restart the network.
# sh /etc/netstart enc0Once the interface is configured, enable and start our OpenIKED daemon.
# chmod 0600 /etc/iked.conf# rcctl enable iked# rcctl restart ikedPacket 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.
# SET DEFAULT POLICYblock allset skip on lo
# ALLOW OUTBOUND CONNECTIONSpass out on egress
# ALLOW INBOUND SSHpass 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 IPSECpass 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 TRAFFICpass on enc0 tagged ROADW
# NAT TAGGED TRAFFICmatch out on egress tagged ROADW nat-to (egress:0)And load the firewall rules.
# pfctl -f /etc/pf.confThen allow IP forwarding.
# echo 'net.inet.ip.forwarding=1' >> /etc/sysctl.conf# sysctl net.inet.ip.forwarding=1DNS
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.
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.comEnable and restart the DNS daemon.
# rcctl enable unbound# rcctl restart unboundWith 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 saCredits
- The OpenBSD team for making their wonderful OS.
- Reyk Floeter for making OpenIKED.
- Let's Encrypt for making certificates easy.
- Fazal Majid for making helpful script.