Quick answer

Inside a privileged Debian 12 LXC on Proxmox VE 8, wireguard-tools and a wg0.conf at 10.10.0.1/24 listening on UDP 51820 are the entire server, since the kernel module comes from the Proxmox host rather than the container. Clients take 10.10.0.2 upward with PersistentKeepalive 25, a DuckDNS endpoint, and split-tunnel AllowedIPs of 192.168.1.0/24, 10.10.0.0/24.

Contents

By LK Wood IV · Published 2026-05-27 · Updated 2026-10-12 · ~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 IPv4 full tunnel (IPv4 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

Check inbound IPv4 reachability first. Compare the router’s WAN IPv4 address with the public IPv4 shown by an external IP-check service. A WAN address in 100.64.0.0/10 or a private range, or a mismatch, indicates upstream NAT that needs investigation. With your own upstream router, forward UDP 51820 at both layers. With ISP carrier-grade NAT, request a reachable public IPv4 or use an outbound mesh/relay design such as Tailscale. DDNS updates a name; it cannot open an ISP’s NAT. Test from cellular data or another external network, because a LAN test may depend on NAT loopback.

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

Watch the two-peer setup

WireGuard’s official quick-start demonstration shows two peers configured side by side. It covers WireGuard itself; follow the steps below for the Proxmox LXC and router settings. Use the player’s full-screen control to read the terminal text on a phone.

Two-peer command-line demonstration by the WireGuard project. The video loads when you start playback.

Written sequence: generate a private/public key pair for each peer; create and address each wg0 interface; configure each interface with its own private key plus the other peer’s public key, allowed IPs, and endpoint where needed; bring both interfaces up; then inspect them with wg show. Keep private keys local; exchange public keys. The quick start explains each command.

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 (this recipe's isolation tradeoff; see below)

In a privileged LXC, root inside the container maps to root on the host. A container escape can therefore compromise the Proxmox host and its guests. Keep this container dedicated and patched, retain the default AppArmor restrictions, and expose only the VPN port. Proxmox’s container security documentation recommends unprivileged containers for better isolation. If the required network operations fail in an unprivileged container on your host, use a VM rather than removing host security controls. This guide does not validate a separate unprivileged configuration.

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 and wg0.conf root-owned with mode 0600. A stolen private key lets an attacker impersonate that peer in new connections; rotate the key and update its peers. WireGuard’s forward secrecy means disclosure of a long-term private key alone does not decrypt previously recorded sessions. A compromised endpoint can still expose plaintext and active session secrets.

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 Address = 10.10.0.1/24 line defines the VPN subnet, and every client’s AllowedIPs (your LAN such as 192.168.1.0/24, or 0.0.0.0/0 for full-tunnel) is also a CIDR block. The subnet & CIDR calculator makes it easy to get those masks and host ranges right. It also helps you pick a VPN subnet that won’t overlap the LAN you’re routing to.

The 10.10.0.0/24 subnet is the WireGuard VPN subnet. It is 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
# IPv4 full tunnel (uncomment instead; see IPv6 caveat below)
# AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

PersistentKeepalive = 25 keeps the connection alive through NAT, the address translation your router does. 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):

  1. Go to duckdns.org, create an account, create a domain: yourname.duckdns.org
  2. Create an update script:
install -d -o root -g root -m 0700 /opt/duckdns
umask 077
# Keep the token out of command arguments and in a root-only curl config.
cat > /opt/duckdns/update.conf << 'EOF'
url = "https://www.duckdns.org/update?domains=yourname&token=YOUR-TOKEN&ip="
EOF
cat > /opt/duckdns/duck.sh << 'EOF'
#!/bin/bash
set -euo pipefail
response=$(curl --fail --silent --show-error --max-time 30 \
  --proto '=https' --config /opt/duckdns/update.conf)
if [[ "$response" != "OK" ]]; then
  logger -p user.err -t duckdns 'DuckDNS update failed; check the protected config'
  exit 1
fi
EOF
chown root:root /opt/duckdns/update.conf /opt/duckdns/duck.sh
chmod 0600 /opt/duckdns/update.conf
chmod 0700 /opt/duckdns/duck.sh
/opt/duckdns/duck.sh  # test once; curl verifies the HTTPS certificate

# Add to cron — update every 5 minutes
printf '%s\n' '*/5 * * * * root /opt/duckdns/duck.sh' > /etc/cron.d/duckdns
chown root:root /etc/cron.d/duckdns
chmod 0644 /etc/cron.d/duckdns
  1. Use yourname.duckdns.org:51820 as 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:

  1. Enable the WireGuard tunnel
  2. Check that you can reach the server: ping 10.10.0.1
  3. For split tunnel: verify you can reach homelab devices: ping 192.168.1.2 (Proxmox host)
  4. For IPv4 full tunnel: verify an IPv4 IP-check service shows your home IPv4, then separately check IPv6. An IPv6 result from the remote network is a bypass of this IPv4-only tunnel.

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

IPv4 full tunnel (AllowedIPs = 0.0.0.0/0):

  • IPv4 traffic routes through the WireGuard server and your home network
  • Use your home IPv4 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

The server config enables IPv4 forwarding and IPv4 NAT only. IPv6 is outside this recipe. If the client has IPv6 connectivity, it can bypass the tunnel unless you block it on that client. Verify both address families on each intended client; do not add ::/0 without a tested IPv6 address, forwarding, routing and firewall setup.

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 comparison

WireGuard (self-hosted)Tailscale
Setup time30–60 min15 min
Third-party dependencyNoneTailscale coordinator
Works behind double NATRequires port forward on one sideYes, fully automatic
Client countUnlimitedGenerous device allowance, 6 free users
Key managementManualAutomatic
ACLs / per-client rulesManual iptablesTailscale ACL policy
Mobile appOfficial WireGuard appTailscale app
Local DNS integrationManual (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). For the full decision framework, including which one to pick behind carrier-grade NAT (CGNAT), see Tailscale vs WireGuard: which one should you actually run?.

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.

Sources

Frequently asked questions

WireGuard vs Tailscale: which should I use?
Tailscale if you want something that works in 15 minutes with no server to maintain, peer-to-peer NAT traversal by default, and handles roaming clients well. WireGuard if you want zero third-party coordinator (full control of the VPN server), a fixed entry point (static IP/DDNS on your home network), or you want to route IPv4 internet traffic through your home IP using this guide’s full-tunnel configuration. Tailscale uses WireGuard under the hood but adds the coordination layer. For a homelab that’s already running Proxmox, WireGuard is a natural choice - it’s one LXC container and no ongoing SaaS dependency.
Do I need a static IP for a WireGuard server?
No, but it helps. A dynamic DNS hostname (DuckDNS is free) updates every time your home IP changes — most residential IPs change rarely or not at all. Set the WireGuard server endpoint to your DDNS hostname instead of a static IP. The worst case: after an IP change, re-run ‘wg-quick down/up’ on the client to re-resolve the hostname.
Can I run WireGuard in a Proxmox LXC container?
Yes. The LXC shares the Proxmox host kernel, so verify WireGuard support on the host and install the tools in the container. This recipe uses a privileged Debian 12 LXC; its root identity maps to host root, which weakens isolation. Prefer an unprivileged container where your host configuration supports the required network operations, or a VM for stronger isolation. Adding a capability alone is not a complete unprivileged setup recipe.
How do I route all client traffic through the WireGuard server (full tunnel)?
For the IPv4-only server in this guide, set client AllowedIPs to 0.0.0.0/0 and enable IPv4 forwarding and NAT on the server. This sends IPv4 internet traffic through home. IPv6 can still use the client’s local connection; block IPv6 on the client if that bypass is unacceptable, or configure and test a separate IPv6 routing/firewall design. Adding ::/0 alone does not make this server support IPv6.
How many clients can a WireGuard server handle?
WireGuard is highly efficient. A single LXC container with 1 vCPU and 256MB RAM can handle 10–20 simultaneous VPN clients easily. WireGuard’s cryptographic overhead is low compared to OpenVPN or IPsec. A 1 Gbps home internet connection is more likely to be the bottleneck than the WireGuard server itself.

Sources and corrections

Last updated
Methodology
See our methodology for research and review standards.
Corrections
Spotted an error or a stale number? Email contact@techfuelhq.com. Confirmed corrections are recorded here with the date and change.

About the author

Written by Lowell K. Wood IV, who builds and runs TechFuelHQ from St. Louis, Missouri.