WireGuard VPN Setup for Self-Hosting

What Is WireGuard?

WireGuard is a modern VPN protocol built into the Linux kernel. It’s faster, simpler, and more secure than OpenVPN or IPSec. A WireGuard tunnel encrypts all traffic between your devices and your home server, giving you secure access to self-hosted services from anywhere.

Unlike Tailscale (which uses WireGuard under the hood with automated management), raw WireGuard requires manual key exchange and configuration. You get full control at the cost of more setup work.

Prerequisites

  • A Linux server with a public IP or port forwarding (Port Forwarding Guide)
  • SSH access to your server (SSH Setup Guide)
  • Port 51820/UDP forwarded from your router to your server
  • A client device (phone, laptop) to connect from

WireGuard vs Tailscale

FeatureWireGuard (raw)Tailscale
Key managementManualAutomatic
NAT traversalRequires port forwardingBuilt-in
Works behind CGNATNoYes
Config complexityMediumLow
ControlFullTailscale manages coordination
CostFreeFree (personal, up to 100 devices)
DependenciesNone (kernel module)Tailscale service + account

Use raw WireGuard if: You want full control, have a public IP, or don’t want to depend on Tailscale’s infrastructure. Use Tailscale if: You want zero-config setup, are behind CGNAT, or need mesh networking. See Tailscale Setup.

wg-easy provides WireGuard with a web management UI. Easiest setup.

# docker-compose.yml
services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:14
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
    environment:
      - WG_HOST=vpn.yourdomain.com    # Your public domain or IP
      - PASSWORD_HASH=$$2a$$12$$hashgoeshere  # bcrypt hash of admin password
      - WG_DEFAULT_DNS=1.1.1.1,8.8.8.8
      - WG_ALLOWED_IPS=0.0.0.0/0      # Route all traffic through VPN
    volumes:
      - wg-easy-data:/etc/wireguard
    ports:
      - "51820:51820/udp"
      - "127.0.0.1:51821:51821/tcp"   # Web UI — localhost only
    restart: unless-stopped

volumes:
  wg-easy-data:

Generate the password hash:

docker run -it ghcr.io/wg-easy/wg-easy:14 wgpw 'YourStrongPassword'
# Copy the output hash (escape $ as $$ in docker-compose.yml)
docker compose up -d

Access the web UI at http://your-server:51821 (via SSH tunnel or Tailscale). Add clients through the UI — it generates QR codes you can scan with the WireGuard mobile app.

Method 2: Native WireGuard (Manual)

Install WireGuard

# Ubuntu/Debian
sudo apt install wireguard

# Verify kernel module
sudo modprobe wireguard
lsmod | grep wireguard

Generate Keys

# Server keys
wg genkey | tee server_private.key | wg pubkey > server_public.key
chmod 600 server_private.key

# Client keys (repeat per client)
wg genkey | tee client1_private.key | wg pubkey > client1_public.key
chmod 600 client1_private.key

Server Configuration

# /etc/wireguard/wg0.conf
[Interface]
Address = 10.200.200.1/24
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# Client 1 — phone
[Peer]
PublicKey = CLIENT1_PUBLIC_KEY
AllowedIPs = 10.200.200.2/32

# Client 2 — laptop
[Peer]
PublicKey = CLIENT2_PUBLIC_KEY
AllowedIPs = 10.200.200.3/32

Replace eth0 with your server’s actual network interface name (check with ip addr).

Enable IP Forwarding

echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Start WireGuard

# Start
sudo wg-quick up wg0

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

# Check status
sudo wg show

Client Configuration

# Client config (client1.conf)
[Interface]
Address = 10.200.200.2/24
PrivateKey = CLIENT1_PRIVATE_KEY
DNS = 1.1.1.1, 8.8.8.8

[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = vpn.yourdomain.com:51820
AllowedIPs = 0.0.0.0/0    # Route all traffic (full tunnel)
# AllowedIPs = 10.200.200.0/24, 192.168.1.0/24  # Split tunnel (only LAN traffic)
PersistentKeepalive = 25

Install Client

  • macOS/Windows/Linux: Download from wireguard.com
  • iOS/Android: Install WireGuard from App Store / Google Play
  • Import the config file or scan a QR code

Generate a QR code for mobile:

sudo apt install qrencode
qrencode -t ansiutf8 < client1.conf

Full Tunnel vs Split Tunnel

Full Tunnel (Route All Traffic)

# Client config
AllowedIPs = 0.0.0.0/0

All internet traffic goes through your home server. Use this to:

  • Use your home’s Pi-hole DNS from anywhere
  • Encrypt all traffic on untrusted WiFi
  • Appear to be at home (geo-restrictions)

Split Tunnel (Route Only LAN Traffic)

# Client config
AllowedIPs = 10.200.200.0/24, 192.168.1.0/24

Only traffic to your home network goes through the VPN. Internet traffic uses the local connection. Better battery life on mobile, lower latency for general browsing.

Recommendation: Split tunnel for daily use, full tunnel on untrusted networks.

Firewall Configuration

# Allow WireGuard port
sudo ufw allow 51820/udp

# Allow forwarding from VPN to LAN
sudo ufw route allow in on wg0 out on eth0

See Firewall Setup with UFW for more details.

Common Mistakes

1. Forgetting IP Forwarding

Without net.ipv4.ip_forward = 1, the server receives VPN traffic but doesn’t route it to the LAN or internet. Clients connect but can’t reach anything.

2. Wrong AllowedIPs on Server

The server’s AllowedIPs for each peer must be the client’s VPN IP only (e.g., 10.200.200.2/32). Setting it to 0.0.0.0/0 would route all server traffic to that client.

3. Firewall Blocking UDP 51820

WireGuard uses UDP, not TCP. sudo ufw allow 51820/udp — don’t forget the /udp.

4. Not Using PersistentKeepalive Behind NAT

If the client is behind NAT (common on mobile), add PersistentKeepalive = 25 to keep the connection alive. Without it, the NAT table entry expires and the tunnel drops.

5. Using the Same Key Pair for Multiple Clients

Each client needs its own key pair. Reusing keys between clients causes routing conflicts.

FAQ

Is WireGuard secure?

Yes. WireGuard uses state-of-the-art cryptography (ChaCha20, Poly1305, Curve25519). Its small codebase (~4,000 lines) has been formally verified. It’s included in the Linux kernel since version 5.6.

Can I run WireGuard and Tailscale simultaneously?

Not recommended. Both create WireGuard interfaces and can conflict. Use one or the other.

How do I add a new client?

Generate a new key pair, add a [Peer] section to the server config, create a client config file, and restart WireGuard: sudo wg-quick down wg0 && sudo wg-quick up wg0.

What’s the performance overhead?

Negligible. WireGuard adds ~1ms latency and achieves near-line-rate throughput. It’s significantly faster than OpenVPN.

Can I access my LAN devices through WireGuard?

Yes. Include your LAN subnet in the client’s AllowedIPs (e.g., 192.168.1.0/24) and ensure the server has IP forwarding enabled and proper routing/NAT rules.

Next Steps