WireGuard Self-Hosted VPN on Proxmox
By LK Wood IV · 2026-06-13 · ~11 min read · St. Louis County, MO
Tailscale is easier. If you want the 15-minute path to remote access, the Tailscale tutorial is the right read. WireGuard is for when you want zero coordinator: your server, your key pair, no third-party knowing which clients are connected.
This guide sets up a WireGuard server in a Proxmox LXC container, generates client configs for phones and laptops, and covers the split vs full tunnel decision.
What you’ll have at the end
- WireGuard server running in a Proxmox LXC, listening on UDP port 51820
- Clients (phone, laptop) that can reach your homelab from anywhere
- Your choice of split tunnel (access homelab only) or full tunnel (all traffic through home)
- Dynamic DNS hostname so it works when your home IP changes
Prerequisites
- Proxmox VE 8 host on a home network with internet access
- A port forward on your router: UDP 51820 → your WireGuard LXC IP
- Optionally: a DuckDNS or Cloudflare DNS hostname for your home IP
Verify WireGuard kernel module on the host:
# On the Proxmox host (not inside an LXC)
lsmod | grep wireguard
# Should show: wireguard
# If not, load it:
modprobe wireguard
echo "wireguard" >> /etc/modules
Step 1: Create the LXC container
In Proxmox web UI → Create CT:
Hostname: wireguard
Template: Debian 12 (bookworm)
Storage: 4GB disk
RAM: 256MB
vCPU: 1
Network: Static IP (e.g., 192.168.1.7/24)
Privileged: yes (simplest for net_admin capability)
Start the container, then inside it:
apt update && apt install -y wireguard qrencode
Step 2: Generate server key pair
# Inside the WireGuard LXC
cd /etc/wireguard
umask 077
# Generate server private and public keys
wg genkey | tee server_private.key | wg pubkey > server_public.key
cat server_private.key
cat server_public.key
Keep server_private.key secure — anyone with the private key can decrypt traffic.
Step 3: Create the server config
nano /etc/wireguard/wg0.conf
[Interface]
# The WireGuard server's IP on the VPN subnet
Address = 10.10.0.1/24
ListenPort = 51820
PrivateKey = <paste server_private.key here>
# Enable IP forwarding and NAT for client traffic
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Clients are added below as [Peer] blocks
The 10.10.0.0/24 subnet is the WireGuard VPN subnet — separate from your home LAN (192.168.1.0/24). The server gets 10.10.0.1; clients get 10.10.0.2, 10.10.0.3, etc.
Enable IP forwarding:
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p
Start and enable WireGuard:
systemctl enable --now wg-quick@wg0
# Verify
wg show
Step 4: Generate client configs
For each client (phone, laptop, etc.) generate a key pair and config. Do this on the server for convenience:
# Generate client keys (do this for each client)
wg genkey | tee client1_private.key | wg pubkey > client1_public.key
cat client1_private.key # goes in the client config
cat client1_public.key # goes in the server config as a [Peer]
Client config (save to a file or display as QR code):
[Interface]
Address = 10.10.0.2/32
PrivateKey = <client1_private.key>
DNS = 192.168.1.5 # your AdGuard Home IP, or 1.1.1.1 if not running AdGuard
[Peer]
# The WireGuard server
PublicKey = <server_public.key>
Endpoint = your-home.duckdns.org:51820
# Split tunnel: access homelab only
AllowedIPs = 192.168.1.0/24, 10.10.0.0/24
# Full tunnel: all traffic through home (uncomment instead)
# AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
PersistentKeepalive = 25 keeps the connection alive through NAT — without it, an idle connection behind NAT may drop after a few minutes.
Add the client to the server config:
# Append to /etc/wireguard/wg0.conf on the server
cat >> /etc/wireguard/wg0.conf << EOF
[Peer]
# Client 1 (phone)
PublicKey = <client1_public.key>
AllowedIPs = 10.10.0.2/32
EOF
# Reload WireGuard (no downtime for other clients)
wg syncconf wg0 <(wg-quick strip wg0)
Display the client config as a QR code (scan with the WireGuard app on iOS/Android):
# Create the client config as a file first
cat > /tmp/client1.conf << 'EOF'
[Interface]
Address = 10.10.0.2/32
PrivateKey = <client1_private.key>
DNS = 192.168.1.5
[Peer]
PublicKey = <server_public.key>
Endpoint = your-home.duckdns.org:51820
AllowedIPs = 192.168.1.0/24, 10.10.0.0/24
PersistentKeepalive = 25
EOF
qrencode -t ansiutf8 < /tmp/client1.conf
# Or save as PNG:
qrencode -t PNG -o /tmp/client1.png < /tmp/client1.conf
Delete the client private key file from the server after you’ve scanned it — it should only exist on the client.
Step 5: Set up dynamic DNS
If your home IP changes (most residential ISPs assign dynamic IPs), use a free DDNS service:
DuckDNS (free, simple):
- Go to duckdns.org, create an account, create a domain:
yourname.duckdns.org - Create an update script:
mkdir -p /opt/duckdns
cat > /opt/duckdns/duck.sh << 'EOF'
#!/bin/bash
echo url="https://www.duckdns.org/update?domains=yourname&token=YOUR-TOKEN&ip=" | curl -k -o /opt/duckdns/duck.log -K -
EOF
chmod +x /opt/duckdns/duck.sh
# Add to cron — update every 5 minutes
echo "*/5 * * * * root /opt/duckdns/duck.sh >/dev/null 2>&1" >> /etc/cron.d/duckdns
- Use
yourname.duckdns.org:51820as the Endpoint in all your client configs.
Step 6: Set up the port forward
On your router (OPNsense, pfSense, or consumer firmware):
- Forward UDP port 51820 to your WireGuard LXC IP (
192.168.1.7)
In OPNsense: Firewall → NAT → Port Forward → Add:
- Interface: WAN
- Protocol: UDP
- Destination port: 51820
- Redirect target: 192.168.1.7, port 51820
Verify the connection
On a client, after importing the config:
- Enable the WireGuard tunnel
- Check that you can reach the server:
ping 10.10.0.1 - For split tunnel: verify you can reach homelab devices:
ping 192.168.1.2(Proxmox host) - For full tunnel: verify your exit IP: visit a “what is my IP” site — it should show your home IP
On the server, wg show displays connected peers, their last handshake time, and transfer stats. A handshake within the last 3 minutes means the client is actively connected.
Adding AdGuard Home as VPN DNS
If you’re running AdGuard Home for DNS filtering and local hostname resolution, set it as the DNS in client configs:
[Interface]
DNS = 192.168.1.5 # AdGuard Home IP
This lets VPN clients:
- Resolve
pve.lan,nas.lan, and other local hostnames - Get DNS-level ad blocking while connected to the VPN
- Use AdGuard’s split DNS without a separate Tailscale configuration
Split tunnel vs full tunnel
Split tunnel (AllowedIPs = 192.168.1.0/24, 10.10.0.0/24):
- Only homelab traffic goes through the VPN
- Internet traffic uses the local connection at the client’s location
- Faster for general browsing; no impact on video streaming
- Can’t browse the web through your home IP
Full tunnel (AllowedIPs = 0.0.0.0/0, ::/0):
- All traffic routes through the WireGuard server and your home network
- Use your home IP as the exit node
- Useful on untrusted networks (airports, hotels) or for geo-unlocking
- Your home upload bandwidth becomes the internet speed limit for clients
For a homelab VPN where the goal is remote access to services, split tunnel is usually the right choice — it’s faster and doesn’t load your home upload connection with all internet traffic.
WireGuard vs Tailscale: the honest comparison
| WireGuard (self-hosted) | Tailscale | |
|---|---|---|
| Setup time | 30–60 min | 15 min |
| Third-party dependency | None | Tailscale coordinator |
| Works behind double NAT | Requires port forward on one side | Yes, fully automatic |
| Client count | Unlimited | 100 free, 3 users |
| Key management | Manual | Automatic |
| ACLs / per-client rules | Manual iptables | Tailscale ACL policy |
| Mobile app | Official WireGuard app | Tailscale app |
| Local DNS integration | Manual (set DNS per client) | MagicDNS, automatic |
Tailscale wins on simplicity and NAT traversal. WireGuard wins on control, privacy (no coordinator logs), and flexibility (full tunnel, custom DNS, any subnet routing).
If you have OPNsense already, it has a built-in WireGuard plugin that eliminates the need for a separate LXC container — it runs directly on the firewall and gets port forwarded automatically.
Running VLANs? See the VLAN homelab guide for segmenting your WireGuard clients from IoT and trusted LAN traffic. For OPNsense specifically, the OPNsense mini PC setup guide covers the WireGuard plugin under Firewall → WireGuard.