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 IP10.99.0.1/24, listens on UDP 51820 - node-b: real IP
203.0.113.20, tunnel IP10.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:
- WireGuard Basics — concepts, how the protocol works, AllowedIPs explained
- WireGuard Masterclass — routing, NAT traversal, split tunneling, DNS
- WireGuard Mesh & Multi-Site — hub-and-spoke, full mesh, multi-site BGP