← Back to blog
Homelab Networking Security
 ·  7 min read

WireGuard VPN on Raspberry Pi: the definitive self-hosted setup guide

Step-by-step: install WireGuard, generate keys, configure peers, set up IP forwarding, and access your home network securely from anywhere — including the critical UFW rules that most guides forget.

Why WireGuard over OpenVPN?

OpenVPN has been the self-hosted VPN standard for years, but WireGuard is simply better for homelab use:

Prerequisites

Installation

sudo apt update && sudo apt install -y wireguard

On modern Raspberry Pi OS (kernel 5.x+), WireGuard is included as a kernel module. No DKMS compilation needed.

Key generation

WireGuard uses public/private key pairs. Generate one pair for the server and one for each client (peer):

# Server keys
wg genkey | sudo tee /etc/wireguard/server_private.key | \
  wg pubkey | sudo tee /etc/wireguard/server_public.key

# Client keys (repeat for each device)
wg genkey | tee client_private.key | wg pubkey | tee client_public.key

# Secure the private keys
sudo chmod 600 /etc/wireguard/server_private.key
Important

Never share or commit private keys. Only public keys are exchanged between peers. The private key should only ever exist on the device it was generated for.

Server configuration (/etc/wireguard/wg0.conf)

[Interface]
Address = 10.13.13.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>

# Enable IP forwarding on bring-up
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# Peer 1 — Mobile phone
[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.13.13.2/32

# Peer 2 — Laptop
[Peer]
PublicKey = <LAPTOP_PUBLIC_KEY>
AllowedIPs = 10.13.13.3/32

The 10.13.13.0/24 subnet is the VPN tunnel network. Each peer gets a static IP within it. The server is .1, peers start at .2.

Persistent IP forwarding

The PostUp sysctl line handles forwarding at runtime, but you also need it to survive reboots:

sudo tee /etc/sysctl.d/99-wireguard-forwarding.conf << EOF
net.ipv4.ip_forward=1
EOF
sudo sysctl --system

UFW configuration — the part most guides skip

If your server runs UFW (and it should), a fresh ufw enable with default deny-incoming will silently block all WireGuard traffic. This is the most common reason WireGuard “doesn’t work” after a security hardening session.

Common pitfall

After activating UFW with hardened rules, the VPN port was blocked and every connection attempt from mobile showed zero bytes exchanged — not even a handshake. The fix is two rules that most WireGuard guides don’t mention.

You need three things from UFW:

# 1. Allow the WireGuard UDP port (incoming connections)
sudo ufw allow 51820/udp comment 'WireGuard'

# 2. Allow traffic to be routed THROUGH the VPN interface
sudo ufw route allow in on wg0
sudo ufw route allow out on wg0

The route rules are necessary because UFW's default policy also covers forwarded traffic (deny (routed)). Without them, VPN clients can connect to the tunnel but cannot reach the internet or LAN through it.

Enable and start WireGuard

# Start WireGuard
sudo wg-quick up wg0

# Enable on boot
sudo systemctl enable wg-quick@wg0

# Verify
sudo wg show wg0

The wg show output will display connected peers with their latest handshake timestamp and transfer bytes — the quickest way to confirm everything is working.

Client configuration

For each peer, create a config file (or QR code for mobile):

[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY>
Address = 10.13.13.2/32
DNS = 1.1.1.1

[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = mynw.oajm.tech:51820
AllowedIPs = 0.0.0.0/0   # Route all traffic through VPN
PersistentKeepalive = 25

<code>AllowedIPs = 0.0.0.0/0</code> routes all client traffic through the tunnel (full tunnel mode). If you only want to access the home LAN, use 10.13.13.0/24, 10.0.0.0/24 instead (split tunnel).

<code>PersistentKeepalive = 25</code> sends a keepalive packet every 25 seconds, which is essential for maintaining the connection through NAT and mobile network transitions.

Generate a QR code for mobile

sudo apt install qrencode
qrencode -t ansiutf8 < /path/to/client.conf

Scan it directly with the WireGuard app on iOS or Android — no manual typing required.

fail2ban integration

fail2ban should never ban your own devices. Add the VPN and LAN subnets to the ignore list in /etc/fail2ban/jail.local:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/24 10.13.13.0/24

Without this, repeated SSH connection attempts through the VPN (or failed attempts when the tunnel was down) will trigger a ban on your own IP — locking you out of the very machine running your VPN.

Verifying the connection

# On the server: check connected peers and transfer stats
sudo wg show wg0

# Expected output for a connected peer:
# peer: LZx1dg...
#   endpoint: 72.136.119.90:21207
#   allowed ips: 10.13.13.2/32
#   latest handshake: 13 seconds ago
#   transfer: 992 KiB received, 5.25 MiB sent

A handshake timestamp under 3 minutes means the peer is actively connected. Zero transfer bytes with no handshake means the UDP port is blocked at the firewall level.

Result

With this setup, the Pi acts as a secure gateway to the entire home network. SSH, RDP, and all local services are accessible from anywhere in the world over an encrypted tunnel, with no VPN provider having visibility into your traffic.

← Back to blog 🔗 View on GitHub