A second PBS at home: offsite backups without S3, without a white IP, without a VPS intermediary
Continuation of the story about moving from five VPS instances to one dedicated server. Last time I stopped at the scheme “PBS in LXC on the host + offsite sync to S3”. In the end, I rebuilt this scheme—and it turned out that S3 was an unnecessary link here.
Where I left off last time
In the previous article I described my backup scheme on four layers:
- ZFS snapshots on the pool (instant, against “oops, I deleted the DB”).
- PBS in LXC on the same host (proper incremental backups).
- Sync job from PBS to S3 (offsite).
- Discourse backups directly into S3 (application insurance).
Layer 3 bothered me for all this time. I’ll name three reasons—and one of them you probably haven’t seen in tutorials.
S3 breaks PBS deduplication. The whole charm of PBS is in chunk-level deduplication: a new backup only stores the delta from the previous one, not the full size of the VM. When syncing into S3, PBS stores chunks as objects—and yes, formally deduplication is preserved. But verifying the backup integrity means downloading a significant amount of chunks back. And that’s where reason two comes in.
Selectel is “like its own”—but if tomorrow they decide to raise prices or lose a region, I have no leverage. This is money that drips in every month, and that in a disaster might not return the data (legal hold, account freeze, anything). For backups, this is critical—exactly when you lose your primary site, you must be able to switch to a reserve without negotiating with the provider.
Direct restore is impossible. S3 just contains bytes. To restore from them, you first need to bring up an intermediate PBS, sync the data back, and only then run qmrestore. If my dedup is on fire, I need a second location ready to restore right now. Not “in an hour, once I spin up a temporary PBS”.
So the logical conclusion: instead of S3, you need a second full PBS located physically in a different place—ready to accept sync jobs and, if necessary, give backups back directly.
Where does this second PBS live
The options were:
- Rent from a hosting provider — deja vu from the previous article; I had already moved away from the “many bills” model.
- Cheap VPS with its own PBS — a couple dozen euros per month, an uncontrollable platform, and yet another single point of failure.
- Put it at home on my own NAS — there’s room, the NAS lives in my living room, RAID, UPS, and the ISP doesn’t block SSDs ¯\(ツ)/¯.
The third option wins on all parameters except one: a home NAS behind NAT—my ISP won’t give me a white IP without “dancing and paying”. You can’t just forward ports, messing with DDNS and a dynamic IP isn’t what I want, and keeping the PBS port open to the internet is something I don’t have separately.
That’s where mesh comes onto the stage, which I already have: Netbird, described in the previous article. The dedup server (and other peers) are already connected to it: the dedicated server, the peer server, my laptop, and a couple of old machines. Adding the NAS there is one agent install and one setup key. The NAS gets an address 100.x.x.x in the same private mesh network, and for the dedicated server it becomes as reachable as the neighbor on vmbr1.
What it turned out to be in the end
flowchart LR
subgraph DC["🇵🇱 Dedik OVH Warsaw"]
PVE["Proxmox Host<br/>217.182.203.187"]
subgraph LXC102["LXC 102"]
PBS1["PBS primary<br/>10.10.10.3"]
end
VMs["VM 200, 201, 202, 203<br/>LXC 120, 121, 122"]
PVE --- LXC102
PVE --- VMs
end
subgraph HOME["🏠 Home behind NAT"]
NAS["Synology NAS"]
subgraph COMPOSE["Container Manager"]
NB["netbird agent"]
PBS2["PBS secondary<br/>:8007"]
NB -.network_mode.- PBS2
end
NAS --- COMPOSE
end
subgraph CLOUD["☁️ Netbird Cloud"]
MGMT["Management<br/>+ Signal"]
end
VMs -."vzdump backup".-> PBS1
PVE <-->|control plane| MGMT
NB <-->|control plane| MGMT
PBS1 <==>|"PBS Sync Job<br/>WireGuard tunnel"| PBS2
classDef dc fill:#2d3748,stroke:#4a5568,color:#e2e8f0
classDef home fill:#22543d,stroke:#38a169,color:#f0fff4
classDef cloud fill:#2c5282,stroke:#3182ce,color:#ebf8ff
class DC dc
class HOME,COMPOSE home
class CLOUD cloudVMs and LXCs are backed up locally to PBS primary in LXC 102—fast, via localhost, with full deduplication. Once a day a sync job from PBS primary pushes the delta to PBS secondary on the NAS via the mesh. Snapshots remain in both places, deduplication works independently on both PBS instances, and restore can be done from either of the two—both are “real”.
Through the mesh only sync traffic goes—meaning the NAS isn’t visible to anything outside, with no ports and no DDNS. Through Netbird Cloud only the control plane (peer management) runs; the backups themselves flow directly over the WireGuard tunnel: dedicated server ↔ NAS.
Setup: what changed from a standard PBS
Most of the setup is a regular docker-compose project in Synology Container Manager: pbs from the community build ayufan/proxmox-backup-server plus a sidecar netbird agent in a shared network namespace. Details about the compose file, memory limits, mounting /etc, /lib, /backups—I already wrote them down in a separate note about the process itself in a separate note about the process. Here I’ll focus on what’s specific to the second PBS, not on the basics.
Hostname in the mesh — makes sense
1environment:
2 - NB_HOSTNAME=synology-pbs-offsite
When there are more than a dozen peers, names like synology start losing meaning. synology-pbs-offsite immediately tells you that it’s (a) a NAS, (b) with PBS, and (c) for the offsite role. On the dedicated server in storage.cfg the exact same name will appear.
Netbird ACL — allow only the dedicated server
In the Netbird console I created a group pbs-sync and put only two peers in it: the PVE host and Synology. Rule: everything else in the default group is not allowed to reach pbs-sync. The laptop, peer server, and old machines can’t see port 8007 on the NAS either. If tomorrow someone asks for RDP access for the laptop, it won’t automatically grant access to my backups.
This, by the way, is the main advantage of mesh with ACL over plain WireGuard: in WG everyone in the tunnel can see everyone—ACLs are done with iptables and quickly turn into a mess.
Datastore on a separate volume
On Synology I created a separate shared folder /volume2/PBS on a partition with large mechanical drives (BTRFS, RAID 5). I left an NVMe volume on the NAS for hot data and Docker. PBS doesn’t need high IOPS for backups—it needs volume. Noisy HDD spindles are perfect for this.
1volumes:
2 - ./etc:/etc/proxmox-backup
3 - ./logs:/var/log/proxmox-backup
4 - ./lib:/var/lib/proxmox-backup
5 - ./backups:/backups
In the PBS UI I specify the datastore as /backups. Size: 4 TB. With the current amount of data, it should last a couple of years with retention keep-daily=7 keep-weekly=4 keep-monthly=12 keep-yearly=3.
Sync Job — the most interesting part
This is new for me; it wasn’t included in the previous article. PBS has a built-in feature Remote + Sync Job: one PBS can act as a “client” of another, pull snapshots from it, and store them in its own datastore.
Step 1: Remote on the primary side
On the main PBS (LXC 102) I add the secondary as a remote target. In the UI: Configuration → Remotes → Add.
- Remote ID:
nas-offsite - Host:
synology-pbs-offsite(this is the name from Netbird DNS; it resolves from any peer) - Auth ID:
sync@pbs(a separate user on the secondary; see below) - Password / Token: the password for
sync@pbs - Fingerprint: SHA-256 fingerprint of the secondary certificate
The fingerprint is retrieved on the secondary with one command:
1docker exec pbs proxmox-backup-manager cert info | grep -i fingerprint
Step 2: User for sync on the secondary
On the NAS, inside the pbs container:
1proxmox-backup-manager user create sync@pbs --password 'LongRandomPassword2026'
2proxmox-backup-manager acl update /datastore/main DatastoreBackup --auth-id sync@pbs
3proxmox-backup-manager acl update /remote DatastorePowerUser --auth-id sync@pbs
[!IMPORTANT] The password must be long and without special characters. PBS accepts any password silently when creating a user via
proxmox-backup-manager, but rejects weak passwords at the API level with401 Unauthorized. I caught this on a perfectly normal spot, wasted an hour until I figured out I needed to create a new user with a proper password. Ideally PBS should complain at the creation stage—but it doesn’t.
Step 3: Sync Job
On the primary PBS: Datastore → main → Sync Jobs → Add.
- Local Datastore:
main(this is the datastore on the primary) - Remote:
nas-offsite - Remote Datastore:
main(this is the datastore on the secondary) - Schedule:
daily 03:30(after the daily backup at 02:00, before morning) - Owner:
sync@pbs - Remove vanished:
yes(if you delete an old snapshot on the primary according to retention, then let it be deleted on the secondary as well; otherwise the secondary will grow)
Click Run Now → you can see in the logs how PBS transfers data chunk by chunk. First time is the full volume (I got about 180 GB at the start); all subsequent runs are only the delta.
Speed
I was worried: NAS at home, my link is 600/600 Mbps, dedicated server in Warsaw, about 30 ms between them. Over WireGuard in the mesh PBS pushes at about 400 Mbit/s—this is limited by encryption on the NAS, not by the channel. For an offsite backup this is more than enough. The daily delta within 5–15 GB goes out in a couple of minutes.
Restore: it really works from both sides
I worked out the scenario “the dedicated server is down / OVH is down / disk died” like this:
- Take any spare server (either run Proxmox locally in a VM, or get a new dedicated server from a new hosting provider).
- Install the Netbird agent, and add it to the mesh.
- Connect the NAS as a PBS Storage:
Datacenter → Storage → Add → Proxmox Backup Server, serversynology-pbs-offsite, usersync@pbs. - Run
qmrestorefor the required VM from the NAS’s datastore. No intermediary, no “first spin up PBS”—the secondary already is the PBS.
The test restore of the Discourse VM into an empty Proxmox instance completed in 18 minutes (4 GB RAM, 50 GB disk). With S3 it would have been the whole night.
Cost
| What | Previously | Now |
|---|---|---|
| Object storage in S3 for backups | ~$8/month for 200 GB | $0 |
| Egress for a test restore | ~$15 for 100 GB | $0 |
| Time for offsite restore | from an hour | minutes |
| Control over storage | with Selectel | with me |
The NAS is at home anyway—it wasn’t bought for this task, it just became a bonus. Electricity is pennies because spin-down for inactive disks works.
What I understood along the way
Mesh is an infrastructure primitive, not a “VPN for remote access”. In the previous article I wrote about Netbird in the context of admin access and bypassing blocks. Now I realize that mesh is the zero layer of all my infrastructure. Any new node first connects to the mesh, and then gets its role. This changes how you think about architecture: you stop dividing servers into “yours/other/public”, and they all become peers with different ACLs.
S3 for backups was intelligent laziness. I chose S3 because it’s a “canonical pattern”, not because it actually fit my use case. A second PBS fits better on all parameters except one—it has to be installed and configured by yourself. And even then, it’s just one evening.
Off-site != offline. Many people write that backups must be offline (air-gapped). In my case the secondary is always online through the mesh, and formally it’s not protected from ransomware that hits the primary and then corrupts snapshots on the secondary via the sync job. The protection here is the verify-new flag and ZFS/BTRFS snapshots on the Synology itself at the shared folder level. PBS on the secondary can’t delete or corrupt a snapshot above itself—that’s done only by the NAS host. So the secondary is online for convenience, but under it there’s an offline-history that the sync logic can’t reach.
What’s next
Improvements along the backup chain:
- Verify jobs on the secondary every two weeks to ensure the data on the NAS is really readable, not just “lying there”. Previously this role was played by the hypothetical ability to download from S3 and verify; now it’s a native PBS feature.
- A third offsite layer for the truly paranoid scenario “home and dedicated server die at the same time”. Most likely this will be a periodic manual export of critical datastore snapshots to an external USB drive stored with my parents in another city. Not automation, but a ritual—once per quarter, drive over, update.
If you already have a NAS at home and a mesh between servers, you already have a ready offsite PBS—you just need to assemble it. If you don’t, look at it from a different angle: a home NAS stops being a “vacation photo gadget” and becomes part of production infrastructure. And surprisingly, that’s normal.
In the next note I’ll explain verify jobs, prune strategy for two PBS instances, and how to calculate the real RPO/RTO for such a setup. If you’re interested—write in the comments.