remote-backups.comremote-backups.com
Contact illustration
Sign In
Don't have an account ?Sign Up

PBS over WireGuard: Secure Offsite Tunnels

Putting a Proxmox Backup Server on a public IP and calling it a day is a tempting shortcut. It also puts your entire backup infrastructure one zero-day away from a bad week. WireGuard solves this without the configuration overhead of OpenVPN or the brittleness of IPsec. This post walks through how to wire up encrypted PBS connectivity from client sites to an offsite backup target, with a multi-tenant layout that scales to dozens of clients.

Key Takeaways
  • Exposing PBS port 8007 publicly is unnecessary and increases attack surface; WireGuard gives you encrypted reachability without it
  • WireGuard is kernel-resident, has no listening port when idle, and uses key-based peer auth with no per-user accounts
  • Hub-and-spoke topology fits MSP work: one central PBS as the hub, every PVE site as a spoke, isolated by namespace
  • Bind PBS to the wg0 interface so even if firewall rules fail open, the public NIC has nothing listening on 8007
  • Preshared keys add a second symmetric layer on top of WireGuard's asymmetric keys; cheap insurance against future protocol breaks
  • If managing the PBS node and tunnels yourself is not the goal, remote-backups.com offers a managed PBS endpoint with WireGuard access included

Why Not Just Expose PBS Publicly?

Proxmox Backup Server runs both the web UI and the backup API on a single port: 8007/TCP. That port is the entire administrative attack surface of the box. Exposing it to the public internet has three concrete problems.

Attack surface. Every CVE in the PBS web stack, the underlying Perl runtime, or the embedded HTTP layer becomes a directly exploitable issue. Most homelab and MSP deployments do not have a SOC watching for exploit attempts on 8007.

Certificate complexity. PBS ships with a self-signed certificate. To make TLS pinning work for clients, you either burn time on a real ACME setup or accept that client config files contain certificate fingerprints that change every renewal. With WireGuard handling transport security, the PBS cert only needs to be valid inside the tunnel, where self-signed is fine.

Firewall sprawl. Each client site needs allow-list rules pointing at the PBS public IP. A new MSP client means a firewall change request on both ends. Static IPs change. NAT gateways get rebuilt. This is the kind of work that quietly degrades over six months until someone runs proxmox-backup-client and gets a connection refused.

PBS has no built-in IP allowlisting

PBS does not ship with native source-IP filtering for port 8007. You need an external firewall, fail2ban, or a network-layer overlay like WireGuard to do it. Treating WireGuard as a hard prerequisite is simpler than retrofitting access control later.

WireGuard Fundamentals (Quick Recap)

WireGuard is a Layer 3 VPN that lives in the Linux kernel. The wire format is roughly 4,000 lines of code, and the userland tooling (wg, wg-quick) is thin glue. A few properties matter for PBS:

  • Stateless handshake. Peers exchange a Noise-protocol handshake every two minutes if traffic is flowing, otherwise nothing. There is no listening port until a valid packet arrives, and invalid packets get silently dropped.
  • Key-based auth. Every peer has a Curve25519 keypair. Authentication is the public key. There are no usernames, no certificates, no PKI to maintain.
  • Single UDP port. WireGuard speaks one UDP port (51820 by default). No control plane on a different port, no fallback protocols.
  • Roaming. Peers can change source IPs mid-session. A client behind dynamic NAT does not need a reconnect dance.

OpenVPN and IPsec do all of this too, with more features. They also come with TLS state machines, IKE daemons, NAT-T quirks, and config files that nobody reads twice. For pinning two PBS endpoints together, WireGuard is the right tool.

Network Design: Hub-and-Spoke vs. Point-to-Point

Pick the topology before you write a config. The choice constrains namespace layout, key management, and how much the design tolerates a new client.

WireGuard topologies for PBS connectivity
Property
Point-to-Point
Hub-and-Spoke
Full Mesh
Best for
Two sites, one PBS
MSPs, multi-site enterprises
PBS-to-PBS sync between peers
PBS namespace fit
Single namespace
One namespace per spoke
Per-peer mapping
Scaling characteristics
Does not scale past 2 sites cleanly
Linear, hub is the bottleneck
n*(n-1)/2 tunnels
Operational complexity
Lowest
Moderate
High; key distribution painful
Failure mode
Tunnel down = no backups
Hub down = no backups for any spoke
Partition tolerant

For MSP work, hub-and-spoke is the right default. The PBS node is the hub. Every PVE site is a spoke. Each spoke gets a unique WireGuard IP, a dedicated PBS namespace, and an ACL rule that limits its API token to that namespace only. Spokes never need to talk to each other, which lets you set AllowedIPs = 10.7.0.1/32 (just the hub) on each spoke and stop there.

Full mesh is for sync replication between PBS nodes, not for client backups. Save it for the case where you have two or more PBS instances syncing to each other.

Step-by-Step: WireGuard Setup on the PBS Node

Run these on the PBS host. Debian and Proxmox kernels both include WireGuard.

bash
apt update
apt install -y wireguard

# Restrictive umask so private keys are mode 600
umask 077
cd /etc/wireguard
wg genkey | tee hub_private.key | wg pubkey > hub_public.key

# One preshared key per spoke; generate now or per-onboarding
wg genpsk > spoke-clientA.psk
Install WireGuard and generate the hub keypair

Pick a private subnet for the tunnel. 10.7.0.0/24 works as long as it does not collide with anything on the PBS LAN. The hub gets .1, every spoke gets a unique address.

ini
[Interface]
Address = 10.7.0.1/24
ListenPort = 51820
PrivateKey = <contents of hub_private.key>

# Bind PBS to the tunnel interface only
PostUp   = iptables -A INPUT -i wg0 -p tcp --dport 8007 -j ACCEPT
PostUp   = iptables -A INPUT -p tcp --dport 8007 -j DROP
PostDown = iptables -D INPUT -i wg0 -p tcp --dport 8007 -j ACCEPT
PostDown = iptables -D INPUT -p tcp --dport 8007 -j DROP

# Spoke: client A (PVE site, Berlin)
[Peer]
PublicKey = <clientA public key>
PresharedKey = <contents of spoke-clientA.psk>
AllowedIPs = 10.7.0.10/32

# Spoke: client B (PVE site, Munich)
[Peer]
PublicKey = <clientB public key>
PresharedKey = <contents of spoke-clientB.psk>
AllowedIPs = 10.7.0.11/32
/etc/wireguard/wg0.conf (PBS hub)

The PostUp/PostDown rules are the important part. They make port 8007 reachable on the wg0 interface and unreachable on every other interface. If your firewall config goes sideways later, the PBS port still has nothing listening on the public NIC.

Bring it up and persist it across reboots:

bash
systemctl enable --now wg-quick@wg0
systemctl status wg-quick@wg0
wg show
Enable wg0 at boot

Configure the PBS process itself to bind only to the wg0 address. Edit /etc/proxmox-backup/proxy.cfg:

conf
proxy: api
    listen 10.7.0.1
/etc/proxmox-backup/proxy.cfg

Reload the proxy: systemctl reload proxmox-backup-proxy. Now the API socket is not even bound to the public interface. Defense in depth.

Verify the bind

After reload, run ss -tlnp | grep 8007. You should see only 10.7.0.1:8007, not 0.0.0.0:8007 or *:8007. If you see the latter, the proxy did not pick up the config change.

Step-by-Step: WireGuard on the PVE Client

Run WireGuard directly on each PVE host. This is simpler than running it inside a VM and avoids the chicken-and-egg problem of needing a VM up to back up VMs.

bash
apt update
apt install -y wireguard

umask 077
cd /etc/wireguard
wg genkey | tee clientA_private.key | wg pubkey > clientA_public.key
Install WireGuard and generate the spoke keypair

Send the public key and the preshared key to the hub operator over a separate channel (Signal, encrypted email, password manager share). The private key stays on the PVE host.

ini
[Interface]
Address = 10.7.0.10/32
PrivateKey = <contents of clientA_private.key>

[Peer]
PublicKey = <hub public key>
PresharedKey = <contents of spoke-clientA.psk>
Endpoint = pbs.example.com:51820
AllowedIPs = 10.7.0.1/32
PersistentKeepalive = 25
/etc/wireguard/wg0.conf (PVE spoke)

AllowedIPs = 10.7.0.1/32 is critical. It restricts the tunnel to the PBS hub address only. The spoke does not route any other traffic over wg0. PersistentKeepalive = 25 keeps the NAT mapping alive when the PVE host is behind a typical home or office gateway.

Bring it up: systemctl enable --now wg-quick@wg0.

Test reachability before touching PVE storage config:

PVE host
root@pve:~#
ping -c 2 10.7.0.1
root@pve:~#
64 bytes from 10.7.0.1: icmp_seq=1 ttl=64 time=12.4 ms
root@pve:~#
64 bytes from 10.7.0.1: icmp_seq=2 ttl=64 time=11.9 ms
root@pve:~#
proxmox-backup-client login --repository 'token@pbs!sync@10.7.0.1:clientA-store'
root@pve:~#
Login successful.
root@pve:~#
proxmox-backup-client snapshot list --repository '...'

Then add PBS as a storage target in PVE, pointing at 10.7.0.1 rather than the public hostname. The PBS fingerprint stored in /etc/pve/storage.cfg is the self-signed one, which is fine because the certificate only ever travels through the tunnel.

MSP Multi-Tenant Design

Hub-and-spoke gets interesting once you have five or ten clients. Treat each client as a fully isolated peer with its own IP, namespace, API token, and preshared key.

Per-client peer layout
Property
Client A, Berlin
Client B, Munich
Client C, Hamburg
WireGuard IP
10.7.0.10/32
10.7.0.11/32
10.7.0.12/32
PBS namespace
client-a
client-b
client-c
AllowedIPs (hub side)
10.7.0.10/32
10.7.0.11/32
10.7.0.12/32
Preshared key
Yes (spoke-clientA.psk)
Yes (spoke-clientB.psk)
Yes (spoke-clientC.psk)
API token
sync@pbs!clientA
sync@pbs!clientB
sync@pbs!clientC

The AllowedIPs = 10.7.0.10/32 on the hub side is what enforces "this peer can only show up as 10.7.0.10". Combined with PBS ACLs that scope sync@pbs!clientA to namespace client-a, a compromised PVE host cannot read another tenant's snapshots even if the WireGuard layer holds.

Preshared keys are worth the small operational cost. They add a symmetric secret on top of WireGuard's asymmetric handshake. If a future cryptanalytic break weakens Curve25519, the PSK still has to be recovered separately. PSKs are 32 bytes of random; they sit in the config file mode 600, get rotated when a client offboards, and that's the entire lifecycle.

For the hub endpoint itself, prefer a static IP over DNS for the spoke Endpoint = ... line. DNS works, but a slow or hijacked resolver during an incident is a problem you do not need. If you must use DNS, set a short TTL and pin to a record you control.

Namespace isolation does the heavy lifting

WireGuard gets the encrypted bytes from spoke to hub. PBS namespaces and ACLs are what stop client A from listing client B's snapshots. Both layers are required. Read the PBS namespaces multi-tenant guide for the namespace and ACL specifics, and the broader MSP backup design post for the tenancy model end-to-end.

Common Mistakes

A few patterns show up repeatedly in WireGuard-fronted PBS deployments. Avoid them.

Setting AllowedIPs = 0.0.0.0/0 on the spoke. This routes every packet from the PVE host through the tunnel. PVE management traffic now goes via the hub. The hub's link becomes a single point of failure for cluster operations, not just backups. Use 10.7.0.1/32.

Forgetting PersistentKeepalive on roaming spokes. PVE hosts behind NAT will drop their session table entries after a few minutes of idle. The next backup attempt fails until the keepalive fires. Set it to 25 seconds on every spoke.

Reusing one keypair across clients. Tempting on the first MSP onboarding because it works. Now offboarding a client means rotating every spoke. One keypair per peer.

Skipping the PBS bind config. WireGuard makes 8007 reachable through the tunnel. It does not stop PBS from also listening on the public NIC. Edit proxy.cfg and bind to the wg0 address explicitly. Pair this with security hardening on PBS itself so the inside of the tunnel is also locked down.

Treating the tunnel as a backup of itself. WireGuard handshake state lives in kernel memory. If the hub reboots and the spoke's Endpoint resolves to a stale IP, the tunnel will not come up automatically. Test reboot recovery once a quarter, the same way you should test restores.

Wrapping Up

Putting Proxmox Backup Server behind WireGuard is the difference between a backup target that is one CVE from compromise and one that is invisible from the public internet. The setup is a single wg0.conf on each side, plus a proxy.cfg bind that closes the public listening socket. For MSPs, the hub-and-spoke layout combined with PBS namespaces gives you per-client isolation that holds at both the network and storage layers.

Run through the offboarding drill before you onboard your second client. Removing a [Peer] block from the hub config and reloading wg0 should be a 30-second operation. If it is not, the design has accumulated state that will hurt later.

Don't want to manage WireGuard and a PBS node yourself?

remote-backups.com gives you a managed Proxmox Backup Server endpoint with WireGuard access included. EU datacenters, isolated namespaces per client, no public 8007 anywhere.

Start free

Technically yes, but it creates a dependency loop: the VM that holds the tunnel is itself something you want to back up, and if it fails to boot, you have no path to PBS. Run WireGuard on the PVE host. Kernel-level overhead is negligible.

On modern CPUs, kernel WireGuard saturates 1 Gbps links without trouble and pushes 5-10 Gbps with AES-NI. PBS chunk transfers are not the bottleneck; disk and dedup are. Expect single-digit-percent overhead at most.

If you already have IPsec in production and the team operates it well, keep it. For greenfield PBS deployments, WireGuard is faster to set up, easier to audit, and produces less operational burden over time.

The proxmox-backup-client TCP connection breaks and the backup task fails. PBS does not corrupt the datastore: incomplete chunks are not committed. The next scheduled run picks up cleanly, and deduplication means very little data needs to be re-sent.

Strictly required, no. Recommended, yes. The PSK is a hedge against future cryptanalytic breaks in Curve25519 or ChaCha20-Poly1305. The operational cost is one line in the config and a 32-byte file. The benefit is a defense layer that survives an asymmetric crypto break.
Bennet Gallein
Bennet Gallein

remote-backups.com operator

Infrastructure enthusiast and founder of remote-backups.com. I build and operate reliable backup infrastructure powered by Proxmox Backup Server, so you can focus on what matters most: your data staying safe.