How to Set Up a WireGuard VPN on FreeBSD
A complete walkthrough configuring a WireGuard tunnel on FreeBSD using the in-kernel wg driver, from key generation to a working peer-to-peer connection.
FreeBSD includes an in-kernel WireGuard implementation (the wg driver), avoiding the need for a userspace WireGuard implementation entirely. This walks through a complete two-peer setup.
Step 1: confirm the wg kernel module is available
kldload wg
kldstat | grep wg
Step 2: install the userspace configuration tools
pkg install wireguard-tools
The kernel module handles the actual packet processing; wireguard-tools provides wg and wg-quick, the commands used to configure it.
Step 3: generate a key pair on each peer
wg genkey | tee privatekey | wg pubkey > publickey
Run this on both machines that will form the VPN tunnel — each peer needs its own private/public key pair, and needs the other peer’s public key to complete the configuration.
Step 4: create the interface configuration on the first peer
# /usr/local/etc/wireguard/wg0.conf
[Interface]
PrivateKey = <peer1-private-key>
Address = 10.0.0.1/24
ListenPort = 51820
[Peer]
PublicKey = <peer2-public-key>
AllowedIPs = 10.0.0.2/32
Endpoint = <peer2-public-ip>:51820
Step 5: create the matching configuration on the second peer
# /usr/local/etc/wireguard/wg0.conf
[Interface]
PrivateKey = <peer2-private-key>
Address = 10.0.0.2/24
ListenPort = 51820
[Peer]
PublicKey = <peer1-public-key>
AllowedIPs = 10.0.0.1/32
Endpoint = <peer1-public-ip>:51820
Each side’s [Peer] section describes the other machine — this symmetry is the most common source of misconfiguration, so double-check public keys are pasted from the right file on each side.
Step 6: bring the tunnel up on both peers
wg-quick up wg0
Step 7: verify the tunnel is actually passing traffic
wg show
ping 10.0.0.2 # from peer1, targeting peer2's tunnel address
wg show displays handshake and transfer statistics per peer — a peer that’s never completed a handshake points at a configuration mismatch (wrong public key, wrong endpoint, or a firewall blocking the UDP port) rather than a WireGuard problem itself.
Step 8: open the WireGuard port in pf, if enabled
# /etc/pf.conf
pass in on em0 proto udp from any to any port 51820
A correctly configured tunnel that still won’t handshake across the internet is frequently just a firewall blocking the UDP port WireGuard uses.
Step 9: enable the tunnel to start automatically at boot
sysrc wireguard_enable=YES
sysrc wireguard_interfaces=wg0
Why the in-kernel implementation matters
Running WireGuard as an in-kernel driver rather than a userspace daemon avoids the context-switching overhead a userspace implementation incurs on every packet, giving FreeBSD’s WireGuard support performance much closer to the protocol’s original Linux in-kernel implementation than a portable userspace version could realistically achieve — a meaningful practical advantage for a VPN carrying real production traffic rather than just occasional administrative access.