How to Set Up a WireGuard VPN Server on Your Homelab

How to Set Up a WireGuard VPN Server on Your Homelab

Why WireGuard on Your Homelab Matters

You need remote access to your homelab without exposing services directly to the internet or managing complex OpenVPN configurations. WireGuard gives you a modern, auditable VPN kernel module (just ~4000 lines of code) that's dramatically faster than OpenVPN, with simpler key management and easier debugging. This post walks you through a production-grade WireGuard homelab setup with client configurations and a proper kill switch.

This guide assumes you're running a dedicated edge device or VM that you're comfortable putting on your LAN gateway tier—not a random VPS. I'll cover installation, server configuration, client setup, and troubleshooting on a real setup.

Prerequisites and System Requirements

Software versions tested:

  • WireGuard 1.0.20240704 (current as of July 2024)
  • Ubuntu 24.04.1 LTS (but works on any distro with a modern kernel)
  • Linux kernel 5.6+ (WireGuard in mainline; older kernels need DKMS module)

Hardware and network assumptions:

  • Dedicated device or VM with two network interfaces (one to LAN, one optional for external routing)
  • Static IP on your homelab network (e.g., 192.168.1.50)
  • Public IP or dynamic DNS pointing to your edge device (we'll use a static port)
  • Root or passwordless sudo access
  • At least 256MB RAM free; I'm running this on a T5810 with 24GB total alongside other services

Before you start: Verify your kernel supports WireGuard with `uname -r` (5.6+) and check `modprobe wireguard` returns cleanly. On older Ubuntu LTS releases, you may need `apt install wireguard-dkms` instead of the mainline module.

Install WireGuard and Generate Keys

Start with a fresh Ubuntu 24.04.1 LTS system or any distro with kernel 5.6+. Install WireGuard and its tools:

sudo apt update
sudo apt install -y wireguard wireguard-tools
sudo modprobe wireguard
lsmod | grep wireguard

You should see `wireguard` in the output. If not, you're on an older kernel and need to install `wireguard-dkms` instead (which takes ~2 minutes to compile).

Create a secure directory for keys with restricted permissions:

sudo mkdir -p /etc/wireguard
sudo chmod 700 /etc/wireguard
cd /etc/wireguard

Generate the server's private and public keys:

sudo bash -c 'wg genkey | tee privatekey | wg pubkey > publickey'
sudo chmod 600 privatekey
sudo cat privatekey  # You'll need this for wg0.conf

Now generate keys for your first client (laptop, phone, etc.). Do this on the server for now—you'll copy the public key into the server config and the client private key to your device:

sudo bash -c 'wg genkey | tee client1-privatekey | wg pubkey > client1-publickey'
sudo cat client1-publickey  # You'll need this for wg0.conf

Gotcha #1: WireGuard keys are base64-encoded raw bytes. Don't manually edit them or use different key generation tools—always use `wg genkey` and `wg pubkey`. If you lose a client's private key, there's no recovery; generate a new pair.

Configure the WireGuard Server (wg0)

Create the main WireGuard interface configuration. Replace `YOUR_SERVER_PRIVATE_KEY` with the output from the previous step:

[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = YOUR_SERVER_PRIVATE_KEY
SaveConfig = false

# Forwarding and NAT for clients
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

# Client 1 (laptop)
[Peer]
PublicKey = YOUR_CLIENT1_PUBLIC_KEY
AllowedIPs = 10.0.0.2/32

Save this as `/etc/wireguard/wg0.conf` using `sudo tee`:

sudo tee /etc/wireguard/wg0.conf > /dev/null << 'EOF'
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = YOUR_SERVER_PRIVATE_KEY
SaveConfig = false

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]
PublicKey = YOUR_CLIENT1_PUBLIC_KEY
AllowedIPs = 10.0.0.2/32
EOF

Replace `eth0` with your actual external-facing interface (check `ip route | grep default`). Set permissions and bring up the interface:

sudo chmod 600 /etc/wireguard/wg0.conf
sudo ip link add dev wg0 type wireguard
sudo ip addr add 10.0.0.1/24 dev wg0
sudo ip link set up dev wg0

Verify it's running:

sudo wg show

You should see the interface, listen port, and peer information. To make this persistent across reboots, enable and start the wg-quick service:

sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
sudo systemctl status wg-quick@wg0

Gotcha #2: The `PostUp` and `PostDown` lines manage NAT and packet forwarding. If you omit these, clients can connect but can't reach your homelab network. Also, check that `/proc/sys/net/ipv4/ip_forward` is `1` (set with `echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward` if not). Some distros disable this by default.

Generate and Distribute Client Configurations

Create a client config file. This contains the client's private key, the server's public key, and the server's public IP/port. Replace placeholders:

[Interface]
PrivateKey = YOUR_CLIENT1_PRIVATE_KEY
Address = 10.0.0.2/32
DNS = 8.8.8.8, 8.8.4.4

[Peer]
PublicKey = YOUR_SERVER_PUBLIC_KEY
Endpoint = your.public.ip:51820
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24
PersistentKeepalive = 25

Save this as `client1.conf`:

tee ~/client1.conf > /dev/null << 'EOF'
[Interface]
PrivateKey = YOUR_CLIENT1_PRIVATE_KEY
Address = 10.0.0.2/32
DNS = 8.8.8.8, 8.8.4.4

[Peer]
PublicKey = YOUR_SERVER_PUBLIC_KEY
Endpoint = your.public.ip:51820
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24
PersistentKeepalive = 25
EOF

The `AllowedIPs` field defines which traffic goes through the VPN. I've set `10.0.0.0/24` (the VPN network) and `192.168.1.0/24` (your homelab LAN). If you want all internet traffic routed through the VPN too, add `0.0.0.0/0`. The `PersistentKeepalive = 25` keeps the connection alive through NAT on the client side (important for mobile).

Convert the config to a QR code for mobile clients:

sudo apt install -y qrencode
qrencode -t ansiutf8 < ~/client1.conf

For Linux/macOS laptops, copy the `.conf` file directly. For iOS/Android, scan the QR code or import the `.conf` via the WireGuard app. Never email configs unencrypted; use signal, encrypted storage, or transfer over your already-authenticated VPN.

Add a Kill Switch for Client Leak Protection

A kill switch ensures that if your VPN connection drops, traffic doesn't leak outside the tunnel. For Linux clients, add `SaveConfig = false` to the `[Interface]` section and use firewall rules. Here's a systemd service that manages the kill switch on Ubuntu/Debian clients:

[Unit]
Description=WireGuard Kill Switch
After=network.target [email protected]
[email protected]

[Service]
Type=oneshot
ExecStart=/usr/local/bin/wg-killswitch-up.sh
ExecStop=/usr/local/bin/wg-killswitch-down.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Save as `/etc/systemd/system/wg-killswitch.service` (on your client machine, not the server). Create the scripts:

sudo tee /usr/local/bin/wg-killswitch-up.sh > /dev/null << 'EOF'
#!/bin/bash
set -e

# Allow only VPN interface and localhost
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT
sudo iptables -A INPUT -i wg0 -j ACCEPT
sudo iptables -A OUTPUT -o wg0 -j ACCEPT

# Allow establishing tunnel (UDP 51820 out)
sudo iptables -A OUTPUT -p udp --dport 51820 -j ACCEPT
sudo iptables -A INPUT -p udp --sport 51820 -j ACCEPT

echo "Kill switch engaged"
EOF

sudo tee /usr/local/bin/wg-killswitch-down.sh > /dev/null << 'EOF'
#!/bin/bash
set -e
sudo iptables -P INPUT ACCEPT
sudo iptables -P OUTPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -F
sudo iptables -X
echo "Kill switch disengaged"
EOF

sudo chmod +x /usr/local/bin/wg-killswitch-*.sh
sudo systemctl daemon-reload
sudo systemctl enable wg-killswitch
sudo systemctl start wg-killswitch

This is belt-and-suspenders: even if WireGuard crashes, your traffic stays firewalled until you manually clear the rules or reboot. Test it by pulling your ethernet cable and checking that nothing connects.

Firewall and Port Forwarding

On your homelab edge device (where the server runs), allow WireGuard traffic inbound:

sudo ufw allow 51820/udp
sudo ufw enable
sudo ufw status

If your homelab sits behind NAT (home router), forward port 51820/UDP to your edge device's internal IP. Log into your router's web UI and add a port forward rule: External 51820 UDP → Internal IP (e.g., 192.168.1.50) port 51820.

Verify external reachability from a client outside your network:

nc -u -zv your.public.ip 51820

Should return "succeeded" or similar. If it times out, check your port forwarding and firewall rules.

Common Issues and Troubleshooting

Client connects but can't reach homelab (no packets flowing): Check the server's PostUp iptables rules executed correctly. Run `sudo iptables -t nat -L -n | grep MASQUERADE` and verify your external interface name is correct. Also confirm `ip_forward` is enabled: `cat /proc/sys/net/ipv4/ip_forward` should be `1`.

Connection drops after 5-10 minutes on mobile: Your NAT gateway is timing out the UDP session. Add `PersistentKeepalive = 25` to the Peer section of your mobile client config. This sends a keepalive every 25 seconds, resetting the NAT timeout.

"Cannot find a suitable TUN device" on non-Linux clients: WireGuard needs TUN support in your OS. On macOS, install via Homebrew (`brew install wireguard-tools`). On Windows, download the MSI from wireguard.com/install. On iOS/Android, use the official app from the App Store.

Server listening on all interfaces but peers don't show in `wg show`: Peers appear after they send their first packet. Connect a client, then run `sudo wg show` again. If they still don't appear after 30 seconds, check UDP 51820 isn't being filtered by a middle router (test with `nc -u -zv` from outside).

Can reach VPN gateway (10.0.0.1) but not other homelab services: Your homelab's default gateway doesn't know how to route back to the 10.0.0.0/24 subnet. If you're running this on a router/edge device, this usually works. If it's a separate VM, add a static route on your homelab gateway pointing 10.0.0.0/24 to your WireGuard server's LAN IP.

What You Have Now and Next Steps

You've deployed a self-hosted WireGuard VPN server suitable for secure,

Read more