[TUTORIAL] zfs_send -> restic

ivenae

Well-Known Member
Feb 11, 2022
194
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
Cooles Projekt, sieht echt durchdacht aus. restic check verifiziert dir nur die restic-Chunks, ob der zfs-send-Stream danach auch sauber per zfs receive wieder zurückkommt, sagt dir das nicht. Ich würd regelmäßig echt mal einen Test-Restore in ein Scratch-Dataset fahren, gerade wegen der Diff-Ketten. Eine fehlende oder kaputte Full-Basis reißt dir sonst alle Diffs mit, und das merkst du dann zum blödesten Zeitpunkt.

Warum restic statt PBS? Chunk-Dedup über alles statt ~0% zwischen den Fulls, Incrementals per Dirty-Bitmap, verify/GC hättest du geschenkt. Oder gehts dir gezielt um die eine portable USB-Platte, die danach in den Standby geht? Das kann PBS halt nicht.
 
  • Like
Reactions: Johannes S
Exakt – Ich betreibe das hauptsächliche Backup noch lokal auf Platte, weil extrem schnell, extrem billig.
Das reduziert die Schreibleistung auch in der Praxis auf ca. 10% ggü. vma.zstd sofern man bis zu 15 Diff-Backups je Fuil-Backup macht.

Es gibt keine "Diff-Ketten". Es handelt sich um ein differentielles Backup, kein inkrementelles.
Jedes Backup ist entweder ein Full, oder die Differenz zum letzten Full Backup. Mehr als zwei Iterationen werden für den Restore daher nicht benötigt.
Ich habe jetzt gut 10 Restores auf verschiedenen Geräten getestet. Es läuft wirklich gut.

Zum Thema Restore-Tests: Damit haben auch Veeam Nutzer ihre Erfahrung gemacht. Und auch mit PBS und VMA/VZDump kommt man um Restore-Tests nicht herum.
 
Last edited: