Introduction

You've got a Proxmox VE box running at home, and it's great when you're sitting on the same Wi-Fi as it. The problem shows up the moment you leave the house. Maybe you want to reach a file on your NAS from a coffee shop, or SSH into a container from your phone, or just check that a VM didn't crash overnight while you're at work.

Port forwarding your router straight to that device is the wrong answer, and opening up every service one at a time gets messy fast. The fix most homelab folks land on is running their own VPN server, and WireGuard is the one worth using in 2026. It's fast, it's built into the Linux kernel, and once it's running you stop thinking about "remote access" as a problem at all — your phone just acts like it's on your home network, full stop.

This guide walks through installing WireGuard inside an LXC container on Proxmox VE, rather than on the host itself or in a full VM. That keeps it lightweight, isolated from your other services, and easy to back up or move later. Everything below was tested on Proxmox VE 9.2 with a Debian 13 container template, though the steps are nearly identical on 8.x with Debian 12.

What You Will Learn

  • What WireGuard actually is and why it's a better fit than older VPN protocols for a homelab
  • Why an LXC container is a sensible place to run it, and the one extra step unprivileged containers need
  • How to create the container, install WireGuard, and generate server and client keys
  • How to build a working server config and a matching client config, including a QR code for your phone
  • The router setting you can't skip, and the mistakes that cause "connects but no traffic" problems
  • How to keep the tunnel running after reboots and check that it's actually working

What Is This Feature?

A VPN, short for virtual private network, creates an encrypted tunnel between two devices over the internet. Once that tunnel is up, your laptop or phone behaves as if it's plugged directly into your home network, even if you're on hotel Wi-Fi three time zones away.

WireGuard is a specific VPN protocol, and it's a fairly recent one as these things go. Older VPN technology like OpenVPN or IPsec is flexible but heavy, with a lot of code and a lot of configuration knobs. WireGuard took the opposite approach: a small, auditable codebase (a few thousand lines versus OpenVPN's tens of thousands), modern cryptography with no configurable options to get wrong, and a design simple enough that it was merged directly into the Linux kernel back in version 5.6. That last part matters practically — it means the fast path of the tunnel runs in kernel space, not as a regular userspace process, so it's noticeably quicker and lighter on CPU than the alternatives.

An LXC container, in case you haven't used one yet, is a lightweight form of virtualization that shares the host's Linux kernel instead of running its own. Compare that to a full VM, which boots its own kernel and behaves like a completely separate machine. Containers start in a couple of seconds, use a fraction of the RAM a VM would for the same job, and are a natural fit for a single-purpose service like a VPN endpoint.

Why Would You Use It?

Because the alternative — forwarding individual ports for every service you want to reach — adds up fast and each one is its own little attack surface. A VPN collapses that down to one thing: one WireGuard port open on your router, and everything behind it becomes reachable only to devices that have a valid key. No key, no access, full stop.

Running it in its own LXC container instead of on the Proxmox host itself keeps things clean. If you ever want to snapshot it, move it to a different node, or just nuke it and start over, it's completely separate from your hypervisor's own configuration. I'd avoid putting a VPN server directly on the Proxmox host — it works, but it mixes your management plane with a service that's now facing (a small slice of) the internet, and there's no good reason to do that when a container costs you almost nothing.

It's also just fast. WireGuard connections establish in under a second, and on typical home internet speeds you won't notice the encryption overhead at all. This isn't the fifteen-second-handshake VPN experience a lot of people remember from a decade ago.

Prerequisites

  • Proxmox VE 8.x or 9.x with a Debian 12 or 13 container template already downloaded (from Datacenter → Storage → CT Templates, or pveam update from the shell)
  • Root or sudo access on the Proxmox host
  • Access to your router's admin page, to forward one UDP port
  • About 20 minutes
  • A phone or laptop to test the connection from once it's running

You don't need a static public IP for this to work day to day, but if your ISP changes your address often, plan on pairing this with a dynamic DNS service later so you're not hunting for your current IP every time you connect.

Step-by-Step Tutorial

1. Create the container

From the Proxmox shell, run pct create with a Debian template, or use the Create CT wizard in the web interface. Either way, give it modest resources — WireGuard barely uses any CPU or RAM even under load:

pct create 201 local:vztmpl/debian-13-standard_13.0-1_amd64.tar.zst \
  --hostname wireguard \
  --cores 1 \
  --memory 256 \
  --rootfs local-lvm:4 \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp \
  --unprivileged 1

An unprivileged container is the right default for almost everything you run in Proxmox, WireGuard included — it maps the container's root user to an unprivileged user on the host, so a compromise inside the container doesn't hand over host-level access. The tradeoff is that a couple of kernel-level features need to be explicitly allowed through, which is the next step.

Start the container once it's created:

pct start 201

2. Give the container access to the tun device

WireGuard needs a virtual network device called /dev/net/tun to build its tunnel, and by default an unprivileged container can't touch device nodes on the host. Edit the container's config file directly on the Proxmox host:

nano /etc/pve/lxc/201.conf

Add these two lines at the bottom:

lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net dev/net none bind,create=dir

The first line allows the container to use character device 10:200, which is the tun device's major and minor number on nearly every current Linux system. The second line bind-mounts the host's /dev/net directory into the container so the device node actually shows up inside it. Restart the container for the change to take effect:

pct stop 201 && pct start 201

3. Install WireGuard inside the container

Enter the container's console:

pct enter 201

Update package lists and install the WireGuard tools:

apt update && apt install -y wireguard qrencode

wireguard pulls in wireguard-tools, which gives you the wg and wg-quick commands. qrencode is optional but genuinely useful — it turns a client config into a QR code you can scan directly with the WireGuard phone app instead of typing anything.

4. Generate the server's key pair

WireGuard authenticates peers with public-key cryptography instead of passwords. Generate the server's private and public key:

cd /etc/wireguard
umask 077
wg genkey | tee server_private.key | wg pubkey > server_public.key

The umask 077 matters more than it looks — without it, the key files would be readable by any user in the container. wg genkey writes a private key, and piping it into wg pubkey derives the matching public key without ever writing the private key to disk in plain form outside that one file.

5. Write the server configuration

Create /etc/wireguard/wg0.conf:

[Interface]
Address = 10.10.10.1/24
ListenPort = 51820
PrivateKey = <paste contents of 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

Address is the private IP range for the VPN itself — a small subnet just for WireGuard traffic, separate from your home LAN, so there's no addressing conflict. ListenPort is the UDP port clients connect to; 51820 is WireGuard's informal default, though it doesn't have to be. The PostUp/PostDown lines add and remove firewall rules automatically whenever the tunnel comes up or down, so traffic from VPN clients actually gets routed out to the rest of your network instead of dead-ending inside the container.

You'll add a [Peer] block for each client a bit further down, once you've generated its keys.

6. Turn on IP forwarding

By default, Linux won't route traffic between network interfaces — it'll happily receive packets on wg0 but drop them instead of passing them along to eth0. Enable forwarding permanently by editing /etc/sysctl.conf and uncommenting or adding:

net.ipv4.ip_forward=1

Apply it immediately without a reboot:

sysctl -p

7. Generate a client configuration

Back in /etc/wireguard, generate a key pair for your first client (your phone, say):

wg genkey | tee client1_private.key | wg pubkey > client1_public.key

Add a matching peer block to the bottom of wg0.conf on the server:

[Peer]
PublicKey = <paste contents of client1_public.key>
AllowedIPs = 10.10.10.2/32

Then create the client's own config file, for example client1.conf:

[Interface]
PrivateKey = <paste contents of client1_private.key>
Address = 10.10.10.2/24
DNS = 1.1.1.1

[Peer]
PublicKey = <paste contents of server_public.key>
Endpoint = your-public-ip-or-ddns-hostname:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

AllowedIPs = 0.0.0.0/0 tells the client to send all of its traffic through the tunnel, which is the usual setup for "access my home network from anywhere." If you only want to reach specific devices at home rather than routing everything through your house, narrow this to your LAN subnet instead, something like 192.168.1.0/24, 10.10.10.0/24. PersistentKeepalive sends a tiny packet every 25 seconds so the connection survives NAT timeouts on mobile networks — without it, your phone's tunnel can silently go quiet after a few minutes on cellular data.

Turn that config into a QR code you can scan with the WireGuard app on iOS or Android:

qrencode -t ansiutf8 < client1.conf

8. Forward the port and start the tunnel

On your router, forward UDP port 51820 to the container's IP address on your LAN. This is the one port you're opening — everything else stays closed.

Start the interface and enable it so it survives a reboot:

systemctl enable --now wg-quick@wg0

Check that it's actually up:

wg show

You should see the interface, its listening port, and the peer you configured, with a "latest handshake" field that fills in once a client actually connects.

Commands Explained

  • pct create / pct enter — Proxmox's command-line tool for managing LXC containers; create builds one from a template, enter drops you into its shell.
  • wg genkey / wg pubkey — generate a Curve25519 private key and derive its matching public key. There's no separate "certificate authority" step like you'd see with OpenVPN; the key pair is the whole identity.
  • wg-quick@wg0 — a systemd service that reads /etc/wireguard/wg0.conf, brings up the wg0 interface, assigns its address, and runs any PostUp/PostDown commands. The wg0 part matches the config filename.
  • wg show — prints the live state of any WireGuard interfaces on the machine: peers, handshake times, and data transferred.
  • sysctl -p — reloads kernel parameters from /etc/sysctl.conf without needing a reboot.
  • iptables -t nat -A POSTROUTING -j MASQUERADE — rewrites the source address of outgoing packets so replies find their way back through the container instead of getting lost trying to reach a private VPN IP directly.

Common Errors

"RTNETLINK answers: Operation not permitted" when starting wg-quick — almost always means the lxc.cgroup2.devices.allow and lxc.mount.entry lines from step 2 are missing or the container wasn't restarted after adding them. Double check /etc/pve/lxc/201.conf and restart the container.

Client shows "Handshake did not complete after 5 seconds" — usually a port forwarding problem. Confirm the router is actually forwarding UDP (not TCP) 51820 to the container's current LAN IP, and that no local firewall on the Proxmox host or container is blocking it.

Tunnel connects but you can't reach anything on your LAN — check that IP forwarding is enabled (step 6) and that the MASQUERADE rule references your container's actual outbound interface, which might be eth0 or something else depending on how you set up networking.

"Address already in use" on wg-quick up — another process, sometimes a leftover wg0 interface from a previous attempt, is holding the interface. Run wg-quick down wg0 first, confirm with ip link show wg0 that it's gone, then try again.

Troubleshooting

If wg show shows a peer but the handshake time never updates, the client almost certainly can't reach the server's public endpoint at all — that's a router or ISP problem, not a WireGuard configuration problem. Test from an external network (your phone on cellular data with Wi-Fi off is an easy way to do this) rather than from inside your own LAN, since some routers don't correctly handle a device reaching its own public IP from the inside (a quirk usually called NAT loopback or hairpin NAT).

If everything looks right but browsing is slow or times out intermittently, check the MTU. WireGuard's overhead can occasionally push packets over the effective MTU of certain connections, particularly on DSL or CGNAT-heavy mobile networks. Adding MTU = 1420 under the client's [Interface] section is a common fix.

If you rebuilt the container and lost your keys, that's fine — there's no recovery step needed. Just regenerate a fresh key pair on the server and on each client, and update both sides. Nothing about WireGuard's keys is tied to any external identity, so there's nothing else to reissue.

Best Practices

  • Give every client its own key pair and its own /32 address in AllowedIPs. Don't reuse one client config across multiple devices — if a phone gets lost, you want to revoke just that one peer.
  • Pick a WireGuard subnet, like 10.10.10.0/24, that doesn't overlap with your home LAN or any other VPN you already run.
  • Back up /etc/wireguard on the container (or just snapshot the whole container in Proxmox) once your keys are generated. Losing the server's private key means regenerating configs for every single client.
  • Restrict AllowedIPs on client configs to just what they need if you don't want every device routing all its traffic through your house. It's a small change with a real privacy and bandwidth benefit.
  • Keep the container's Debian install patched with regular apt update && apt upgrade, same as you would on the Proxmox host itself.

Frequently Asked Questions

Do I need a static public IP for this to work?

No, but you'll need to look up your current IP each time it changes unless you set up dynamic DNS, which just gives you a hostname that automatically updates to point at your current address.

Is WireGuard actually secure enough for this?

Yes. It uses modern, non-configurable cryptography (ChaCha20 for encryption, Curve25519 for key exchange) that's been extensively reviewed, and it's the VPN protocol underneath commercial services like Tailscale and Mullvad.

Can I run this on the Proxmox host directly instead of in a container?

Technically yes, but I wouldn't. Keeping it in its own LXC container means a problem with the VPN never touches your hypervisor, and you can snapshot or migrate it independently.

What's the difference between this and just using Tailscale?

Tailscale is built on WireGuard and handles key exchange and NAT traversal for you automatically, which is easier to set up. Running your own WireGuard server like this gives you full control over the server and no dependency on a third party's coordination service, at the cost of managing the keys and port forwarding yourself.

How many clients can one WireGuard server handle?

Far more than a typical homelab needs. A single wg0 interface can handle dozens of peers without any real performance impact — the constraint you'll hit first is your home upload bandwidth, not WireGuard itself.

Conclusion

Once this is running, "remote access to my homelab" stops being a recurring problem you solve one service at a time. Your phone connects, and everything behind your router — Proxmox's web interface, your NAS, SSH into any container — is just there, the same as if you were standing in your living room.

The container itself costs you almost nothing in resources, and because it's isolated from the Proxmox host, you can experiment with client configs or even break the whole thing without any risk to your actual hypervisor. Add a second client, hand it to someone else in your household, and you've got a private VPN that didn't cost a monthly subscription.