This backs up Proxmox guests (LXC and QEMU) to an SFTP server using restic. That's it. It is not trying to be Proxmox Backup Server, and if PBS works for you, go use PBS.
The point is an encrypted offsite copy without standing up yet another server for it.
restic already does dedup, encryption and incremental backups over plain SFTP, so pbo
is mostly glue: it runs vzdump, throws the archive into a restic repo, and pushes it
offsite. Restore does the reverse and feeds the archive to pct restore or
qmrestore, always into a new VMID. No daemon, no web service. Drive it from
pbo menu, or use the individual commands if you'd rather type.
This is beta. It backs up and restores real machines and there's a test suite, but things still move around, so don't make it the only copy of anything important just yet.
A quick look at the interface before you clone it. The main menu:
And the status screen, showing both tiers with the next scheduled run:
restic already solved the hard parts, so there's no reason to reinvent them. It speaks SFTP itself, so there's no FUSE mount and no rclone in the middle. Everything goes into one repo no matter how many guests or nodes you have. The repo password is both the encryption key and the recovery key, so if you lose it the data is gone for good. Keep a copy somewhere safe.
Nothing exotic:
- A Proxmox host (
pct,qm,vzdump). restic,jq,zstd,flock,curl.install.shinstalls whatever's missing.- An SFTP target you can log into with a key. Storage Box paths are relative because the account is chrooted, which is expected.
git clone https://github.com/TubalQ/PBO
cd PBO
sudo ./install.shGoes into /usr/local and offers to run setup. It leaves the timer off, so it won't
start a nightly job you didn't ask for.
pbo setup # everything, in one passsetup is a single guided pass that gets you from a clean clone to running backups.
It generates an SSH key if you don't have one and shows you how to install it on the
target, pins the target's host key, tests the SFTP connection before it commits
anything, writes /etc/pbo/config, creates the repo, sets retention, confirms which
guests to back up, and offers to enable the timers (nightly backup + weekly prune).
The one thing it can't do for you is get the public key onto the target — it prints the key and the exact command, you run that once, and the connection test confirms it took.
Afterwards, export the DR key somewhere safe (pbo menu → Export DR key). Don't skip
that: it's the only thing that can decrypt the offsite data.
If you'd rather enable the schedule by hand instead of from the wizard:
systemctl enable --now pbo.timer # backups, 05:00 daily
systemctl enable --now pbo-prune.timer # prune, Sunday 06:30Then pbo doctor any time to check the repo is reachable, the timer is on, the
DR key is 0600, and every guest has a recent snapshot.
There's no cluster mode to set up. Install it on every node and each node backs up its own guests into the same repo, the same way Proxmox's own backup jobs work. No coordinator, no node reaching across to another over SSH.
sudo ./install.sh
pbo setup # same repo, same password
systemctl enable --now pbo.timerWith BACKUP_ORDER=auto each node works out its own guests. Move a guest to another
node and that node backs it up on the next run; restic tags snapshots by vmid, so the
history isn't lost.
A few things worth getting right:
- Use the same repo password on every node, in
/etc/pbo/restic-pass. Keep it out of/etc/pve, since pmxcfs replicates that directory in cleartext, which defeats the point. - To edit the config once instead of N times, put the non-secret parts in
/etc/pve/pbo/configand let pmxcfs sync it. The local file still holds the password and wins on conflicts. - Prune from one node only, since it takes an exclusive lock. Set
PRUNE_OWNERto pin it to a node.
pbo [global flags] <command> [arguments]
Global flags:
--json machine-readable output
--dry-run show what would happen, do nothing
Commands:
setup setup wizard
menu interactive prompt
init create or verify the repo
backup <vmid> back up one guest
run-schedule [--stream|--batch] back up this node's guests
status running jobs and lock holder
doctor health check (repo, timer, key, per-guest age)
list [vmid] offsite archives
usage repo size, dedup ratio, snapshot count
fetch <vmid> <ts> extract+verify a snapshot into the cache
restore <vmid> <ts> --to N restore to a new vmid
verify restic integrity check (both tiers)
prune [--if-owner] apply retention
unlock clear a stale repo lock after a killed run
rotate-key <file> rotate the repo password to a new value
test-restore <vmid> restore to a throwaway vmid, boot, destroy
backup-host back up this host's config + rebuild metadata
restore-host <node> <ts> --to <dir> [--path <p>] extract host files to a dir
pbo backup-host captures the Proxmox host's own configuration into the same
repo, tagged type=host, so a rebuild is not starting from a blank machine. It
stores a curated set of paths (/etc/pve, networking, /etc/pbo, systemd units,
cron, apt sources — see HOST_BACKUP_PATHS) plus collected rebuild metadata
(pveversion -v, the manual package list, storage and network layout). It is
manual, not part of the nightly schedule, and appears in list/status/the menu
like a guest under host-<node>.
SSH keys and the restic DR key are deliberately excluded (HOST_BACKUP_EXCLUDES):
a secret never belongs in the repo it protects, and the host must keep its own
offline copy of its keys. Restore is a file restore, never pct restore:
pbo restore-host <node> <ts> --to /var/tmp/dr # extract everything
pbo restore-host <node> <ts> --to /var/tmp/dr --path /etc/pve # one pathNothing is written to live host paths; you review the extracted tree and copy what you need back by hand.
| Setting | Effect |
|---|---|
LOCAL_REPO=true |
Keep a local repo, copy each snapshot offsite. Fast local restores. |
LOCAL_REPO=false |
Straight to SFTP. Barely touches local disk. |
BACKUP_MODE=stream |
One guest at a time (default). Low disk. |
BACKUP_MODE=batch |
Dump everything to cache first, then upload, if it fits. |
The point of a backup is the restore, so: the host is dead and you have a clean machine.
Install pbo, give it the same repo password (the DR key), and restore. It always
restores to a new VMID and won't overwrite a running guest. And since a backup you've
never tested isn't really a backup, test-restore runs the whole loop for you: it
restores a throwaway copy, boots it, and throws it away.
Plain SFTP has no append-only mode, so a compromised host can delete its own offsite backups. Three things close that gap, in order of effort:
- Provider snapshots. Turn on your Storage Box's scheduled snapshots and set
STORAGE_BOX_SNAPSHOTS_CONFIRMED=true. This is the real ransomware backstop: even if the host wipes the repo, the provider keeps read-only copies.pbo doctorwarns until you've confirmed it. - Pin the host key. Setup offers to pin it for you: it runs
ssh-keyscanagainst the target, writes the key to/etc/pbo/known_hosts, and points the ssh command at it with-o UserKnownHostsFile=... -o StrictHostKeyChecking=yesinstead of trust-on-first- use. Decline it and the connection falls back toaccept-new. - Append-only repo. For a host that should never be able to delete, put a restic REST server in append-only mode (or a restricted SFTP user) in front of the storage and prune from elsewhere. More moving parts; worth it if the host is exposed.
Rotate the repo password with pbo rotate-key (or menu → Rotate DR key). restic keys
wrap the master key, so it's instant and re-encrypts nothing, but the old key stops
working, so export the new one immediately.
Everything is documented in etc/config.example. The only secret is the repo password
(RESTIC_PASSWORD_FILE, 0600). The config file itself holds nothing sensitive, so it's
fine to keep in version control.
Patches welcome. It's plain bash with tests in tests/. If you change how something
behaves, please add a test that covers it.
AGPL-3.0-or-later. See LICENSE.

