WireGuard VPN Setup for Secure Remote Access to Your Infrastructure

Deploy WireGuard VPN for secure remote access to self-hosted services. Covers server setup, client configuration, split tunneling, DNS, and mobile access...

Y
Yash Pritwani
12 min read

One owner, one affected system, and the next buyer or recovery deadline mapped.

Why WireGuard?

WireGuard is the modern VPN protocol that replaced OpenVPN and IPsec for most use cases. It is:

Fast: Runs in the Linux kernel, ~3x faster than OpenVPN
Simple: ~4,000 lines of code vs OpenVPN's ~100,000
Secure: Modern cryptography (Curve25519, ChaCha20, Poly1305)
Lightweight: Minimal battery drain on mobile devices

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 180" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="180" rx="12" fill="#1a1a2e"/><circle cx="60" cy="90" r="20" fill="none" stroke="#3b82f6" stroke-width="2"/><text x="60" y="94" text-anchor="middle" fill="#3b82f6" font-size="11" font-family="system-ui">User</text><rect x="120" y="65" width="95" height="50" rx="8" fill="#6366f1" opacity="0.85"/><text x="167" y="85" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Identity</text><text x="167" y="100" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Verify</text><rect x="250" y="65" width="95" height="50" rx="8" fill="#a855f7" opacity="0.85"/><text x="297" y="85" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Policy</text><text x="297" y="100" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Engine</text><rect x="380" y="65" width="95" height="50" rx="8" fill="#2dd4bf" opacity="0.85"/><text x="427" y="85" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Access</text><text x="427" y="100" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Proxy</text><rect x="510" y="65" width="60" height="50" rx="8" fill="#f59e0b" opacity="0.85"/><text x="540" y="94" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">App</text><defs><marker id="arrow5" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="82" y1="90" x2="118" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><line x1="217" y1="90" x2="248" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><line x1="347" y1="90" x2="378" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><line x1="477" y1="90" x2="508" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><text x="167" y="140" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">MFA + Device</text><text x="297" y="140" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">Least Privilege</text><text x="427" y="140" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">Encrypted Tunnel</text><text x="300" y="165" text-anchor="middle" fill="#6366f1" font-size="11" font-family="system-ui" font-weight="bold">Never Trust, Always Verify</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Zero Trust architecture: every request is verified through identity, policy, and access proxy layers.</p></div>

Server Setup

Install WireGuard

# Ubuntu/Debian
sudo apt update && sudo apt install wireguard

# Generate server keys
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
chmod 600 /etc/wireguard/server_private.key

Server Configuration

# /etc/wireguard/wg0.conf
[Interface]
PrivateKey = SERVER_PRIVATE_KEY_HERE
Address = 10.0.0.1/24
ListenPort = 51820
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

# Peer 1: Laptop
[Peer]
PublicKey = CLIENT1_PUBLIC_KEY_HERE
AllowedIPs = 10.0.0.2/32

# Peer 2: Phone
[Peer]
PublicKey = CLIENT2_PUBLIC_KEY_HERE
AllowedIPs = 10.0.0.3/32

# Peer 3: Tablet
[Peer]
PublicKey = CLIENT3_PUBLIC_KEY_HERE
AllowedIPs = 10.0.0.4/32

Enable IP Forwarding

echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

Start WireGuard

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

# Verify
sudo wg show

Client Setup

Generate Client Keys

# On the server or locally
wg genkey | tee client_private.key | wg pubkey > client_public.key

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 150" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="150" rx="12" fill="#1a1a2e"/><rect x="30" y="40" width="100" height="55" rx="6" fill="none" stroke="#3b82f6" stroke-width="1.5"/><text x="80" y="60" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="monospace">Hello World</text><text x="80" y="80" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">Plaintext</text><rect x="175" y="30" width="90" height="75" rx="8" fill="#6366f1" opacity="0.85"/><text x="220" y="55" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Encrypt</text><text x="220" y="72" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">AES-256</text><text x="220" y="92" text-anchor="middle" fill="#f59e0b" font-size="20" font-family="system-ui">&#x1f511;</text><rect x="310" y="40" width="100" height="55" rx="6" fill="none" stroke="#a855f7" stroke-width="1.5"/><text x="360" y="60" text-anchor="middle" fill="#a855f7" font-size="10" font-family="monospace">x8f2...k9z</text><text x="360" y="80" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">Ciphertext</text><rect x="455" y="30" width="90" height="75" rx="8" fill="#2dd4bf" opacity="0.85"/><text x="500" y="55" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Decrypt</text><text x="500" y="72" text-anchor="middle" fill="#1a1a2e" font-size="9" font-family="system-ui">AES-256</text><text x="500" y="92" text-anchor="middle" fill="#f59e0b" font-size="20" font-family="system-ui">&#x1f511;</text><defs><marker id="arrow6" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="132" y1="67" x2="173" y2="67" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow6)"/><line x1="267" y1="67" x2="308" y2="67" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow6)"/><line x1="412" y1="67" x2="453" y2="67" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow6)"/><text x="300" y="130" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">Symmetric Encryption: same key encrypts and decrypts</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Encryption transforms readable plaintext into unreadable ciphertext, reversible only with the correct key.</p></div>

Linux Client

# /etc/wireguard/wg0.conf
[Interface]
PrivateKey = CLIENT_PRIVATE_KEY_HERE
Address = 10.0.0.2/24
DNS = 10.0.0.1

[Peer]
PublicKey = SERVER_PUBLIC_KEY_HERE
Endpoint = vpn.example.com:51820
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24  # Split tunnel
PersistentKeepalive = 25

macOS Client

# Install via Homebrew
brew install wireguard-tools

# Or use the App Store WireGuard app and import the config file

Mobile (QR Code)

Generate a QR code for easy mobile setup:

# Install qrencode
sudo apt install qrencode

# Generate QR code from config
qrencode -t ansiutf8 < /etc/wireguard/clients/phone.conf

Scan the QR code with the WireGuard mobile app and connect instantly.

Split Tunneling

Route only internal traffic through the VPN (better performance):

# In client config - only route these networks through VPN
[Peer]
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24

Route all traffic through the VPN (better security on public WiFi):

# In client config - route everything through VPN
[Peer]
AllowedIPs = 0.0.0.0/0, ::/0

DNS Configuration

Run a DNS server on the VPN server for internal name resolution:

# docker-compose.yml
services:
  pihole:
    image: pihole/pihole:latest
    environment:
      WEBPASSWORD: your-password
      PIHOLE_DNS_: "1.1.1.1;8.8.8.8"
    volumes:
      - pihole_data:/etc/pihole
    ports:
      - "10.0.0.1:53:53/tcp"
      - "10.0.0.1:53:53/udp"
    restart: unless-stopped

Add custom DNS entries for internal services:

# In Pi-hole custom DNS
192.168.1.101 git.internal
192.168.1.101 n8n.internal
192.168.1.101 grafana.internal

Automated Client Provisioning

#!/bin/bash
# add-vpn-client.sh
CLIENT_NAME="\$1"
CLIENT_IP="\$2"
SERVER_PUBLIC_KEY="\$(cat /etc/wireguard/server_public.key)"
SERVER_ENDPOINT="vpn.example.com:51820"

# Generate keys
CLIENT_PRIVATE=\$(wg genkey)
CLIENT_PUBLIC=\$(echo "\$CLIENT_PRIVATE" | wg pubkey)

# Create client config
mkdir -p /etc/wireguard/clients
cat > "/etc/wireguard/clients/\$CLIENT_NAME.conf" << CONF
[Interface]
PrivateKey = \$CLIENT_PRIVATE
Address = \$CLIENT_IP/24
DNS = 10.0.0.1

[Peer]
PublicKey = \$SERVER_PUBLIC_KEY
Endpoint = \$SERVER_ENDPOINT
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24
PersistentKeepalive = 25
CONF

# Add peer to server
cat >> /etc/wireguard/wg0.conf << PEER

# \$CLIENT_NAME
[Peer]
PublicKey = \$CLIENT_PUBLIC
AllowedIPs = \$CLIENT_IP/32
PEER

# Reload WireGuard
wg syncconf wg0 <(wg-quick strip wg0)

# Generate QR code
qrencode -t ansiutf8 < "/etc/wireguard/clients/\$CLIENT_NAME.conf"

echo "Client \$CLIENT_NAME configured with IP \$CLIENT_IP"

Usage:

./add-vpn-client.sh laptop 10.0.0.2
./add-vpn-client.sh phone 10.0.0.3

Security Best Practices

1. Use unique keys per device: Never share private keys between devices 2. Restrict AllowedIPs: Only allow the specific IPs each client needs 3. Firewall the VPN port: Only allow UDP 51820 from expected locations 4. Rotate keys periodically: Generate new key pairs every 6-12 months 5. Monitor connections: Check wg show regularly for unknown peers 6. Use PersistentKeepalive: Set to 25 for clients behind NAT

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 220" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="220" rx="12" fill="#1a1a2e"/><path d="M300,25 L380,55 L380,120 Q380,170 300,195 Q220,170 220,120 L220,55 Z" fill="none" stroke="#6366f1" stroke-width="2.5"/><path d="M300,40 L365,65 L365,118 Q365,160 300,180 Q235,160 235,118 L235,65 Z" fill="#6366f1" opacity="0.15"/><rect x="280" y="95" width="40" height="30" rx="4" fill="#6366f1" opacity="0.9"/><path d="M288,95 L288,82 Q288,72 300,72 Q312,72 312,82 L312,95" fill="none" stroke="#6366f1" stroke-width="2.5"/><circle cx="300" cy="110" r="4" fill="#ffffff"/><text x="90" y="60" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="system-ui">Firewall</text><line x1="130" y1="57" x2="218" y2="57" stroke="#3b82f6" stroke-width="1" stroke-dasharray="3,3"/><text x="90" y="100" text-anchor="middle" fill="#a855f7" font-size="10" font-family="system-ui">WAF</text><line x1="110" y1="97" x2="220" y2="85" stroke="#a855f7" stroke-width="1" stroke-dasharray="3,3"/><text x="90" y="140" text-anchor="middle" fill="#2dd4bf" font-size="10" font-family="system-ui">SSO / MFA</text><line x1="130" y1="137" x2="222" y2="120" stroke="#2dd4bf" stroke-width="1" stroke-dasharray="3,3"/><text x="510" y="60" text-anchor="middle" fill="#f59e0b" font-size="10" font-family="system-ui">TLS/SSL</text><line x1="470" y1="57" x2="382" y2="57" stroke="#f59e0b" stroke-width="1" stroke-dasharray="3,3"/><text x="510" y="100" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="system-ui">RBAC</text><line x1="490" y1="97" x2="380" y2="85" stroke="#3b82f6" stroke-width="1" stroke-dasharray="3,3"/><text x="510" y="140" text-anchor="middle" fill="#a855f7" font-size="10" font-family="system-ui">Audit Logs</text><line x1="470" y1="137" x2="378" y2="120" stroke="#a855f7" stroke-width="1" stroke-dasharray="3,3"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Defense in depth: multiple security layers protect your infrastructure from threats.</p></div>

Performance

WireGuard adds minimal overhead:

Metric
Direct
WireGuard
OpenVPN

|--------|--------|-----------|---------|

Throughput
940 Mbps
880 Mbps
320 Mbps
Latency
1ms
+0.5ms
+2ms
CPU usage
-
2%
15%

At TechSaaS, we use WireGuard to provide secure remote access to our self-hosted infrastructure. Engineers can access internal services from anywhere while keeping everything off the public internet.

Need secure remote access to your infrastructure? Contact [email protected].

#wireguard#vpn#security#remote-access#networking

Need the next owner and evidence step mapped?

Send the current system and deadline. Yash replies with the service path, first proof artifact, and handoff owner.