[TUTORIAL] zfs_send -> restic

ivenae

Well-Known Member
Feb 11, 2022
193
95
48
42

Proxmox-Backup mit ZFS_SEND und Restic​


Wer virtuelle Maschinen und LXC-Container unter Proxmox betreibt, steht früher oder später vor der Frage nach einem verlässlichen, platzsparenden Backup. Genau dafür ist dieses Skript gedacht: Es sichert VMs, Container und ausgewählte Verzeichnisse automatisiert auf eine externe Festplatte – und kombiniert dabei die Stärken zweier bewährter Werkzeuge.


Die Idee dahinter​


Das Programm nutzt zfs send, um konsistente Snapshots der ZFS-Datasets zu erzeugen, und übergibt diese Streams an Restic, das die Daten dedupliziert, verschlüsselt und versioniert im Backup-Repository ablegt. Restic-Repository und Passwortdatei werden beim ersten Lauf automatisch erzeugt – eine manuelle Einrichtung ist nicht nötig.


Gesichert wird alles, was zu einer Maschine gehört: die Laufwerke der VMs, die Dateisysteme der LXC-Container sowie deren Proxmox-Konfiguration. Optional lassen sich zusätzliche lokale Verzeichnisse einbeziehen. Über eine schlanke Konfigurationsdatei bestimmt man, welche Maschinen ausgeschlossen werden, ob Templates übersprungen werden und welche Ordner unberücksichtigt bleiben sollen.


Full- und Differential-Backups​


Statt jedes Mal alles komplett zu sichern, arbeitet das Skript mit einem Full/Differential-Konzept. Jedes Differential ist ein Delta gegen das letzte Voll-Backup – das spart erheblich Speicherplatz und Zeit. Ein neues Voll-Backup wird nur geschrieben, wenn es wirklich nötig ist: an einem optional festgelegten Wochentag, wenn das letzte Full älter als eine einstellbare Anzahl Tage ist, oder wenn die Grundlage für ein Differential fehlt.


Snapshots​


Die vom Programm gesicherten Snapshots erstellt das Programm NICHT selbst, diese müssen mit externen Mitteln erstellt werden. Ich behelfe mir hier seit jeher mit cv4pve-autosnap , welches ich täglich abends VOR DEM BACKUP aufrufe. Die hier konfigurierte Retention muss größer sein als das Intervall von Full-Snapshots. Sollte der letzte vorgefundene Snapshot älter als 86400 Sekunden (1 Tag) sein, gibt es eine Warnung, auf die mit einem E-Mail Programm gefiltert werden kann.


Automatische Aufräum-Logik​


Alte Sicherungen werden nach einer konfigurierbaren Aufbewahrungsregel (täglich, wöchentlich, monatlich) automatisch entfernt. Dabei achtet das Programm darauf, keine Voll-Backups zu löschen, von denen noch benötigte Differentials abhängen.


Wird ein neues Voll-Backup fällig und der Platz auf dem Datenträger reicht nicht aus, baut das Skript die Historie selbstständig ab – vom ältesten Backup an, notfalls bis das Medium leer ist – und warnt deutlich, falls ein größerer Datenträger nötig wird.


Sicherheit und Betrieb​


Vor jedem Lauf prüft das Programm Systemzustand, Pool-Status und Speicherbelegung. Ein Lock verhindert parallele Läufe, nach dem Backup folgt eine Integritätsprüfung der neu geschriebenen Daten. Auf Wunsch verschickt das Skript eine E-Mail mit dem Ergebnis und versetzt die Backup-Platte anschließend in den Standby.


So entsteht ein Backup, das ohne tägliches Zutun läuft, sparsam mit Speicher umgeht und sich im Ernstfall zuverlässig zurückspielen lässt.


Speicherverbrauch und Deduplizierung von zfs_send​


Sofern der Basis-Snapshot einer Full-Sicherung verschieden ist, ist die Deduplizierung durch restic nahezu 0 %.
Anders ausgedrückt: Zwei verschiedene Full-Sicherungen überschneiden sich gar nicht und lassen sich durch restic nicht deduplizieren – anders als etwa ein RAW Image des Datenträgers (vgl. Proxmox Backup Server).

Dazu kommt, dass ein komprimierter zfs_send Stream (zstd9) in etwa 20 - 25% größer ist als ein zstd-VMA Abbild durch vzdump.

Auf der Haben-Seite stehen die differentiellen Tages-Images, die meist nur ein Bruchteil (1 - 5%) eines Voll-Backups benötigen.
Wer nur alle 2 Tage ein Voll-Backup durchführt, hat die Speicherplatzersparnis (und damit auch die I/O) bereits wieder eingespart.

Anders sieht es bei LXC Containern aus. Diese werden dateibasiert gesichert und lassen sich hervorragend deduplizieren.


Installation und Konfiguration​


Abhängigkeiten:
Code:
apt install restic jq pv

Alle drei Dateien nach /usr/local/bin/ kopieren und .txt Endung entfernen.
Code:
chmod +x ResticBackup.sh
chmod +x ResticRestore.sh

UUID der ext. Festplatte herausfinden und in die /usr/local/bin/config.cfg eintragen.
Code:
blkid

Alle auskommentierten Parameter in der Restic Restore entsprechen dem optionalen Default.

Sicherung aller VMs, LXC Container und den optionalen zu sichernden LOCAL_DIRS durchführen:
Code:
./ResticBackup.sh

Sicherung von Maschine 115 durchführen:
Code:
./ResticBackup.sh 115

Restore
Code:
./ResticRestore.sh --help

# z.B.
./ResticRestore.sh --machine 115 --target 9115

Lizenz​


Gemeinfrei nach CC-0
 

Attachments

Last edited:
Full Backup (mit einer etwas älteren Version, daher etwas andere Bedingungen für notwendigen Puffer)
Code:
[2026-07-09T22:20:17+0200] [INFO] Password file not found, auto-creating: /media/SmartStore/ResticBackup.pw
[2026-07-09T22:20:17+0200] [INFO] Password generated (32 chars, saved with chmod 600)
[2026-07-09T22:20:17+0200] [WARNING] IMPORTANT: Save this password elsewhere, it is needed for restore!
[2026-07-09T22:20:17+0200] [WARNING]   cat '/media/SmartStore/ResticBackup.pw'
[2026-07-09T22:20:17+0200] [INFO] Repo: /media/SmartStore/ResticBackup
[2026-07-09T22:20:17+0200] [INFO] Restic repo not found, auto-initializing: /media/SmartStore/ResticBackup
[2026-07-09T22:20:19+0200] [INFO] Restic repo initialized and ready
[2026-07-09T22:20:20+0200] [INFO] Repo data size before backup: 0 B
[2026-07-09T22:20:20+0200] [INFO] === Starting Proxmox backup ===
[2026-07-09T22:20:21+0200] [INFO] --- VM 100 (vDC01) ---
[2026-07-09T22:20:24+0200] [INFO]   Disk count changed: repo has 0, VM has 4 -> forcing full backup
[2026-07-09T22:20:24+0200] [INFO]   Disk count changed -> Full backup
[2026-07-09T22:20:24+0200] [INFO]   Full backup: efidisk0
[2026-07-09T22:20:25+0200] [INFO]   OK: efidisk0 full (rpool/data/vm-100-disk-0)
[2026-07-09T22:20:26+0200] [INFO]   Disk count changed -> Full backup
[2026-07-09T22:20:26+0200] [INFO]   Full backup: scsi0
[2026-07-09T23:28:06+0200] [INFO]   OK: scsi0 full (rpool/data/vm-100-disk-4)
[2026-07-09T23:28:07+0200] [INFO]   Disk count changed -> Full backup
[2026-07-09T23:28:07+0200] [INFO]   Full backup: scsi1
[2026-07-09T23:40:45+0200] [INFO]   OK: scsi1 full (rpool/data/vm-100-disk-1)
[2026-07-09T23:40:45+0200] [INFO]   Disk count changed -> Full backup
[2026-07-09T23:40:45+0200] [INFO]   Full backup: tpmstate0
[2026-07-09T23:40:47+0200] [INFO]   OK: tpmstate0 full (rpool/data/vm-100-disk-2)
[2026-07-09T23:40:48+0200] [INFO]   OK: config
[2026-07-09T23:40:48+0200] [INFO] No LXC containers found
[2026-07-09T23:40:51+0200] [INFO] Data added this run: 354.4 GiB
[2026-07-09T23:40:51+0200] [INFO] Disk space: 538.6 GiB free of 893.1 GiB (538.6 GiB), need 443.7 GiB (10% + written)
[2026-07-09T23:40:51+0200] [INFO] === Proxmox backup complete ===
[2026-07-09T23:40:51+0200] [INFO] VMs=1 CTs=0 Disks=4 (Full=4 Incr=0) Skipped=0 | Written=354.4 GiB | Errors=0 Warnings=2
[2026-07-09T23:40:51+0200] [INFO] === Retention: forget + prune ===
[2026-07-09T23:40:51+0200] [INFO] Policy: keep-daily=10 keep-weekly=6 keep-monthly=6
[2026-07-09T23:40:51+0200] [INFO] Protecting full backups referenced by kept incrementals
[2026-07-09T23:40:52+0200] [INFO] Nothing to forget
[2026-07-09T23:40:52+0200] [INFO] === Post-backup integrity check ===
[2026-07-09T23:40:52+0200] [INFO] Data written this run: 354.4 GiB
[2026-07-09T23:40:52+0200] [INFO] Checking: 10G (max: 10 GiB)
[2026-07-09T23:41:19+0200] [INFO] Integrity check passed


Diff Backup. Statt 1,5 Stunden benötigt es nur 1 min. Yay.
Code:
Sichere UUID f0876d4c-8a68-4afe-8c3d-a5c6ae8e59dd
[2026-07-13T12:30:01+0200] [INFO] Base: /media/SmartStore
[2026-07-13T12:30:01+0200] [INFO] Repo: /media/SmartStore/ResticBackup
[2026-07-13T12:30:05+0200] [INFO] Repo data size before backup: 354.4 GiB
[2026-07-13T12:30:05+0200] [INFO] === Starting Proxmox backup ===
[2026-07-13T12:30:05+0200] [INFO] === Planungsphase (Full/Diff-Bestimmung) ===
[2026-07-13T12:30:13+0200] [INFO]   Alle Disks differentiell sicherbar -> kein erzwungenes Full
[2026-07-13T12:30:13+0200] [INFO] === Platzpruefung vor Backup ===
[2026-07-13T12:30:13+0200] [INFO]   Diff-/LXC-/LOCAL-Lauf -> Diff-Run/No VM
[2026-07-13T12:30:13+0200] [INFO]   [Diff-Run/No VM] Frei: 538.6 GiB / 893.1 GiB  |  Puffer (5%): 44.7 GiB
[2026-07-13T12:30:13+0200] [INFO]   [Diff-Run/No VM] Genug frei (>= Puffer) -> kein Pruning noetig
[2026-07-13T12:30:14+0200] [INFO] --- VM 100 (vDC01) ---
[2026-07-13T12:30:15+0200] [INFO]   OK: config
[2026-07-13T12:30:16+0200] [INFO]   Config-Snapshot-ID: 48cf4b7e
[2026-07-13T12:30:18+0200] [INFO]   Differential: autodaily260709190002 -> autohourly260713100002
[2026-07-13T12:30:19+0200] [INFO]   OK: efidisk0 diff (rpool/data/vm-100-disk-0)
[2026-07-13T12:30:22+0200] [INFO]   Age: differential is 3 day(s) after full (a9c732da)
[2026-07-13T12:30:23+0200] [INFO]   Differential: autodaily260709190002 -> autohourly260713100002
[2026-07-13T12:30:26+0200] [INFO]   OK: scsi0 diff (rpool/data/vm-100-disk-4)
[2026-07-13T12:30:29+0200] [INFO]   Age: differential is 3 day(s) after full (bec955ba)
[2026-07-13T12:30:30+0200] [INFO]   Differential: autodaily260709190002 -> autohourly260713100002
[2026-07-13T12:30:47+0200] [INFO]   OK: scsi1 diff (rpool/data/vm-100-disk-1)
[2026-07-13T12:30:50+0200] [INFO]   Age: differential is 3 day(s) after full (03834f68)
[2026-07-13T12:30:52+0200] [INFO]   Differential: autodaily260709190002 -> autohourly260713100002
[2026-07-13T12:30:53+0200] [INFO]   OK: tpmstate0 diff (rpool/data/vm-100-disk-2)
[2026-07-13T12:30:56+0200] [INFO]   Age: differential is 3 day(s) after full (6c5b8dfd)
[2026-07-13T12:30:56+0200] [INFO] No LXC containers found
[2026-07-13T12:30:59+0200] [INFO] Data added this run: 1.8 GiB
[2026-07-13T12:30:59+0200] [INFO] Disk space: 536.8 GiB frei von 893.1 GiB (Warnschwelle 10% = 89.3 GiB)
[2026-07-13T12:30:59+0200] [INFO] === Proxmox backup complete ===
[2026-07-13T12:30:59+0200] [INFO] VMs=1 CTs=0 Disks=4 (Full=0 Diff=4) Skipped=0 | Written=1.8 GiB | Errors=0 Warnings=0
[2026-07-13T12:30:59+0200] [INFO] === Retention: forget + prune ===
[2026-07-13T12:30:59+0200] [INFO] Policy: keep-daily=10 keep-weekly=3 keep-monthly=2
[2026-07-13T12:30:59+0200] [INFO] Protecting full backups referenced by kept differentials
[2026-07-13T12:31:00+0200] [INFO] Nothing to forget
[2026-07-13T12:31:00+0200] [INFO] === Post-backup integrity check ===
[2026-07-13T12:31:00+0200] [INFO] Data written this run: 1.8 GiB
[2026-07-13T12:31:00+0200] [INFO] Checking: 2G (max: 10 GiB)
[2026-07-13T12:31:19+0200] [INFO] Integrity check passed
fstrim /media/SmartStore
fstrim: /media/SmartStore: the discard operation is not supported
fstrim not supported
Unmount /media/SmartStore
Setze standby: /dev/sda

/dev/sda:
 issuing standby command


Auch hier: Das Full Backup wurde mit einer alten Version erstellt, daher fehlen noch ein paar Tags und die Größe kann nicht ermittelt werden.
Die Größe der Config-Datei entspricht der Größe aller Datenträger des Diffs + aller Datenträger des zugehörigen Full Backups.
Code:
root:/usr/local/bin# ./ResticRestore.sh --list --size
SNAP ID     Backup Time       TYPE     MACHINE    NAME                      DISK              SIZE  PARENT      CONFIG
ca828acf    2026-07-11 02:32  CONFIG   vm-100     Server                    -            504.33GiB  -           ca828acf
a928b4dc    2026-07-11 02:32  FULL     vm-100     Server                    scsi1        504.33GiB  -           ca828acf
facd9984    2026-07-11 04:12  CONFIG   vm-101     Client                    -             81.48GiB  -           facd9984
0b9e3348    2026-07-11 04:12  FULL     vm-101     Client                    scsi0         81.48GiB  -           facd9984
493e4519    2026-07-11 23:42  CONFIG   vm-100     Server                    -            506.57GiB  -           493e4519
7667631f    2026-07-11 23:42  DIFF     vm-100     Server                    scsi1        506.57GiB  a928b4dc    493e4519
3cadf33d    2026-07-11 23:42  CONFIG   vm-101     Client                    -             82.94GiB  -           3cadf33d
592c8e47    2026-07-11 23:42  DIFF     vm-101     Client                    scsi0         82.94GiB  0b9e3348    3cadf33d
6b6dacc0    2026-07-14 01:00  CONFIG   vm-100     Server                    -            508.04GiB  -           6b6dacc0
5cee0ad5    2026-07-14 01:00  DIFF     vm-100     Server                    scsi1        508.04GiB  a928b4dc    6b6dacc0
cda6558a    2026-07-14 01:01  CONFIG   vm-101     Client                    -             83.08GiB  -           cda6558a
6557ead4    2026-07-14 01:01  DIFF     vm-101     Client                    scsi0         83.08GiB  0b9e3348    cda6558a


root:/usr/local/bin# ./ResticRestore.sh --target 667 --machine 101
Restic Restore V 4.3.6
[01:26:58] [INFO] Machine: vm-101 -> Target: 667
[01:26:58] [INFO] Config snapshot: cda6558a
[01:26:58] [INFO] Restoring backup run config-id=cda6558a
[01:26:58] [INFO] === Restoring VM 101 -> 667 ===
repository 622c9c8a opened (version 2, compression level auto)
[0:01] 100.00%  28 / 28 index files loaded
restoring snapshot cda6558a of [/vm-101.conf] at 2026-07-14 01:01:23.907287838 +0200 CEST by root to /tmp/restic-restore-cfg-3733521
Summary: Restored 1 files/dirs (581 B) in 0:00
[01:27:01] [INFO]   Config: /etc/pve/qemu-server/667.conf
[01:27:01] [INFO] Restoring disks ...
[01:27:12] [INFO]   scsi0: Full (0b9e3348, 81.48GiB) + Differential (6557ead4, 1.60GiB)
scsi0-full: 81.5GiB 0:19:17 [72.1MiB/s] [================================================================================================================================================================>] 100%
scsi0-diff: 1.60GiB 0:00:24 [67.8MiB/s] [================================================================================================================================================================>] 100%
[01:46:57] [INFO]   OK: scsi0 (full + differential)
[01:46:57] [INFO] === VM 667 restore complete ===
[01:46:57] [INFO] Start with: qm start 667
 
Last edited:
  • Like
Reactions: Johannes S