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

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 help with security?

TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.