Synopsis #
OpenBSD includes the wg(4) WireGuard interface in the base system. This guide configures one OpenBSD gateway and one roaming OpenBSD client with a split tunnel. The client reaches a private LAN through the VPN while ordinary Internet traffic continues to use the client’s existing connection.
WireGuard authenticates peers with public keys. It does not assign addresses, distribute routes, provide user authentication, or create firewall policy. Those functions must be configured explicitly.
Example Topology #
The example uses:
| Role | Value |
|---|---|
| Gateway public address | 198.51.100.10 |
| Gateway UDP port | 51820 |
| VPN network | 10.64.0.0/24 |
| Gateway VPN address | 10.64.0.1 |
| Client VPN address | 10.64.0.2 |
| Private LAN behind gateway | 10.10.10.0/24 |
| Gateway LAN interface | em1 |
The public address is from a documentation-only range. Replace it with the gateway’s actual stable address or name.
Generate and Protect Keys #
On each peer, create a root-only directory and generate a 32-byte private key with openssl(1) :
# install -d -o root -g wheel -m 700 /etc/wireguard
# Create a protected key directory
# openssl rand -base64 32 > /etc/wireguard/wg0.key
# Generate this peer's private key
# chmod 600 /etc/wireguard/wg0.key
# Prevent other accounts from reading it
Never copy a private key to the other peer.
To obtain the corresponding public key, create a temporary interface with ifconfig(8)
, load the private key, record the wgpubkey value, and destroy the interface:
# ifconfig wg0 create
# Create a temporary WireGuard interface
# ifconfig wg0 wgkey "$(cat /etc/wireguard/wg0.key)"
# Load the local private key
# ifconfig wg0 | grep wgpubkey
# Record only the displayed public key
# ifconfig wg0 destroy
# Remove the temporary interface
Exchange the public keys through an authenticated channel. Record which device owns each key.
Configure the Gateway #
Create a protected /etc/hostname.wg0 on the gateway before inserting any key material. The first command preserves an existing configuration so the settings can be merged instead of replaced:
# test -e /etc/hostname.wg0 || install -o root -g wheel -m 600 /dev/null /etc/hostname.wg0
# Create the empty file only when it does not already exist
# chown root:wheel /etc/hostname.wg0
# chmod 600 /etc/hostname.wg0
# Enforce restrictive ownership and permissions before editing
# vi /etc/hostname.wg0
# Insert the configuration shown below
Replace CLIENT_PUBLIC_KEY with the client’s public key and replace SERVER_PRIVATE_KEY with the contents of the gateway key file:
wgport 51820
wgkey SERVER_PRIVATE_KEY
wgpeer CLIENT_PUBLIC_KEY wgaip 10.64.0.2/32
inet 10.64.0.1 255.255.255.0
up
Confirm the ownership and permissions after editing because the file contains a private key:
# chown root:wheel /etc/hostname.wg0
# chmod 600 /etc/hostname.wg0
Enable forwarding persistently in /etc/sysctl.conf:
net.inet.ip.forwarding=1
Apply the interface and forwarding setting:
# sh /etc/netstart wg0
# Create and configure the persistent interface
# sysctl net.inet.ip.forwarding=1
# Permit routed IPv4 traffic immediately
Configure the Gateway Firewall #
Integrate the following fragment into the complete /etc/pf.conf:
wan = "em0"
lan = "em1"
vpn_if = "wg0"
vpn_net = "10.64.0.0/24"
lan_net = "10.10.10.0/24"
pass in on $wan proto udp to ($wan) port 51820
pass in on $vpn_if inet proto icmp from $vpn_net to ($vpn_if) icmp-type echoreq
pass in on $vpn_if from $vpn_net to $lan_net
pass out on $lan from $vpn_net to $lan_net
The LAN must have a route back to 10.64.0.0/24 through the OpenBSD gateway. If the OpenBSD host is not the LAN’s default router and the LAN router cannot accept that route, source NAT can provide a constrained fallback:
match out on $lan from $vpn_net to $lan_net nat-to ($lan)
Do not add this translation when normal routing can provide the return path. Parse and load the complete policy, then retain the recovery session:
# pfctl -nf /etc/pf.conf
# Check syntax without changing the active policy
# pfctl -f /etc/pf.conf
# Load the verified ruleset
Configure the Roaming Client #
Create a protected /etc/hostname.wg0 on the client before inserting any key material. The first command preserves an existing configuration so the settings can be merged instead of replaced:
# test -e /etc/hostname.wg0 || install -o root -g wheel -m 600 /dev/null /etc/hostname.wg0
# Create the empty file only when it does not already exist
# chown root:wheel /etc/hostname.wg0
# chmod 600 /etc/hostname.wg0
# Enforce restrictive ownership and permissions before editing
# vi /etc/hostname.wg0
# Insert the configuration shown below
Replace CLIENT_PRIVATE_KEY with the client private key and SERVER_PUBLIC_KEY with the gateway public key:
wgkey CLIENT_PRIVATE_KEY
wgpeer SERVER_PUBLIC_KEY wgendpoint 198.51.100.10 51820 wgaip 10.64.0.0/24 wgaip 10.10.10.0/24 wgpka 25
inet 10.64.0.2 255.255.255.0
!/sbin/route add -inet 10.10.10.0/24 10.64.0.1
up
The persistent keepalive is useful when the client is behind stateful NAT. The explicit route sends only the private LAN through the tunnel. Restrict and apply the file:
# chown root:wheel /etc/hostname.wg0
# Preserve administrative ownership
# chmod 600 /etc/hostname.wg0
# Protect the private key
# sh /etc/netstart wg0
# Create the tunnel and private-LAN route
Verify the Tunnel #
On both peers, inspect the interface:
# ifconfig wg0
The status shows public keys, allowed IP ranges, endpoints, and recent handshakes. WireGuard is connectionless; no permanent connected state exists.
From the client, generate tunnel traffic and confirm routing:
$ route -n get 10.10.10.1
# Confirm that the private LAN route uses wg0
$ ping 10.64.0.1
# Test the WireGuard gateway address
$ ping 10.10.10.1
# Test the private LAN path
On the gateway, observe encrypted and decrypted traffic with tcpdump(8) :
# tcpdump -ni em0 udp port 51820
# Observe encrypted WireGuard datagrams
# tcpdump -ni wg0
# Observe decrypted tunnel traffic
If no handshake appears, compare both public keys, the gateway endpoint, UDP reachability, and PF counters. If the handshake succeeds but LAN traffic fails, inspect forwarding, the client’s route, the gateway’s PF policy, and the LAN return path.
Add More Clients #
Generate a unique key pair and VPN address for every client. Add one wgpeer line per public key on the gateway, with a unique /32 allowed IP. Do not reuse a private key or VPN address.
Removing a peer from /etc/hostname.wg0 and reapplying the interface revokes that peer’s future handshakes. Key rotation requires installing the replacement public key on the opposite peer before removing the old key.
A full-tunnel VPN requires pinned endpoint reachability, default-route changes, DNS ownership, Internet forwarding, and leak-policy decisions. Those concerns are deliberately outside this split-tunnel procedure.
See VPN and Cryptographic Tunneling for protocol selection and site-to-site designs.