What is WireGuard and why not just use OpenVPN?
WireGuard was written by Jason Donenfeld and published in 2015. It runs in kernel space rather than user space, which is why it outperforms OpenVPN on the same hardware. On a Raspberry Pi 4 you can expect 200–300 Mbit/s throughput with WireGuard versus roughly 50–80 Mbit/s with OpenVPN.
The cryptography is opinionated by design. WireGuard uses Curve25519 for key exchange, ChaCha20-Poly1305 for encryption and BLAKE2s for hashing. OpenVPN lets you configure which algorithms to use, which sounds flexible but means people regularly misconfigure it with weaker settings. WireGuard has no weak configuration options. There is nothing to get wrong.
The codebase is small enough that a single person can audit it. That is a feature, not an accident.
Prerequisites
- Raspberry Pi running Pi OS 64-bit Bullseye or newer (Linux kernel 5.15+)
- Sudo access on the Pi
- A static or reserved LAN IP for the Pi (set a DHCP reservation in your router based on the Pi's MAC address)
- A public IP or DDNS hostname to reach the server from outside your network. DuckDNS and Cloudflare both work
- Port forwarding on your router: UDP 51820 forwarded to the Pi's LAN IP
Check your kernel version:
uname -r
# Should return 5.15 or higher
Installation
sudo apt update && sudo apt install -y wireguard
That is the entire installation. Because WireGuard is in the kernel, this command only installs the user-space tools: wg and wg-quick. No compilation, no DKMS, no kernel modules to build.
wg --version
# e.g.: wireguard-tools v1.0.20210914
Generate keys
WireGuard uses static Curve25519 key pairs for authentication. Each peer (server and client) has its own pair.
# Generate the server's key pair
wg genkey | sudo tee /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key
# Lock down the private key
sudo chmod 600 /etc/wireguard/server_private.key
# Generate a client key pair (repeat for each new client)
wg genkey | tee client1_private.key | wg pubkey > client1_public.key
# Display all four keys — you will need them below
sudo cat /etc/wireguard/server_private.key
sudo cat /etc/wireguard/server_public.key
cat client1_private.key
cat client1_public.key
Server configuration
Create /etc/wireguard/wg0.conf. Replace SERVER_PRIVATE_KEY with the contents of /etc/wireguard/server_private.key, and CLIENT1_PUBLIC_KEY with the contents of client1_public.key.
sudo nano /etc/wireguard/wg0.conf
[Interface]
# Server's private key
PrivateKey = SERVER_PRIVATE_KEY
# Server's IP address inside the tunnel
Address = 10.0.0.1/24
# UDP port WireGuard listens on
ListenPort = 51820
# iptables rules applied on interface up/down
# Replace eth0 with your interface — check with: ip link show
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# Client 1
PublicKey = CLIENT1_PUBLIC_KEY
AllowedIPs = 10.0.0.2/32
sudo chmod 600 /etc/wireguard/wg0.conf
ip link show. On Pi OS it is typically eth0 for wired and wlan0 for WiFi. Replace eth0 in the PostUp/PostDown lines accordingly.
Enable IP forwarding
The Pi needs to route traffic onwards rather than stop it. Open /etc/sysctl.conf and find this line:
#net.ipv4.ip_forward=1
Remove the #. Apply without rebooting:
sudo sysctl -p
# Verify — should return 1
cat /proc/sys/net/ipv4/ip_forward
Start and enable WireGuard
# Bring up wg0
sudo wg-quick up wg0
# Enable automatic start at boot
sudo systemctl enable wg-quick@wg0
# Check what is running
sudo wg show
wg show displays the active interface, listening port, and any configured peers. If you see interface: wg0 with your ListenPort, the server is running. The peer section will show no handshake timestamp yet. That appears once a client connects.
Client configuration
WireGuard clients for Windows, macOS, iOS and Android all use the same config format. Download the official app from wireguard.com/install. On Linux: sudo apt install wireguard.
Create wg0-client1.conf on the client device:
[Interface]
# Client's private key (contents of client1_private.key)
PrivateKey = CLIENT1_PRIVATE_KEY
# Client's IP inside the tunnel — must match AllowedIPs on the server
Address = 10.0.0.2/32
# Optional: use the VPN server as DNS
DNS = 10.0.0.1
[Peer]
# Server's public key
PublicKey = SERVER_PUBLIC_KEY
# Server's public IP or DDNS hostname
Endpoint = YOUR_IP_OR_DDNS:51820
# 0.0.0.0/0 = full tunnel (all traffic via VPN)
# Use 10.0.0.0/24, 192.168.1.0/24 for split tunnel instead
AllowedIPs = 0.0.0.0/0, ::/0
# Keep the connection alive behind NAT
PersistentKeepalive = 25
AllowedIPs = 0.0.0.0/0 routes all internet traffic through your Pi. If you only want access to your home network, use AllowedIPs = 10.0.0.0/24, 192.168.1.0/24 instead. Split tunnel is faster but leaves other traffic outside the VPN.
Add the client to a running server
You do not need to restart WireGuard to add a new peer:
sudo wg set wg0 peer CLIENT1_PUBLIC_KEY allowed-ips 10.0.0.2/32
To make it persist across reboots, save back to wg0.conf:
sudo wg-quick save wg0
Verify the connection
# On the client: bring up the tunnel
sudo wg-quick up wg0-client1
# Ping the server
ping 10.0.0.1
# On the server: check peers and handshake timestamp
sudo wg show
A latest handshake timestamp a few seconds old in wg show means the tunnel is up and encrypted traffic is flowing.
Troubleshooting
Port forwarding
This is the most common issue. Log into your router and forward UDP 51820 to the Pi's LAN IP. Test from outside your network (use mobile data, not your home WiFi):
nc -zvu YOUR_PUBLIC_IP 51820
UFW blocking traffic
If you are running UFW on the Pi:
sudo ufw allow 51820/udp
sudo ufw allow OpenSSH
sudo ufw enable
# Allow forwarding in UFW
sudo nano /etc/default/ufw
# Set: DEFAULT_FORWARD_POLICY="ACCEPT"
MTU issues
PPPoE connections (common on older xDSL lines) have an MTU of 1492 instead of 1500. WireGuard defaults to 1420. If you see dropped connections or large file transfers failing, add this to [Interface] in the server config:
MTU = 1380
View logs
sudo journalctl -u wg-quick@wg0 -f
Adding more clients
Generate a fresh key pair for each client. Add a new [Peer] block to the server config with that client's public key and a unique tunnel IP (10.0.0.3/32, 10.0.0.4/32 and so on). The client config follows the same format shown above.
sudo wg show
Frequently asked questions
What is the difference between WireGuard and OpenVPN?
WireGuard is faster, uses fewer resources and has a codebase of roughly 4,000 lines versus OpenVPN's ~100,000. Fewer lines means fewer potential vulnerabilities. OpenVPN is older and more battle-tested in enterprise environments. For a homelab, WireGuard wins on every measure.
Can I run WireGuard and Pi-hole on the same Pi?
Yes. Set Pi-hole's LAN IP as the DNS value in your WireGuard client config. All DNS traffic through the VPN then goes through Pi-hole, including from your phone when you are on mobile data.
Does WireGuard work behind CG-NAT?
CG-NAT is a problem because you have no static public IP. Two options: a cheap VPS as a jumphost (Hetzner CAX11 for ~4 EUR/month), or use Cloudflare Tunnel which requires no open port at all.