| pick your distro, get ZFS on root
kldload — your platform, your way, free
Source

WireGuard on kldload — encrypted networking in 5 minutes

WireGuard is already installed on kldload desktop and server profiles — the kernel module ships in-kernel since Linux 5.6, and the userspace tools (wg, wg-quick) are included. There's nothing to install. This page shows the practical minimum: generate keys, write a config file, bring up the tunnel, and verify it works.

For mesh networking, multi-site setups, and advanced routing, see the WireGuard Masterclass and WireGuard Mesh & Multi-Site.

WireGuard works as a kernel module — no daemon, no certificates, no CA. Each peer has a keypair. You share public keys. Traffic is encrypted automatically. If a peer is reachable, the tunnel works. If it's not, nothing happens — no errors, no retries, no timeouts. It's the simplest VPN you'll ever configure.


Verify WireGuard is ready

wg --version
# wireguard-tools v1.0.20210914 - https://git.zx2c4.com/wireguard-tools/

modinfo wireguard | grep "^version"
# version: 1  (in-kernel, no DKMS needed)

Generate keys

Do this on each node separately. Never share private keys.

# Generate a keypair
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey

# Lock down the private key
chmod 600 /etc/wireguard/privatekey

# Show the keys
cat /etc/wireguard/privatekey   # keep this secret
cat /etc/wireguard/publickey    # share this with peers

Point-to-point example

This example connects two kldload nodes — node-a and node-b. Replace the public IPs and public keys with your actual values.

Setup summary:

  • node-a: real IP 203.0.113.10, tunnel IP 10.99.0.1/24, listens on UDP 51820
  • node-b: real IP 203.0.113.20, tunnel IP 10.99.0.2/24, connects to node-a

node-a: /etc/wireguard/wg0.conf

[Interface]
# node-a's tunnel address
Address = 10.99.0.1/24
# node-a's private key (from /etc/wireguard/privatekey on node-a)
PrivateKey = <node-a-private-key>
# UDP port to listen on
ListenPort = 51820

[Peer]
# node-b's public key (from /etc/wireguard/publickey on node-b)
PublicKey = <node-b-public-key>
# What IPs to route through this peer
AllowedIPs = 10.99.0.2/32
# node-b's real IP and port (if node-b also has a static IP)
Endpoint = 203.0.113.20:51820
# Keep the tunnel alive through NAT
PersistentKeepalive = 25

node-b: /etc/wireguard/wg0.conf

[Interface]
# node-b's tunnel address
Address = 10.99.0.2/24
# node-b's private key (from /etc/wireguard/privatekey on node-b)
PrivateKey = <node-b-private-key>
ListenPort = 51820

[Peer]
# node-a's public key (from /etc/wireguard/publickey on node-a)
PublicKey = <node-a-public-key>
AllowedIPs = 10.99.0.1/32
# node-a's real IP and port
Endpoint = 203.0.113.10:51820
PersistentKeepalive = 25

Lock down the config files on both nodes:

chmod 600 /etc/wireguard/wg0.conf

Bring up the tunnel

Run this on both nodes:

wg-quick up wg0

Expected output on node-a:

[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.99.0.1/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 10.99.0.2/32 dev wg0

Test the tunnel

# From node-a, ping node-b's tunnel IP
ping -c 3 10.99.0.2

# From node-b, ping node-a's tunnel IP
ping -c 3 10.99.0.1

Expected:

PING 10.99.0.2 (10.99.0.2) 56(84) bytes of data.
64 bytes from 10.99.0.2: icmp_seq=1 ttl=64 time=1.23 ms
64 bytes from 10.99.0.2: icmp_seq=2 ttl=64 time=0.98 ms
64 bytes from 10.99.0.2: icmp_seq=3 ttl=64 time=1.05 ms
# Check tunnel status and handshake time
wg show

# Expected output includes:
# interface: wg0
#   public key: ...
#   private key: (hidden)
#   listening port: 51820
#
# peer: <node-b-pubkey>
#   endpoint: 203.0.113.20:51820
#   allowed ips: 10.99.0.2/32
#   latest handshake: X seconds ago
#   transfer: 1.23 KiB received, 456 B sent

If latest handshake shows a time, the tunnel is up and traffic is flowing. If it says (none), the peers haven't exchanged a handshake yet — check firewall rules and that UDP 51820 is open.

# Check firewall on CentOS/RHEL
firewall-cmd --list-all | grep udp

# Open the WireGuard port if needed
firewall-cmd --add-port=51820/udp --permanent
firewall-cmd --reload

# Check firewall on Debian/Ubuntu
ufw status
ufw allow 51820/udp

Persistent config via systemd

Make the tunnel start automatically on boot:

systemctl enable --now wg-quick@wg0

This creates a systemd unit that runs wg-quick up wg0 at boot and wg-quick down wg0 at shutdown.

# Check status
systemctl status wg-quick@wg0

# View logs
journalctl -u wg-quick@wg0 --since "today"

# Reload config without tearing down the tunnel (for minor changes)
wg syncconf wg0 <(wg-quick strip wg0)

Useful commands

# Show active tunnel(s) and peer stats
wg show

# Show just the interface
wg show wg0

# Bring down the tunnel
wg-quick down wg0

# Show tunnel traffic in real time (updates every second)
watch -n 1 wg show

# Generate a new preshared key for extra security (optional, add to both peers)
wg genpsk

Go deeper

This page covered the minimum to get two nodes talking. For more: