Skip to content
daniel@cosenza:~/blog
FreeBSDHow-To March 9, 2026 2 min read

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.