Proxmox VE Backup & Restore Tool
A self-contained bash script for backing up and restoring all critical Proxmox VE configuration files. Archives are stored locally (never on the remote host) and can be used to rebuild a PVE node after boot drive failure.
Overview
The script connects to a Proxmox node (locally or over SSH), collects configuration files from across the filesystem, packages them into a timestamped .tar.gz archive, and stores it on the machine running the script. Restore extracts a backup archive and pushes files back.
Requirements
- Source node: Proxmox VE (any supported version)
- Machine running the script: Linux with
bash,tar,gzip - Remote access: SSH access to the Proxmox node (passwordless key recommended)
- Permissions: Root or passwordless sudo on the Proxmox node
- rsync (optional): Installed on both machines for incremental transfers — falls back to tar/cat/pipe if absent
Usage
Usage: pve-backup.sh <command> [options]
Commands:
backup Create a backup of Proxmox configuration
restore Restore Proxmox configuration from a backup
list List available backups
verify Verify a backup archive's integrity (SHA256)
Backup options:
-d, --dir DIR Local backup directory (default: ./pve-backups)
-h, --host HOST Remote Proxmox host (SSH)
-u, --user USER SSH user (default: root)
-i, --identity FILE SSH identity file
Restore / Verify options:
-d, --dir DIR Directory containing backup archive
-f, --file FILE Specific backup archive to restore/verify
-y, --yes Skip confirmation prompts (DANGEROUS)
List options:
-d, --dir DIR Directory to list backups from
Examples
# Local backup (must be root on the Proxmox node)
./pve-backup.sh backup
# Remote backup with custom directory
./pve-backup.sh backup -h 192.168.1.100 -d /mnt/backups
# Remote backup as non-root user (uses sudo -n automatically)
./pve-backup.sh backup -h 192.168.1.100 -u admin -i ~/.ssh/id_rsa
# Verify a backup archive
./pve-backup.sh verify -f pve-backups/pve-backup-prox1-20250101_120000.tar.gz
# Restore the latest backup
./pve-backup.sh restore
# Restore a specific backup archive
./pve-backup.sh restore -f /path/to/pve-backup-prox1-20250101_120000.tar.gz
# Non-interactive restore (for automation)
./pve-backup.sh restore -f /path/to/backup.tar.gz -y
# List all local backups
./pve-backup.sh list
Defaults
| Setting | Default |
|---|---|
| Backup directory | ./pve-backups (relative to script location) |
| SSH user | root |
| Archive name format | pve-backup-{hostname}-{YYYYMMDD}_{HHMMSS}.tar.gz |
Transfer Method
The script uses one of two transfer methods depending on what's available:
rsync (preferred)
When connecting to a remote host, the script checks if rsync is installed on the remote side. If yes:
- File-based configs (
/etc/pve/,/etc/network/interfaces, APT sources, etc.) are transferred viarsync -a - Subsequent backups only transfer changed files, reducing bandwidth and time
- Permissions and ownership are preserved natively by rsync
- If sudo is needed,
--rsync-path="sudo -n rsync"is used automatically
tar/cat/pipe (fallback)
If rsync is not available on the remote host, the script falls back to the original method:
/etc/pve/is tarballed on the remote, piped through SSH, and extracted locally- Individual files are fetched with
cat > fileover SSH - Directories (APT sources, sysctl, cron) are tarballed with
cp -a, then piped through SSH - Slightly slower on repeated runs since everything is transferred every time
What Gets Backed Up
/etc/pve/ (pmxcfs snapshot)
The core Proxmox cluster filesystem. Transferred via rsync -a when available, otherwise tarballed and piped over SSH. Either way, runtime-only data is excluded:
| Included | Description |
|---|---|
datacenter.cfg |
Datacenter-wide settings |
storage.cfg |
Storage pool definitions |
user.cfg |
Users, groups, and permissions |
domains.cfg |
Authentication realms (LDAP, AD, etc.) |
vzdump.cfg |
Default backup job settings |
nodes/{node}/qemu-server/*.conf |
VM configuration files |
nodes/{node}/lxc/*.conf |
LXC container configuration files |
nodes/{node}/pve-ssl.pem |
Web UI SSL certificate |
nodes/{node}/pve-ssl.key |
Web UI SSL private key |
nodes/{node}/pveproxy-ssl.pem |
Custom SSL certificate (if set) |
nodes/{node}/pveproxy-ssl.key |
Custom SSL private key (if set) |
sdn/ |
Software Defined Network configs (VLANs, VNets, subnets) |
ha/ |
High Availability resources and groups |
firewall/ |
Datacenter and node-level firewall rules |
priv/tfa.cfg |
TFA tokens (restore with caution) |
priv/shadow |
User password hashes |
| Excluded | Reason |
|---|---|
.rrd/ |
Round-robin database (runtime stats, regenerated) |
priv/known_hosts |
SSH host keys (regenerated on boot) |
*.lock |
Runtime locks (ephemeral) |
Network Configuration
| File | Description |
|---|---|
/etc/network/interfaces |
Bridge, bond, VLAN, and NIC definitions |
/etc/hostname |
System hostname |
/etc/hosts |
Static hostname resolution |
/etc/fstab |
Filesystem mount table (ZFS, NFS, CIFS mounts, etc.) |
/etc/resolv.conf |
DNS nameservers and search domains |
APT Sources
| Path | Description |
|---|---|
/etc/apt/sources.list |
Main APT repository list |
/etc/apt/sources.list.d/ |
Third-party and Proxmox enterprise/no-subscription repos |
/etc/apt/auth.conf |
APT authentication credentials |
Cluster Configuration
| File | Description |
|---|---|
/etc/corosync/corosync.conf |
Corosync cluster ring and membership config |
Ceph Storage
| File | Description |
|---|---|
/etc/ceph/ceph.conf |
Ceph cluster connection settings |
System Tuning
| Path | Description |
|---|---|
/etc/sysctl.d/ |
Kernel parameter overrides (network buffers, etc.) |
/etc/cron.d/ |
System cron jobs |
/etc/cron.daily/ |
Daily cron scripts |
Package Manifest
| File | Contents |
|---|---|
packages.txt |
Full list of installed packages (dpkg-query -W) |
packages-manual.txt |
Manually installed packages (apt-mark showmanual) |
pveversion.txt |
Proxmox VE version string |
Firewall Rules
| File | Description |
|---|---|
iptables.rules |
IPv4 iptables ruleset (via iptables-save) |
ip6tables.rules |
IPv6 ip6tables ruleset (via ip6tables-save) |
Metadata (META file inside archive)
hostname: proxmox1
created: 2025-01-01T12:00:00Z
pveversion: pve-manager/8.2.4/123456789abc
pve_nodes: proxmox1 proxmox2 proxmox3
Restore Process
- Extracts the backup archive to a temporary directory
- Creates a rollback snapshot of the current
/etc/pveat/var/tmp/pve-restore-rollback-{timestamp}/(and also a tarball at/etc/pve-rollback.tar.gzon the remote node) - Restores
/etc/pveby piping the archived tarball intotar -x -C /etc/pveon the target node - Restores network configs (
/etc/network/interfaces,hostname,hosts,resolv.conf) by piping each file through SSH - Restores APT sources, sysctl, and cron overrides
- Does not restore the package manifest — this is for reference only
After restore:
- Reboot recommended — network and hostname changes apply at boot
- Restart
pveproxyif the web UI behaves oddly after/etc/pverestore
SSH Non-Root Access (sudo)
If you connect with a non-root SSH user (-u admin), the script automatically detects this and prefixes all remote commands with sudo -n. The remote user must have passwordless sudo configured:
echo "admin ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/admin
If passwordless sudo is not available, the script exits with a clear error.
Archive Format
Each backup is a gzipped tarball with this internal structure. The /etc/pve/ data and sysconfig may be stored either as raw directories (when rsync was used) or as nested tarballs (when the fallback method was used):
pve-backup-proxmox1-20250101_120000.tar.gz
├── META # Metadata (hostname, date, version, nodes)
└── etc/
├── pve/
│ ├── datacenter.cfg # (raw files — rsync) or
│ └── pve-config.tar.gz # (nested tarball — fallback)
├── apt/
│ ├── sources.list # (raw files — rsync) or
│ └── apt-sources.tar.gz # (nested tarball — fallback)
└── system/
├── interfaces # /etc/network/interfaces
├── hostname # /etc/hostname
├── hosts # /etc/hosts
├── resolv.conf # /etc/resolv.conf
├── fstab # /etc/fstab
├── corosync.conf # /etc/corosync/corosync.conf
├── ceph.conf # /etc/ceph/ceph.conf
├── sysctl.d/ # (raw dir — rsync) or
├── sysconfig.tar.gz # (nested tarball — fallback)
├── packages.txt # Full package manifest
├── packages-manual.txt # Manually installed packages
├── pveversion.txt # pveversion output
├── iptables.rules # iptables-save output
└── ip6tables.rules # ip6tables-save output
The restore logic handles both layouts transparently.
Verification
Every backup automatically generates a .sha256 checksum file alongside the archive. You can verify integrity at any time:
# Verify the latest backup
./pve-backup.sh verify -f pve-backups/pve-backup-prox1-20250101_120000.tar.gz
The verify command checks:
- The archive is a valid gzip'd tar file (no corruption)
- The SHA256 checksum matches the stored
.sha256file (no tampering)
If no .sha256 file exists (e.g., from older backups), one is generated on the spot.
Security Notes
- Backup over SSH: All data is transferred over the encrypted SSH channel. No data is stored on the remote host.
- Sensitive data: The archive contains SSL private keys, password hashes (
/etc/pve/priv/shadow), and TFA tokens. Treat backup archives as sensitive credentials. - SSH keys: Use
-ito specify an SSH identity file. Agent forwarding is not required.
Limitations
- Package manifest is for reference only — the script does not install/restore packages
- Firewall rules are exported via
iptables-save— restoration requires manualiptables-restore(PVE firewall is handled via/etc/pve/firewall/in the pve-config archive) - Ceph config is backed up, but the Ceph monitor maps and OSD tree are not
- VM/CT disk images and backups are not included — use the built-in Proxmox Backup Server or
vzdumpfor those
Future Enhancements (not yet implemented)
These are left as exercises for the reader or future development:
- Retention / rotation —
--keep Nflag to auto-prune backups older than N days (or keep only N most recent) - Cron / systemd timer — drop-in unit for daily automated backups
- Encryption — GPG or
ageencrypt the archive before writing it to disk, decrypt on restore - Email / notification — alert on backup failure or successful completion
- Restore dry-run — show what would be restored without actually pushing files