Synopsis #
Build site-to-site virtual private networks (VPNs) and encrypted host-to-host tunnels with IKEv2 using the base system iked(8) and the kernel IPsec stack ipsec(4) , with NAT traversal, protected-traffic selectors, and diagnostic tools. A complete road-warrior deployment using the in-kernel wg(4) interface is maintained separately. Runtime management uses ikectl(8) , packet filtering is handled by pf.conf(5) and pfctl(8) , and link inspection uses tcpdump(8) . The interface for inspecting cleartext IPsec traffic is enc(4) .
Use these patterns to interconnect sites over untrusted networks, to publish internal services across providers securely, or to replace legacy tunnels with modern cryptography.
Design Considerations #
- Selectors and scope. Define traffic by networks (for example,
10.0.0.0/24to10.20.0.0/24) rather thanany. Keep selectors minimal and symmetric on both peers. - Authentication. Pre-shared key (PSK) is simplest for site-to-site. Certificates scale better and are preferred where third-party trust or revocation is needed.
- NAT traversal. Permit UDP ports 500 and 4500 and protocol ESP on WAN. IKEv2 NAT-T is automatic when address translation is detected.
- Routing. Enable only the address-family forwarding required by the protected networks, then add the corresponding static or dynamic routes.
- Performance. Prefer AES-GCM proposals to offload integrity to the cipher. Keep MTU and MSS in mind where encapsulation crosses constrained links.
- Operations. Ensure time sync on all peers (for example, with ntpd(8) ). Log IKE and PF events during rollout.
Configuration #
The examples assume:
- Site A WAN:
198.51.100.10, LAN:10.0.0.0/24 - Site B WAN:
203.0.113.10, LAN:10.20.0.0/24
Adjust interface names and addresses to match the deployment.
Baseline system settings (both sites) #
doas sh <<'ROOT'
printf '%s\n' 'net.inet.ip.forwarding=1' >> /etc/sysctl.conf
# Persist IPv4 forwarding for the IPv4 protected networks
sysctl net.inet.ip.forwarding=1
# Apply IPv4 forwarding immediately
ROOT
The example carries IPv4 only. Do not enable IPv6 forwarding without an IPv6 routing and PF policy.
PF allowances for IKEv2 and ESP (both sites) #
Permit IKE (UDP 500/4500) and ESP on the WAN, and allow traffic between the protected subnets on the inside. Syntax is defined in pf.conf(5) .
## /etc/pf.conf - minimal allowances for IKEv2/IPsec
set skip on lo
wan = "em0" # adjust
lan_net = "{ 10.0.0.0/24, 10.20.0.0/24 }"
block all
# IKE and IPsec on the WAN
pass in on $wan proto udp to ($wan) port { 500, 4500 } keep state
pass in on $wan proto esp keep state
pass out on $wan proto udp from ($wan) to any port { 500, 4500 } keep state
pass out on $wan proto esp from ($wan) keep state
# Merge LAN ingress/egress allowances for the protected subnets into the main policy.
# Tunnel-mode outer headers between the two example gateways
pass on enc0 proto ipencap from 198.51.100.10 to 203.0.113.10 keep state (if-bound)
pass on enc0 proto ipencap from 203.0.113.10 to 198.51.100.10 keep state (if-bound)
# Permit protected subnets before encapsulation and after decryption
pass on enc0 from 10.0.0.0/24 to 10.20.0.0/24 keep state (if-bound)
pass on enc0 from 10.20.0.0/24 to 10.0.0.0/24 keep state (if-bound)
Use the actual gateway addresses, including those seen on enc0 when NAT is involved. Keep every enc0 state bound to that interface so it cannot allow unencrypted WAN traffic after an IPsec SA expires. Allow the tunnel headers and protected inner packets separately. Limit outgoing WAN UDP to the gateway address and IKE/NAT-T ports.
Reload and confirm with pfctl(8) .
doas pfctl -f /etc/pf.conf
doas pfctl -sr | egrep 'udp .* (500|4500)|proto esp|enc0'
Site-to-site IKEv2 with PSK #
Configuration is in iked.conf(5)
. One side initiates (active), the other listens (passive). Proposals below use AES-GCM.
Site A (initiator) #
## /etc/iked.conf - Site A
ikev2 "s2s-a2b" active esp from 10.0.0.0/24 to 10.20.0.0/24 \
peer 203.0.113.10 \
ikesa enc aes-256-gcm prf hmac-sha2-256 group modp2048 \
childsa enc aes-256-gcm \
psk "change-this-shared-secret"
Site B (responder) #
Set peer to Site A’s WAN address. The from and to networks identify the traffic the tunnel protects.
## /etc/iked.conf - Site B
ikev2 "s2s-b2a" passive esp from 10.20.0.0/24 to 10.0.0.0/24 \
peer 198.51.100.10 \
ikesa enc aes-256-gcm prf hmac-sha2-256 group modp2048 \
childsa enc aes-256-gcm \
psk "change-this-shared-secret"
Enable and start iked(8) on both peers with rcctl(8) :
doas chmod 600 /etc/iked.conf
doas iked -n && doas rcctl enable iked
# Start at boot
doas rcctl start iked
# Launch now (initiator will dial the responder)
If either peer is behind NAT, IKEv2 will encapsulate ESP in UDP 4500 automatically (NAT-T). Ensure the upstream allows those ports.
WireGuard Deployments #
Configure a Road-Warrior WireGuard VPN covers keys, persistent interfaces, allowed IP ranges, routes, PF rules, and the return path. Use that guide for the complete client and gateway setup.
Verification #
- IKEv2 state and flows with ikectl(8) :
doas ikectl show sa
# IKE_SA and CHILD_SA state, SPIs, lifetimes
doas ipsecctl -sf
# Traffic selectors currently installed
- Path testing:
ping -n -c 3 10.20.0.1
# Test the protected path from Site A to Site B
- PF and routes:
doas pfctl -vvsr | egrep 'udp .* (500|4500)|proto esp'
# Confirm IKEv2 and ESP allowances
netstat -rn | grep '10\.20\.0\.'
# Confirm the protected-network route
Troubleshooting #
- No IKE negotiation. Verify UDP 500/4500 reachability and that the responder is in
passivemode. Inspect logs in/var/log/daemonforiked. - Selector mismatch. If one side uses
10.0.0.0/24 to 10.20.0.0/24and the other uses10.0.0.0/24 to 10.0.0.0/24, CHILD_SA will not install. Alignfrom/tonetworks. Checkipsecctl -sf. - NAT or path asymmetry. Ensure upstreams carry UDP 4500 unchanged and that return traffic follows the same egress. For IPsec,
enc0rules should allow the protected subnets. - MTU black holes. If large transfers stall, clamp MSS on the plaintext IPsec path during testing in PF (
match on enc0 scrub (max-mss 1380)) and refine after measuring path MTU. - Clock skew. Certificates and IKE lifetimes are time-sensitive. Ensure NTP is working with ntpd(8) .