-
Notifications
You must be signed in to change notification settings - Fork 2
Backup Management
This guide covers the full backup lifecycle for QEMU VMs and LXC containers in PVE4J — from creating backups, inspecting and managing the resulting files, applying retention, restoring a guest, to driving cluster-wide scheduled backup jobs.
All backup operations follow the Proxmox VE API exactly. Long-running operations return a
PveTask and support .waitForCompletion(proxmox) / .onCompletion(...).
- Creating a Backup (single VM)
- Creating Backups at the Node Level (vzdump)
- Backup Defaults
- Extracting a Guest Config from an Archive
- Listing Backups
- Inspecting a Backup Volume
- Editing Notes and Protection
- Deleting a Backup
- Retention / Pruning
- Restoring a VM
- Restoring a Container (LXC)
- Scheduled Backup Jobs
The simplest entry point backs up one VM. The VMID is set for you.
try {
PveTask task = proxmox.getNodes()
.get("pve-node-01")
.getQemu()
.get(100)
.backup() // uses Proxmox defaults
.waitForCompletion(proxmox)
.execute();
System.out.println("Backup completed!");
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}import fr.freshperf.pve4j.entities.nodes.node.qemu.PveQemuBackupOptions;
try {
PveQemuBackupOptions options = PveQemuBackupOptions.builder()
.storage("DATA") // target storage for the archive
.mode("snapshot") // "snapshot" (default), "suspend", "stop"
.compress("zstd") // "0", "1", "gzip", "lzo", "zstd"
.notesTemplate("{{guestname}} - {{node}}")
.protected_(true) // protect the archive from pruning/deletion
.pruneBackups("keep-last=3,keep-daily=7")
.bwlimit(102400) // I/O limit in KiB/s (0 = unlimited)
.build();
PveTask task = proxmox.getNodes()
.get("pve-node-01")
.getQemu()
.get(100)
.backup(options)
.waitForCompletion(proxmox)
.execute();
System.out.println("Backup completed with options!");
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}
PveQemuBackupOptionsexposes the full per-VMvzdumpsurface (storage, mode, compress, notes-template, protected, remove, bwlimit, ionice, prune-backups, performance, fleecing, pigz, zstd, lockwait, stopwait, script, tmpdir, dumpdir, quiet, stdexcludes, mailto, mailnotification, notification-mode, job-id).
To back up several guests at once (a list of VMIDs, a whole pool, or all guests on the node),
use the node-level vzdump facade.
import fr.freshperf.pve4j.entities.nodes.node.vzdump.PveVzdumpOptions;
try {
PveVzdumpOptions options = PveVzdumpOptions.builder()
.vmid("100,101,102") // or .all(true), or .pool("production")
.storage("DATA")
.mode("snapshot")
.compress("zstd")
.build();
PveTask task = proxmox.getNodes()
.get("pve-node-01")
.getVzdump()
.backup(options)
.waitForCompletion(proxmox)
.execute();
System.out.println("Node-level backup started!");
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}Read the effective default vzdump parameters for a node (optionally for a given storage).
import fr.freshperf.pve4j.entities.nodes.node.vzdump.PveVzdumpDefaults;
try {
PveVzdumpDefaults defaults = proxmox.getNodes()
.get("pve-node-01")
.getVzdump()
.getDefaults("DATA") // or getDefaults() for the default storage
.execute();
System.out.println("Default mode: " + defaults.getMode());
System.out.println("Default compress: " + defaults.getCompress());
System.out.println("Default retention: " + defaults.getPruneBackups());
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}Read the guest configuration stored inside a backup archive, without restoring it. The argument is a backup volume identifier (volid).
try {
String config = proxmox.getNodes()
.get("pve-node-01")
.getVzdump()
.extractConfig("DATA:backup/vzdump-qemu-100-2026_05_31-18_21_01.vma.zst")
.execute();
System.out.println(config);
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}Backups are listed through the storage that holds them. You can list all backups, or only those belonging to a specific VM.
import fr.freshperf.pve4j.entities.nodes.node.storage.PveStorageContent;
import java.util.List;
try {
List<PveStorageContent> backups = proxmox.getNodes()
.get("pve-node-01")
.getStorage()
.get("DATA")
.getBackups(100) // or getBackups() for every backup on the storage
.execute();
for (PveStorageContent backup : backups) {
System.out.println("Volid: " + backup.getVolid()); // pass this to restore/extractConfig
System.out.println("Size: " + backup.getSize() + " bytes");
System.out.println("Notes: " + backup.getNotes());
System.out.println("Protected: " + backup.isProtected());
System.out.println("---");
}
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}The
volidreturned here (e.g.DATA:backup/vzdump-qemu-100-...vma.zst) is exactly the value expected by restore and extractConfig.
import fr.freshperf.pve4j.entities.nodes.node.storage.PveStorageVolume;
try {
PveStorageVolume volume = proxmox.getNodes()
.get("pve-node-01")
.getStorage()
.get("DATA")
.getVolume("DATA:backup/vzdump-qemu-100-2026_05_31-18_21_01.vma.zst")
.execute();
System.out.println("Path: " + volume.getPath());
System.out.println("Format: " + volume.getFormat());
System.out.println("Protected: " + volume.isProtected());
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}Update a backup's notes and/or its protection status. Pass null to leave a value unchanged.
try {
proxmox.getNodes()
.get("pve-node-01")
.getStorage()
.get("DATA")
.updateVolumeAttributes(
"DATA:backup/vzdump-qemu-100-2026_05_31-18_21_01.vma.zst",
"Keep until end of quarter", // new notes
true) // protect from deletion/pruning
.execute();
System.out.println("Backup attributes updated!");
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}try {
PveTask task = proxmox.getNodes()
.get("pve-node-01")
.getStorage()
.get("DATA")
.deleteVolume("DATA:backup/vzdump-qemu-100-2026_05_31-18_21_01.vma.zst")
.waitForCompletion(proxmox)
.execute();
System.out.println("Backup deleted!");
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}Protected backups cannot be deleted until you clear their protection with updateVolumeAttributes.
Preview which backups a prune would keep or remove (dry-run), then prune for real. Both use the same options; when no retention string is given, the storage configuration is used.
import fr.freshperf.pve4j.entities.nodes.node.storage.PvePruneBackupsOptions;
import fr.freshperf.pve4j.entities.nodes.node.storage.PvePruneBackupEntry;
import java.util.List;
try {
PvePruneBackupsOptions options = PvePruneBackupsOptions.builder()
.pruneBackups("keep-last=3,keep-weekly=4")
.type("qemu")
.vmid(100)
.build();
var storage = proxmox.getNodes().get("pve-node-01").getStorage().get("DATA");
// 1. Dry-run preview
List<PvePruneBackupEntry> preview = storage.getPruneBackups(options).execute();
for (PvePruneBackupEntry entry : preview) {
System.out.println(entry.getVolid() + " -> " + entry.getMark()); // keep / remove / protected / renamed
}
// 2. Actually prune
PveTask task = storage.pruneBackups(options)
.waitForCompletion(proxmox)
.execute();
System.out.println("Prune completed!");
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}Restore a VM from a backup archive. The archive is a Proxmox volume identifier in the
form <storage>:backup/<filename> — not a bare filename or a path. Passing a bare
filename triggers a Proxmox error: "Only root can pass arbitrary filesystem paths".
import fr.freshperf.pve4j.entities.nodes.node.qemu.PveQemuRestoreOptions;
try {
PveQemuRestoreOptions options = PveQemuRestoreOptions.builder()
// Either build the volid from storage + file name...
.archive("DATA", "vzdump-qemu-100-2026_05_31-18_21_01.vma.zst")
// ...or pass the full volid directly:
// .archive("DATA:backup/vzdump-qemu-100-2026_05_31-18_21_01.vma.zst")
.force(true) // overwrite an existing VM with the same VMID
.storage("local-lvm") // target storage for the restored disks (optional)
.start(false) // start after restore (optional)
.build();
PveTask task = proxmox.getNodes()
.get("pve-node-01")
.getQemu()
.restore(100, options)
.waitForCompletion(proxmox)
.execute();
System.out.println("VM restored!");
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}The most reliable way to obtain a valid archive value is to take the volid straight from
Listing Backups:
String volid = proxmox.getNodes().get("pve-node-01").getStorage().get("DATA")
.getBackups(100).execute().get(0).getVolid();
proxmox.getNodes().get("pve-node-01").getQemu()
.restore(100, PveQemuRestoreOptions.builder().archive(volid).force(true).build())
.waitForCompletion(proxmox)
.execute();Other restore options:
unique(true)(random MAC addresses),liveRestore(true),bwlimit(int),pool(String),name(String),description(String).
Restoring an LXC container works just like a VM restore, but through the getLxc() facade and
PveLxcRestoreOptions. The archive is the backup volume identifier
(<storage>:backup/<filename>) of an LXC backup (vzdump-lxc-...tar.zst), exactly as returned
by Listing Backups.
import fr.freshperf.pve4j.entities.nodes.node.lxc.PveLxcRestoreOptions;
try {
PveLxcRestoreOptions options = PveLxcRestoreOptions.builder()
// Either build the volid from storage + file name...
.archive("DATA", "vzdump-lxc-200-2026_05_31-18_21_01.tar.zst")
// ...or pass the full volid directly:
// .archive("DATA:backup/vzdump-lxc-200-2026_05_31-18_21_01.tar.zst")
.force(true) // overwrite an existing container with the same VMID
.storage("local-lvm") // target storage for the restored rootfs (optional)
.unique(true) // assign a random MAC address (optional)
.start(false) // start after restore (optional)
.build();
PveTask task = proxmox.getNodes()
.get("pve-node-01")
.getLxc()
.restore(200, options)
.waitForCompletion(proxmox)
.execute();
System.out.println("Container restored!");
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}Other restore options:
bwlimit(int),pool(String),hostname(String),password(String),description(String).
Backup jobs are cluster-wide schedules that periodically back up the selected guests. They are
accessed via proxmox.getCluster().getBackup().
import fr.freshperf.pve4j.entities.cluster.backup.PveBackupJob;
import java.util.List;
try {
List<PveBackupJob> jobs = proxmox.getCluster().getBackup().getJobs().execute();
for (PveBackupJob job : jobs) {
System.out.printf("%s | enabled=%s | schedule=%s | storage=%s%n",
job.getId(), job.isEnabled(), job.getSchedule(), job.getStorage());
}
PveBackupJob job = proxmox.getCluster().getBackup().getJob(jobs.get(0).getId()).execute();
System.out.println("Next run (unix): " + job.getNextRun());
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}import fr.freshperf.pve4j.entities.cluster.backup.PveBackupJobCreateOptions;
try {
PveBackupJobCreateOptions options = PveBackupJobCreateOptions.builder()
.schedule("mon..fri 02:00") // systemd calendar event subset
.storage("DATA")
.vmid("100,101") // or .all(true) / .pool("production")
.mode("snapshot")
.compress("zstd")
.enabled(true)
.comment("Weeknight backups")
.pruneBackups("keep-daily=7,keep-weekly=4")
.build();
proxmox.getCluster().getBackup().createJob(options).execute();
System.out.println("Backup job created!");
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}import fr.freshperf.pve4j.entities.cluster.backup.PveBackupJobUpdateOptions;
import java.util.List;
try {
// Update some settings; use delete(...) to reset settings back to their defaults.
PveBackupJobUpdateOptions update = PveBackupJobUpdateOptions.builder()
.enabled(false)
.delete(List.of("comment"))
.build();
proxmox.getCluster().getBackup().updateJob("backup-xxxxxxxx-xxxx", update).execute();
// Delete a job
proxmox.getCluster().getBackup().deleteJob("backup-xxxxxxxx-xxxx").execute();
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}import fr.freshperf.pve4j.entities.cluster.backup.PveBackupIncludedVolumes;
import fr.freshperf.pve4j.entities.cluster.backup.PveBackupGuest;
import java.util.List;
try {
// Which guests/volumes a given job covers
PveBackupIncludedVolumes included = proxmox.getCluster()
.getBackup()
.getIncludedVolumes("backup-xxxxxxxx-xxxx")
.execute();
for (PveBackupIncludedVolumes.Guest guest : included.getChildren()) {
System.out.println("Guest " + guest.getId() + " (" + guest.getType() + ")");
for (PveBackupIncludedVolumes.Volume volume : guest.getChildren()) {
System.out.printf(" %s included=%s (%s)%n",
volume.getId(), volume.isIncluded(), volume.getReason());
}
}
// Guests not covered by ANY backup job
List<PveBackupGuest> uncovered = proxmox.getCluster()
.getBackup()
.getGuestsNotBackedUp()
.execute();
for (PveBackupGuest guest : uncovered) {
System.out.printf("Not backed up: %d (%s) %s%n",
guest.getVmid(), guest.getType(), guest.getName());
}
} catch (ProxmoxAPIError | InterruptedException e) {
e.printStackTrace();
}The
backup-infodirectory index itself is available viaproxmox.getCluster().getBackup().getBackupInfo(), which returns the names of the available sub-resources; the data is exposed by the methods above.
- VM Management - Full QEMU VM lifecycle
- Storage Management - Storage pools and content
- Cluster Operations - Cluster-wide operations and HA
- Task Handling - Working with asynchronous tasks
Getting Started
Core Concepts
Resource Management
Infrastructure
Security
Community