Proxmox Backup Server on a VPS
Run Proxmox Backup Server on a VPS as an offsite target: datastore setup, namespaces per host, prune and garbage collection, encryption keys, restore tests.
What Proxmox Backup Server on a VPS actually gives you
Proxmox Backup Server (PBS) on a VPS is an offsite target that speaks the same protocol your Proxmox VE (virtual environment) cluster already uses, so every backup after the first one is incremental, deduplicated across guests, encrypted before it leaves your building, and verifiable afterwards. You rent a VPS with a block volume, install PBS on Debian 13, create one datastore on that volume, and add it in Proxmox VE as a storage of type pbs. The install takes ten minutes. Everything after it, namespaces, garbage collection, key custody and a restore you have actually run, is what decides whether the backup is worth anything a year from now.
The reason to use PBS instead of copying vzdump files to a rented disk is the chunk store. The client splits each guest disk into chunks of about 4 MiB, hashes them, and uploads only the chunks the datastore does not already hold. For a running virtual machine, QEMU tracks changed blocks in a dirty bitmap after the first backup, so the next run reads only those blocks off the local disk. A 200 GB guest that changes 3 GB a day sends about 3 GB a day. That is what makes a home uplink and a rented volume work together, and it is why a VPS as an offsite backup target beats a spare drive at a friend's house. If you are still deciding where the hypervisor itself should live, Proxmox at home versus a rented VPS covers that question separately.
Size the volume before you rent it
Sizing is arithmetic you perform on your own numbers. Take the space each guest actually uses, not the size of its virtual disk, then add what it changes per day multiplied by the number of days you keep. Compression and deduplication both improve on that figure, so treat the result as a ceiling and not a target.
The data behind this chart
[
{
"label": "web VM",
"used_gb": 40,
"daily_change_gb": 0.8,
"store_gb": 64
},
{
"label": "mail VM",
"used_gb": 120,
"daily_change_gb": 3.0,
"store_gb": 210
},
{
"label": "file server container",
"used_gb": 300,
"daily_change_gb": 1.5,
"store_gb": 345
}
]Those rows are a worked example, not a measurement. Read the used space from df -h inside each guest, and read the daily change from the size of the second and third backups in the PBS task log once they exist.
The mail guest in the example uses 120 GB and changes about 3.0 GB a day, so thirty daily snapshots need roughly 210 GB: one full copy plus thirty days of change. Add the last column across all 3 guests and the total is about 619 GB. Add a fifth on top for indexes, metadata and the room garbage collection needs to work in, which points at a 1 TB volume.
The rest of the plan is small. PBS is happy with 2 GB of RAM and comfortable with 4 GB, because the expensive work happens on the cluster side: the Proxmox VE node reads the guest disks and does the chunking and hashing. What the VPS does is write chunks and run the two heavy jobs, garbage collection and verification. Rent the datastore as a separate block volume rather than one large root disk, because you can grow a volume later without rebuilding the server.
Install Proxmox Backup Server on Debian 13
As of August 2026 the current pairing is Proxmox Backup Server 4 on Debian 13, codename trixie. Older guides pair PBS 2 with Debian 11, and the codename is part of the repository definition, so copying an old suite name gives you an apt error about a missing release file. Start from a plain Debian 13 image. Run everything below as root, or with sudo as written.
sudo apt update && sudo apt install -y wget
sudo wget https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg -O /usr/share/keyrings/proxmox-archive-keyring.gpg
sha256sum /usr/share/keyrings/proxmox-archive-keyring.gpgThe sum must read 136673be77aba35dcce385b28737689ad64fd785a797e57897589aed08db6e45. If it does not, stop. A wrong keyring means you are about to install packages signed by something you have not checked.
Write /etc/apt/sources.list.d/pbs.sources with the no-subscription repository, which is the right one for a server without a support contract:
Types: deb
URIs: http://download.proxmox.com/debian/pbs
Suites: trixie
Components: pbs-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpgsudo apt update
sudo apt install -y proxmox-backup-serverThe web interface answers on HTTPS port 8007. Log in as root@pam with the system root password, because PBS authenticates that user against PAM (pluggable authentication modules), the same accounts the operating system uses. The certificate is self-signed and your browser will say so. That certificate's fingerprint is the value Proxmox VE pins later, so the warning is expected rather than a problem to fix.
Port 8007 is a login form on the public internet, so do not leave it open to everyone. One nftables file covers it. Writing /etc/nftables.conf flushes the current ruleset, so skip this if something else already manages the firewall on this box.
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif lo accept
tcp dport 22 accept
ip saddr 203.0.113.7 tcp dport 8007 accept
}
}Apply it with sudo systemctl enable --now nftables, and keep a second SSH session open while you do: policy drop plus one typo in the SSH rule locks you out of your own server. Replace 203.0.113.7 with the address your cluster leaves from. If that address is dynamic, either widen the rule to your provider's range or terminate the connection in a tunnel, and remember that most VPS panels have a separate network firewall in front of the machine that must allow the same port.
Put the datastore on its own volume
The datastore must not live on the root filesystem. When a datastore fills a shared root filesystem, the backup fails and so does everything else on the box, including the logging you need to work out why. Attach the block volume, format it, mount it, and only then create the datastore inside the mount point.
lsblk
sudo mkfs.ext4 -L pbsstore /dev/vdb
sudo mkdir -p /mnt/datastore/store1Take the device name from lsblk. It is /dev/vdb on most KVM images and /dev/sdb on others, and it is never safe to assume. Add the mount to /etc/fstab by label, so a device rename after a reboot cannot point the datastore at the wrong disk:
LABEL=pbsstore /mnt/datastore/store1 ext4 defaults,relatime 0 2sudo systemctl daemon-reload
sudo mount -a
findmnt -no SOURCE,TARGET,OPTIONS /mnt/datastore/store1findmnt should print the device, the path, and options including rw,relatime. Two failures hide in that one line. If the mount is not there and you create the datastore anyway, PBS writes into the root filesystem underneath the mount point, and the next successful mount hides that data without deleting it: the datastore then looks empty and the root filesystem stays full. If the options say noatime, PBS refuses to work, because it runs an access time safety check when the datastore is created and again at every garbage collection.
sudo proxmox-backup-manager datastore create store1 /mnt/datastore/store1
sudo proxmox-backup-manager datastore listThat creates a .chunks directory holding 65536 subdirectories, named 0000 through ffff. A datastore is hundreds of thousands of small files, not a few large ones. Two things follow. Copying a datastore with an ordinary file-level tool is slow enough to be useless, and a provider volume snapshot taken while backups are running is not a consistent copy of it, which is the same reason snapshots do not replace backups anywhere else.
Namespaces keep two hosts from colliding
A datastore is flat by default. Backups are named vm/100, ct/101 and host/<name>. Two clusters that each have a guest with ID 100 write into the same group, their snapshots interleave, and a retention rule written for one of them counts the other one's snapshots. Namespaces give every source its own tree inside one datastore.
Create them on the PBS host. The --repository argument has the form [[auth-id@]server[:port]:]datastore, so a local one reads root@pam@localhost:store1, and the command asks for the root password.
sudo proxmox-backup-client namespace create --repository 'root@pam@localhost:store1' pve-home
sudo proxmox-backup-client namespace create --repository 'root@pam@localhost:store1' pve-office
sudo proxmox-backup-client namespace list --repository 'root@pam@localhost:store1'Deduplication is unaffected by this split. Chunks are shared across the whole datastore, so ten Debian guests spread over three namespaces still store one copy of the base system. That is the argument for one datastore with namespaces over one datastore per host: separate datastores mean separate chunk pools, and separate chunk pools mean paying for the same Debian install several times over.
Give each source its own account, scoped to its own namespace. An API (application programming interface) token is a credential that belongs to a user and carries its own permissions, which is what you want on a machine that might be stolen.
sudo proxmox-backup-manager user create backup@pbs --email you@example.com
sudo proxmox-backup-manager user generate-token backup@pbs pve-home
sudo proxmox-backup-manager acl update /datastore/store1/pve-home DatastoreBackup --auth-id 'backup@pbs!pve-home'The token command prints the secret exactly once:
Result: {
"tokenid": "backup@pbs!pve-home",
"value": "d63e505a-e3ec-449a-9bc7-1da610d4ccde"
}Copy it now, because PBS keeps no form of it that it can show you again. Look at the access control command twice. It names the token, backup@pbs!pve-home, and not the user, because token permissions are calculated only from entries that name the token itself. An entry for backup@pbs alone leaves the token with no access at all, and the first backup then fails on permissions rather than on anything visible in the network. The path matters just as much: a token scoped to /datastore/store1/pve-home cannot read or delete anything in the office namespace, so one compromised cluster cannot destroy another site's history.
Add the VPS as backup storage in Proxmox VE
Read the certificate fingerprint on the PBS host first.
sudo proxmox-backup-manager cert info | grep FingerprintThen, on any node of the cluster:
sudo pvesm add pbs pbs-offsite --server pbs.example.com --datastore store1
sudo pvesm set pbs-offsite --username 'backup@pbs!pve-home' --password
sudo pvesm set pbs-offsite --fingerprint 'FINGERPRINT_FROM_CERT_INFO'
sudo pvesm set pbs-offsite --namespace pve-home
sudo pvesm set pbs-offsite --prune-backups keep-all=1Paste the value cert info printed in place of the placeholder on the third line. Passing --password with no value makes pvesm prompt for it, so the token secret stays out of your shell history. It is stored at /etc/pve/priv/storage/pbs-offsite.pw, and the storage definition itself goes into /etc/pve/storage.cfg, which is replicated to every node in the cluster, so you configure this once for the whole cluster.
--prune-backups keep-all=1 tells Proxmox VE to delete nothing. Retention belongs on the PBS side, covered further down, for a reason worth stating plainly: the token then needs no permission to delete, so a cluster that gets encrypted by ransomware cannot reach out and prune the offsite history that is meant to save it.
sudo pvesm status --storage pbs-offsite
sudo vzdump 100 --storage pbs-offsite --mode snapshotpvesm status prints active in the status column, with the datastore's total and used space beside it. inactive means the node could not complete a TLS (transport layer security) session to port 8007, which is a firewall or a fingerprint problem rather than a credential one.
The first backup uploads everything, so do the arithmetic before you start it. 200 GB is 1600 gigabits, and a 100 Mbit uplink moves 0.1 gigabit per second, so the floor is about four and a half hours and reality is longer. Start it when you do not need the bandwidth. Every run after that sends only new chunks.
Client-side encryption, and where the key lives
The VPS is a computer you do not own. Encrypt on the client, and the datastore holds chunks the provider cannot read.
sudo pvesm set pbs-offsite --encryption-key autogenThat writes a new key to /etc/pve/priv/storage/pbs-offsite.enc, readable by root only, replicated with the rest of /etc/pve. From the next backup on, the client encrypts each chunk before it goes out. The server can still list your snapshots and their sizes, and it cannot read their contents.
Now the part that makes this a backup instead of a liability. A generated key has no passphrase, and it exists only on the cluster it protects. If that cluster is stolen or encrypted by someone else, the VPS holds data nobody can open. Copy the key off the cluster on the day you create it.
sudo cp /etc/pve/priv/storage/pbs-offsite.enc /root/pbs-offsite.enc
sudo proxmox-backup-client key paperkey /root/pbs-offsite.enckey paperkey prints the key as a document meant to be printed on paper and kept somewhere else. Treat the file itself as the secret it is, since anyone holding it can decrypt every backup made with it. For a larger setup, PBS also supports a master key, an RSA (Rivest Shamir Adleman) key pair created with proxmox-backup-client key create-master-key, where each backup stores its own encryption key encrypted to the public half while the private half stays offline for recovery.
One consequence of the design is worth knowing before you start rather than after. For encrypted backups the chunk digest is calculated from the plain text content joined with the encryption key, so two identical chunks encrypted under different keys produce different digests and never deduplicate against each other. Changing the key means the next backup uploads everything again, and the old chunks sit there until their snapshots are pruned and collected. Decide about encryption before the first upload.
Prune marks, garbage collection reclaims
This is the section that gets skipped, and it is the one that fills the volume. Pruning a snapshot removes its metadata: the manifest, the indexes, the log and the notes. It deletes no chunks at all. Chunks are shared between snapshots, so nothing can know a chunk is unused until every remaining index has been read, and garbage collection is the job that reads them. A datastore with a prune schedule and no garbage collection schedule only ever grows.
Set both. Retention first, one job per namespace:
sudo proxmox-backup-manager prune-job create home-daily --store store1 --ns pve-home --schedule '02:30' --keep-daily 14 --keep-weekly 8 --keep-monthly 6
sudo proxmox-backup-manager prune-job listThen the collection schedule on the datastore, a few hours after the prune job and outside the backup window:
sudo proxmox-backup-manager datastore update store1 --gc-schedule 'Sun 04:27'
sudo proxmox-backup-manager datastore show store1Prove the split to yourself once, on the PBS host:
df -h /mnt/datastore/store1
sudo proxmox-backup-manager garbage-collection start store1
df -h /mnt/datastore/store1Run the prune job, then df, and the used figure does not move. Run garbage collection, then df again, and it does.
Garbage collection runs in two phases. Phase one walks every index in the datastore and updates the access time of every chunk those indexes reference. Phase two deletes the chunks whose access time is older than the cutoff, which is 24 hours and 5 minutes before the run started, or the start of the oldest backup still writing, whichever is earlier. That margin exists because Linux mounts filesystems with relatime by default, which updates an access time roughly once a day instead of on every read. So a chunk written an hour ago is never deleted even if nothing references it yet, and space freed by a prune appears on the first collection that runs more than a day after the chunk was last touched. A datastore that looks like it reclaimed nothing is often just inside that window.
On a small VPS this is the heaviest job the box runs, because it stats every chunk file on the volume. The task log ends with a summary of what was removed and what is still pending because of the grace period. If a lot is pending, run it again the next day. PBS exposes gc-atime-safety-check and gc-atime-cutoff as datastore tuning options, and both should be left alone: they exist for storage that cannot record access times, and turning the safety check off on a filesystem mounted noatime is how you lose chunks that live snapshots still reference.
Verification proves the chunks are still readable
A backup that uploaded cleanly can still be unreadable a year later. Verification re-reads chunks and compares them against the checksums stored in the index, so damage is found on a schedule instead of during a restore.
sudo proxmox-backup-manager verify store1 --read-threads 1 --verify-threads 4Keep the thread counts low on a small VPS. Verification is limited by disk and CPU, and it will otherwise compete with whatever else the box is doing. For a schedule, use the datastore's Verify Jobs tab in the web interface: a weekly job that skips already-verified snapshots and re-verifies anything older than 30 days covers the whole store over time without repeating work.
A snapshot that fails verification is marked as failed in the datastore view. Do not ignore one. Chunks are shared, so a single damaged chunk from a base image usually fails every snapshot that references it. The repair is to forget the failed snapshots and run a fresh backup, which uploads the missing chunks again. If failures keep appearing, suspect the storage under the datastore, and set up disk health monitoring on the VPS so the drive tells you before the verify job does.
Test a restore, then test it without the cluster
You do not know a backup works until you have restored one. Two tests, and they check different things.
Whole guest, on the cluster:
sudo pvesm list pbs-offsite
sudo qmrestore 'pbs-offsite:backup/vm/100/2026-08-14T22:00:00Z' 999 --storage local-lvmThe first column of pvesm list is the volume ID, and the timestamp is part of it, so copy yours instead of typing the example. Restore into an unused guest ID and onto a different storage, then start it with its network interface disconnected. Never restore over a running guest to check that backups work, because a restore that fails halfway then costs you the working copy as well.
The second test is the one nobody runs. Assume the building with the cluster in it is gone, and restore from a machine that was never part of it. On any Debian 13 box, add the client-only repository as /etc/apt/sources.list.d/pbs-client.sources:
Types: deb
URIs: http://download.proxmox.com/debian/pbs-client
Suites: trixie
Components: main
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpgsudo apt update && sudo apt install -y proxmox-backup-client
export PBS_REPOSITORY='backup@pbs!pve-home@pbs.example.com:store1'
export PBS_PASSWORD='<the token secret>'
export PBS_FINGERPRINT='<the value cert info printed>'
proxmox-backup-client snapshot list --ns pve-home
proxmox-backup-client snapshot files vm/100/2026-08-14T22:00:00Z --ns pve-home
proxmox-backup-client restore vm/100/2026-08-14T22:00:00Z 'ARCHIVE_NAME_FROM_THAT_LIST' ./restore-test --keyfile ./pbs-offsite.enc --ns pve-homeFill in the three quoted placeholders from your own values, and take the archive name on the last line from what snapshot files printed. This proves what the first test cannot: that your copy of the key file decrypts real data, and that you can drive the client from a machine that has never held your cluster's configuration. Write down the four values it needed, the repository string, the token secret, the fingerprint and the key file, and keep them together in the place your disaster plan points at.
What deduplication does and does not do to your disk bill
Deduplication is real, and it works across the whole datastore. Ten Debian guests share one copy of the base system between them, so the second identical guest costs almost nothing to store. It saves upload bandwidth too, because the client sends a checksum instead of data for any chunk the server already holds.
What it does not do is worth being blunt about.
- It does not shrink data that changes. A database that rewrites large parts of its files every night produces new chunks every night, and retention multiplies them.
- It does not reach across an encryption key boundary, as covered above.
- It does not reach across a datastore boundary, which is the whole argument for namespaces.
- It does not stop a volume filling. When the datastore is full, backups fail, and the only answers are a bigger volume or shorter retention.
Do not stack another deduplication layer underneath it. Chunks arrive already deduplicated and compressed by the client, so ZFS deduplication under a datastore spends RAM looking for matches that were removed before they were written. Plain ext4 or xfs on the volume is the right choice here.
The web interface reports a deduplication factor for the datastore. That number describes your guests, and it is the only one worth planning with, because published ratios describe someone else's data. If you also need file-level backups of machines that are not Proxmox guests, run those alongside on the same VPS: PBS is a hypervisor-aware target for whole guests, while restic and BorgBackup point at directories, and restic backups to a VPS fit the laptops and standalone servers PBS was never meant to cover.
Failure modes and what you will see
The storage shows inactive. pvesm status --storage pbs-offsite prints inactive when the node cannot complete a TLS session to port 8007. Check the firewall on the VPS, then the provider's separate network firewall, then the fingerprint. A fingerprint that no longer matches the certificate fails in the same visible way as a blocked port, and it changes whenever that certificate is replaced.
The first backup fails on permissions. The access control entry has to name the token rather than the user, and it has to cover the namespace the storage points at. Confirm both on the datastore's permissions tab in the web interface before you look anywhere else.
Garbage collection refuses to start. The access time safety check failed, which almost always means the datastore filesystem is mounted noatime. Run findmnt -no OPTIONS /mnt/datastore/store1 to confirm, fix the option in /etc/fstab, and remount. Do not disable the check to get past it.
The datastore only grows. Prune jobs run and nothing is reclaimed. Either there is no garbage collection schedule, or every collection lands inside the 24 hour grace window because it runs immediately after the backups. Check the schedule with proxmox-backup-manager datastore show store1.
A backup that used to be quick takes hours. A guest that was stopped, migrated or restored loses its dirty bitmap, so the next run reads the whole disk on the cluster side even though it uploads very little. The task log shows a long duration with a small upload figure, and the following run is fast again. If every job on the VPS is slow instead, the cause is usually outside the datastore, and CPU steal time from a noisy neighbour is the first thing to measure.
FAQ
Why does my Proxmox Backup Server datastore keep growing when the prune job runs?
Because pruning only removes snapshot metadata: the manifest, the indexes, the log and the notes. The chunks stay on disk until garbage collection deletes the ones no index references any more. Give the datastore a schedule with proxmox-backup-manager datastore update store1 --gc-schedule 'Sun 04:27', and prove it by running df -h on the datastore path before and after proxmox-backup-manager garbage-collection start store1. Expect a lag of at least a day, because phase two only removes chunks whose access time is older than 24 hours and 5 minutes.
How much disk does a Proxmox Backup Server VPS need?
Add up the space each guest actually uses, then add each guest's daily change multiplied by the number of days you keep. That total is a ceiling, since compression and deduplication both work in your favour. Add about a fifth for indexes and working room, then round up to a volume size you can buy. Re-check after two weeks against the real usage in the datastore view, because a guess made before the first backup is always wrong in one direction or the other.
Where should the backup encryption key be stored?
Anywhere except only on the cluster it protects. Proxmox VE keeps it at /etc/pve/priv/storage/<storage>.enc, which is replicated to every node and therefore lost with the cluster. Copy it out on day one, print it with proxmox-backup-client key paperkey, and keep that copy in a different building. Note also that the key takes part in the chunk digest, so replacing it later means the next backup uploads everything again.
Do I need one datastore per Proxmox host, or namespaces?
One datastore, one namespace per source host or cluster. Deduplication works across a datastore and not between datastores, so splitting by host stores the same base images several times. Namespaces keep the backup groups apart, so two hosts that both have a guest with ID 100 cannot collide, and an access control path of the form /datastore/store1/pve-home limits each host's API token to its own namespace.
Will a small VPS keep up as a Proxmox backup server?
Usually, for a homelab, because the chunking and hashing happen on the Proxmox VE node rather than on the backup server. The VPS writes chunks and runs the two heavy jobs, garbage collection and verification. Give it 4 GB of RAM and keep the verification thread counts low. Schedule both jobs outside the backup window, and if they still take far longer than the disk should need, measure steal time before you buy a bigger plan.