* DONE Framework-Notebook mit restic aufs Ugreen-NAS sichern :blog:backup:restic:linux:docker: CLOSED: [2026-10-25 Sun 15:45] :PROPERTIES: :CREATED: [2026-10-24 Sat 16:30] :ID: 2026-10-25-restic-notebook-backup-nas :END: :LOGBOOK: - State "DONE" from "NEXT" [2026-10-25 Sun 15:45] :END: Mein Framework 13 läuft seit einiger Zeit mit Kubuntu, aber zu meiner Schande hatte ich noch keine richtige Backup-Strategie eingerichtet. Seit dem Umzug auf das neue Ugreen-NAS ist dort genug Platz, also war es Zeit, das nachzuholen. Meine Wünsche waren: - Das Home-Verzeichnis und die Programm-Konfigurationen täglich sichern. - Zusätzlich einmal pro Woche das ganze System, damit ich den Laptop im Notfall komplett wiederherstellen kann. - Nur sichern, wenn der Laptop am Netzteil hängt. - Von unterwegs soll es auch funktionieren. - Aufbewahrung: 4 tägliche, 2 wöchentliche und 4 monatliche Sicherungen. In diesem Artikel zeige ich, wie ich das mit [[https://restic.net][restic]], einem [[https://github.com/restic/rest-server][rest-server]] im Docker auf dem NAS und zwei systemd-Timern umgesetzt habe. Am Ende steht ein längerer Abschnitt zur Fehlersuche, denn der erste Lauf war zunächst quälend langsam. ** Die Idee #+CAPTION: Der Weg der Daten: zuhause direkt übers LAN, unterwegs über Tailscale #+ATTR_HTML: :alt Ablaufdiagramm vom Notebook über LAN oder Tailscale zum rest-server auf dem NAS und weiter zur USB-Platte :width 560 :linked-image-width original [[tsfile:2026-10-24T23.00.01 restic Notebook-Backup Ablauf.png]] Am Laptop läuft restic als root, gestartet von systemd-Timern. Es schickt die verschlüsselten Daten an einen rest-server, der als Container auf dem NAS läuft und alles in einer eigenen Freigabe ablegt. Zuhause geht das direkt über das Heimnetz, unterwegs über [[https://tailscale.com][Tailscale]]. Nachts spiegelt das NAS die Freigabe zusätzlich auf eine USB-Platte. Warum gerade so: - *restic:* Habe ich schon bei anderen Setups in der Vergangenheit verwendet. Mir gefällt dran, dass man damit einfach überprüfen kann, ob das Backup im Ernstfall auch wirklich funktioniert. Denn das ist ja oft das Problem: Man hat ein super Backup und wenn man es einspielen will, wird einem dann erst klar, ob die Backupdatei sauber angelegt wurde oder ob sie kaputt ist. Es dabei aber zu beachten, dass bei *restic der Speicherplatz am NAS bzw. externer Festplatte überwacht werden muss. Geht der Speicherplatz aus, ist das restic Backup nicht vollständig.* - *rest-server statt SFTP:* Er läuft als Container wie alles andere auf dem NAS. Ich brauche keinen SSH-Benutzer und muss nicht mit den Rechten von UGOS kämpfen. - *Append-only:* Der rest-server läuft im Modus =--append-only=. Der Laptop darf also nur neue Sicherungen anlegen, aber nichts löschen. Selbst ein Trojaner mit root-Rechten auf dem Laptop kann die Backups damit nicht zerstören. Der Preis dafür ist, dass das Aufräumen alter Sicherungen auf dem NAS laufen muss. - *Ein Repository für beides:* restic dedupliziert. Die wöchentliche System-Sicherung kostet nach dem ersten Lauf kaum noch Platz. - *Nur am Netzteil:* Der Timer versucht es tagsüber alle drei Stunden. Das Skript sichert nur, wenn der Laptop am Strom hängt und die letzte Sicherung alt genug ist. So klappt es auch, wenn der Laptop nur selten am Netzteil hängt. Es gibt zwei Sicherungen, unterschieden über restic-Tags: | Tag | Was | Wann | |----------+-------------------------------------------------------------+------------------| | =home= | =/home/benutzer=, =/etc=, =/root=, =/usr/local=, Paketlisten | einmal pro Tag | | =system= | das ganze =/= inklusive =/boot= und =/boot/efi=, ohne =/home= | einmal pro Woche | Die täglichen Konfigurationen in =/etc= sind damit doppelt drin, aber das kostet dank Deduplizierung nichts. ** Voraussetzungen - Ein NAS oder Server mit Docker und Docker Compose. Bei mir ist das ein Ugreen DXP4800 Plus mit UGOS. - Tailscale auf NAS und Laptop. Auf dem NAS läuft Tailscale bei mir als eigener Container. - restic ab Version 0.17 auf dem Laptop, weil erst ab dieser Version die Zugangsdaten für den rest-server per Umgebungsvariable gehen. Kubuntu 26.04 bringt 0.18.1 mit. - Platz auf dem NAS. Mein Home hat knapp 400 GiB. ** Teil 1: Das NAS *** Freigabe anlegen In UGOS unter „Systemsteuerung“ → „Freigegebener Ordner“ lege ich eine neue Freigabe =fw13-backup= an. Wichtig sind dabei zwei Einstellungen: - *Kein Zugriff für den normalen Benutzer.* Der Laptop ist per SMB als mein normaler Benutzer angemeldet. Hätte dieser Schreibrechte auf die Freigabe, könnte ein Trojaner die Backups einfach über SMB löschen, und append-only wäre ausgehebelt. Der Container läuft als root und braucht keine Freigaberechte. - *Papierkorb aus.* Gelöschte Daten braucht hier niemand, sie belegen nur Platz. Bei der Gelegenheit ist mir aufgefallen, dass mein normaler Benutzer auch auf die Freigabe =docker= Lesen und Schreiben hatte. Dort liegen Zugangsdaten und Konfigurationen meiner Container. Das habe ich gleich mit abgestellt. *** rest-server im Docker Die Compose-Datei liegt bei mir unter =/volume1/docker/rest-server/docker-compose.yaml=: #+BEGIN_SRC yaml services: rest-server: image: restic/rest-server:0.14.0 container_name: rest-server restart: unless-stopped ports: - "8000:8000" # LAN direkt (zuhause schnell), unterwegs über Tailscale volumes: - /volume1/fw13-backup:/data environment: OPTIONS: "--append-only --private-repos" #+END_SRC =--private-repos= sorgt dafür, dass jeder Benutzer nur in sein eigenes Unterverzeichnis schreiben darf. Der Benutzer =fw13= kommt also nur an =/fw13/= heran. Kommen später weitere Notebooks dazu, bekommt jedes einen eigenen Benutzer. Ursprünglich hatte ich den Port auf =127.0.0.1:8000= beschränkt, damit der rest-server nur über Tailscale erreichbar ist. Warum er jetzt doch im LAN offen ist, steht weiter unten bei der Fehlersuche. Starten und einen Benutzer anlegen: #+BEGIN_SRC sh cd /volume1/docker/rest-server docker compose up -d docker exec -it rest-server create_user fw13 #+END_SRC Das Passwort für den rest-server ist *nicht* das restic-Passwort. Du kannst es mit =openssl rand -hex 16= oder einem anderen Zufallsgenerator erzeugen und speichere es in einen Passwortmanager. Am besten eines ohne Sonderzeichen, dann gibt es später keinen Ärger mit Anführungszeichen. Die Warnung „No user exists“ im Log beim ersten Start ist normal, sie stammt aus der Zeit vor =create_user=. *** Für Tailscale freigeben Für unterwegs leite ich einen eigenen HTTPS-Port im Tailnet auf den rest-server weiter. Bei mir ist Port 8443 schon für etwas anderes belegt, deshalb 8444: #+BEGIN_SRC sh docker exec tailscale tailscale serve --bg --https=8444 http://127.0.0.1:8000 docker exec tailscale tailscale serve status #+END_SRC Das ist nur im Tailnet sichtbar, nicht im Internet (dafür wäre =tailscale funnel= zuständig). Die Einstellung bleibt auch nach einem Neustart erhalten. Ein schneller Test vom Laptop aus. Hier und im Folgenden steht =nas-name..ts.net= für die Tailscale-Adresse des NAS. Die Heimnetz-Adresse =192.168.1.10=, der Benutzer =benutzer= und die Ablageorte der Passwort- und Zugangsdateien (=repo-kennwort= und =zugang.env= am Laptop, =kennwort-fw13= am NAS) sind Beispiele, die du an dein System anpassen musst: #+BEGIN_SRC sh curl -u fw13 --max-time 15 https://nas-name..ts.net:8444/fw13/config #+END_SRC Die Antwort =Not Found= ist hier richtig, denn das Repository gibt es noch nicht. =401= heißt falsches Passwort, und „Connection refused“ heißt, dass die Weiterleitung fehlt. ** Teil 2: Der Laptop Alles läuft als root, damit restic auch =/etc= und die Systemdateien lesen kann. *** restic und Passwörter #+BEGIN_SRC sh sudo apt install restic curl restic version #+END_SRC Dann das Passwort für das Repository. *Ohne dieses Passwort ist das Backup wertlos*, es gehört also sofort in den Passwortmanager: #+BEGIN_SRC sh sudo install -d -m 700 /etc/restic openssl rand -base64 32 | sudo tee /etc/restic/repo-kennwort sudo chmod 600 /etc/restic/repo-kennwort #+END_SRC *** Die Zugangsdatei Alle Einstellungen stehen in =/etc/restic/zugang.env= (danach ~sudo chmod 600 /etc/restic/zugang.env~): #+BEGIN_SRC sh RESTIC_REPOSITORY=rest:https://nas-name..ts.net:8444/fw13/ RESTIC_REST_USERNAME=fw13 RESTIC_REST_PASSWORD= RESTIC_PASSWORD_FILE=/etc/restic/repo-kennwort RESTIC_CACHE_DIR=/var/cache/restic RESTIC_PROGRESS_FPS=0.0166 # Fortschritt etwa 1x pro Minute im Journal # zuhause direkt per LAN RESTIC_REPOSITORY_LAN=rest:http://192.168.1.10:8000/fw13/ NAS_MAC=xx:xx:xx:xx:xx:xx #+END_SRC Zu den letzten Zeilen: - =RESTIC_CACHE_DIR= ist unter systemd nötig. Dort gibt es kein =$HOME=, und ohne Cache muss restic bei jedem Lauf alle Metadaten neu vom NAS holen. - =RESTIC_PROGRESS_FPS= lässt restic auch ohne Terminal etwa einmal pro Minute eine Fortschrittszeile mit Prozent und Restzeit ins Journal schreiben. - =NAS_MAC= ist die MAC-Adresse des NAS, also der Wert nach =lladdr= in der Ausgabe von =ip neigh show 192.168.1.10=. Damit prüft das Skript, ob es wirklich im eigenen Heimnetz ist. Sonst würde es in einem fremden Netz mit zufällig gleicher IP das Passwort unverschlüsselt an ein fremdes Gerät schicken. Damit ich nicht jedes Mal alle Variablen setzen muss, gibt es einen kleinen Kurzbefehl =/usr/local/sbin/restic-fw13=: #+BEGIN_SRC sh #!/bin/sh set -a; . /etc/restic/zugang.env; set +a exec restic "$@" #+END_SRC #+BEGIN_SRC sh sudo chmod 755 /usr/local/sbin/restic-fw13 sudo restic-fw13 init #+END_SRC Ab jetzt gehen =sudo restic-fw13 snapshots=, =restore= und so weiter ohne Tipparbeit. *** Was nicht ins Backup soll =/etc/restic/excludes-home.txt=: #+BEGIN_EXAMPLE /home/benutzer/.cache /home/benutzer/.local/share/Trash /home/benutzer/.local/share/baloo /home/benutzer/.thumbnails /home/benutzer/.var/app/*/cache /home/benutzer/snap/*/*/.cache /home/benutzer/.local/share/Steam /home/benutzer/KI/model /root/.cache #+END_EXAMPLE Steam-Spiele und KI-Modelle kann man jederzeit neu herunterladen, die müssen nicht ins Backup. Meine Fotos und Videos bleiben drin, die liegen nur auf dem Laptop. =/etc/restic/excludes-system.txt=: #+BEGIN_EXAMPLE /home /tmp /var/tmp /var/cache /var/lib/snapd/cache /var/log/journal /swap.img /swapfile #+END_EXAMPLE =/proc=, =/sys=, =/dev= und =/run= fallen durch =--one-file-system= automatisch weg. Weil =/boot= und =/boot/efi= bei mir eigene Partitionen sind (das legt der Kubuntu-Installer bei verschlüsselter Installation so an), stehen sie im Skript ausdrücklich als Quellen. Ob es sie gibt, zeigt =findmnt -M /boot= bzw. =findmnt -M /boot/efi=. *** Das Backup-Skript Das Herzstück ist =/usr/local/sbin/restic-backup.sh= (danach ~sudo chmod 755~). Es bekommt als Parameter =home= oder =system=: #+BEGIN_SRC bash #!/bin/bash # Aufruf: restic-backup.sh home|system set -uo pipefail MODE="${1:-}" set -a; . /etc/restic/zugang.env; set +a STATE=/var/lib/restic-backup mkdir -p "$STATE" case "$MODE" in home) MAXAGE=$((20*3600)) ;; # höchstens 1x pro Tag system) MAXAGE=$((6*24*3600)) ;; # höchstens 1x pro Woche ,*) echo "Aufruf: $0 home|system"; exit 2 ;; esac # Letzte erfolgreiche Sicherung noch frisch? Dann nichts tun. if [ -f "$STATE/$MODE.last" ] && \ [ $(( $(date +%s) - $(stat -c %Y "$STATE/$MODE.last") )) -lt $MAXAGE ]; then echo "$MODE: letzte Sicherung ist noch aktuell"; exit 0 fi # Nur am Netzteil if ! systemd-ac-power; then echo "Akkubetrieb, übersprungen"; exit 0; fi # Zuhause? Dann direkt übers LAN, der Tailscale-Proxy ist beim Hochladen langsam if [ -n "${RESTIC_REPOSITORY_LAN:-}" ] && ping -c1 -W1 192.168.1.10 >/dev/null 2>&1 \ && ip neigh show 192.168.1.10 | grep -qi "${NAS_MAC:-none}"; then export RESTIC_REPOSITORY="$RESTIC_REPOSITORY_LAN"; echo "Weg: LAN" else echo "Weg: Tailscale" fi # NAS erreichbar? if ! restic cat config >/dev/null; then echo "NAS nicht erreichbar, nächster Versuch beim nächsten Timer"; exit 0 fi if [ "$MODE" = home ]; then # Paketlisten für die Neuinstallation P=/var/backups/pakete mkdir -p "$P" dpkg --get-selections > "$P/dpkg-selections.txt" apt-mark showmanual > "$P/apt-manuell.txt" command -v snap >/dev/null && snap list > "$P/snap.txt" command -v flatpak >/dev/null && flatpak list --app --columns=application > "$P/flatpak.txt" lsblk -f > "$P/lsblk.txt"; cp /etc/fstab "$P/fstab" restic backup -o rest.connections=20 --tag home --exclude-caches \ --exclude-file=/etc/restic/excludes-home.txt \ /home/benutzer /etc /root /usr/local /var/backups/pakete RC=$? else restic backup -o rest.connections=20 --tag system --one-file-system \ --exclude-file=/etc/restic/excludes-system.txt \ / /boot /boot/efi RC=$? fi # 0 = ok, 3 = ok, aber einzelne Dateien nicht lesbar if [ $RC -eq 0 ] || [ $RC -eq 3 ]; then touch "$STATE/$MODE.last" echo "$MODE: fertig (Code $RC)" exit 0 fi echo "$MODE: FEHLER (Code $RC)"; exit $RC #+END_SRC Ein paar Gedanken dazu: - Ob eine Sicherung fällig ist, merkt sich das Skript über einen Zeitstempel in =/var/lib/restic-backup=. Der systemd-Timer darf es daher ruhig oft aufrufen. - Fehlt das Netzteil oder das NAS, beendet sich das Skript mit Code 0. Das ist kein Fehler, sondern einfach „heute nicht“. Der nächste Timer versucht es wieder. - Bei der Home-Sicherung landen nebenbei die Paketlisten (=apt-mark showmanual=, Snap, Flatpak), =lsblk= und =fstab= im Backup. Damit lässt sich ein neu installiertes System schnell wieder mit denselben Programmen bestücken. - ~-o rest.connections=20~ hat bei mir messbar nichts gebracht (siehe Fehlersuche), schadet aber auch nicht. ** Teil 3: Zeitplan mit systemd Ein Dienst für beide Sicherungen, =/etc/systemd/system/restic-backup@.service=: #+BEGIN_SRC ini [Unit] Description=restic-Sicherung (%i) aufs NAS Wants=network-online.target After=network-online.target tailscaled.service ConditionACPower=true [Service] Type=oneshot ExecStart=/usr/local/sbin/restic-backup.sh %i Nice=10 IOSchedulingClass=idle #+END_SRC Dazu zwei Timer. =/etc/systemd/system/restic-backup-home.timer=: #+BEGIN_SRC ini [Unit] Description=Home-Sicherung, Versuch alle 3 Stunden [Timer] OnCalendar=*-*-* 09,12,15,18,21:00 Persistent=true RandomizedDelaySec=10min Unit=restic-backup@home.service [Install] WantedBy=timers.target #+END_SRC Und =/etc/systemd/system/restic-backup-system.timer= mit denselben Zeiten, eine halbe Stunde versetzt. Dass das System nur einmal pro Woche gesichert wird, regelt das Skript: #+BEGIN_SRC ini [Unit] Description=System-Sicherung, wöchentlich [Timer] OnCalendar=*-*-* 09,12,15,18,21:30 Persistent=true RandomizedDelaySec=10min Unit=restic-backup@system.service [Install] WantedBy=timers.target #+END_SRC ~Persistent=true~ holt Termine nach, die der Laptop im Ruhezustand verpasst hat. Der erste Lauf von Hand, und danach die Timer einschalten: #+BEGIN_SRC sh sudo systemctl daemon-reload sudo systemctl start --no-block restic-backup@home.service journalctl -fu restic-backup@home.service sudo systemctl enable --now restic-backup-home.timer restic-backup-system.timer systemctl list-timers 'restic*' #+END_SRC Ohne =--no-block= wartet =systemctl start= bis zum Ende der Sicherung, und das kann beim ersten Mal Stunden dauern. #+CAPTION: Der erste System-Lauf im Journal (gekürzt) #+ATTR_HTML: :alt Terminal mit journalctl-Ausgabe einer restic-Sicherung: Weg LAN, Fortschritt und Zusammenfassung :width 560 :linked-image-width original [[tsfile:2026-10-24T23.00.02 restic Notebook-Backup Journal Demo.png]] Läuft gerade eine Sicherung, schreibt restic auf Zuruf sofort eine Fortschrittszeile ins Journal: #+BEGIN_SRC sh sudo pkill -USR1 -x restic journalctl -u restic-backup@home.service -n 3 -o cat #+END_SRC Im Terminal selbst erscheint dabei nichts, die Zeile steht im Journal. Der Bildschirm darf sich dabei übrigens sperren und der Laptop zugeklappt sein. Die Sicherung läuft als Systemdienst unabhängig von der Sitzung weiter, solange der Laptop nicht in den Ruhezustand geht. ** Teil 4: Aufräumen und zweite Kopie auf dem NAS Wegen append-only kann der Laptop keine alten Sicherungen löschen. Das übernimmt das NAS mit einem eigenen restic. Dafür liegt das Repository-Passwort des Laptops auf dem NAS in =/volume1/docker/restic/kennwort-fw13= (~chmod 600~). Das Skript =/volume1/docker/restic/prune-notebooks.sh=: #+BEGIN_SRC sh #!/bin/sh R=/volume1/docker/restic/bin/restic LOG=/volume1/docker/restic/prune-notebooks.log for nb in fw13; do echo "== $(date '+%F %T') $nb" >> "$LOG" $R -r /volume1/fw13-backup/$nb \ --password-file /volume1/docker/restic/kennwort-$nb --retry-lock 30m \ forget --group-by host,tags \ --keep-daily 4 --keep-weekly 2 --keep-monthly 4 --prune >> "$LOG" 2>&1 # erste Woche im Monat: 5 % der Daten prüfen if [ "$(date +%d)" -le 07 ]; then $R -r /volume1/fw13-backup/$nb \ --password-file /volume1/docker/restic/kennwort-$nb \ check --read-data-subset=5% >> "$LOG" 2>&1 fi done #+END_SRC =--group-by host,tags= ist wichtig: Damit gilt die Regel 4/2/4 getrennt für =home= und =system=. Bei =system= gibt es nur eine Sicherung pro Woche, dort bleiben also die letzten vier Wochen und vier Monatsstände. Per Cron als root läuft es jeden Sonntag um 14:00, weit weg vom nächtlichen NAS-Backup: #+BEGIN_EXAMPLE 0 14 * * 0 /volume1/docker/restic/prune-notebooks.sh #+END_EXAMPLE Läuft gerade eine Sicherung vom Laptop, wartet das Aufräumen dank =--retry-lock= bis zu 30 Minuten. Mein NAS sichert sich nachts selbst mit restic auf eine USB-Platte. Das Notebook-Repository habe ich dort aber bewusst *nicht* als weitere Quelle eingetragen. Eine geänderte Quellenliste hätte ein Neueinlesen aller 3 TB ausgelöst, und das Repository ist ohnehin schon verschlüsselt. Stattdessen spiegelt eine zusätzliche Zeile im nächtlichen Skript die Freigabe 1:1 auf die Platte: #+BEGIN_SRC sh rsync -a --delete /volume1/fw13-backup/ "$MNT/fw13-backup/" && echo "fw13-backup gespiegelt" #+END_SRC =$MNT= ist dabei der Einhängepunkt der USB-Platte, den das Skript vorher ermittelt. ** Teil 5: Prüfen und Test-Wiederherstellung Ein Backup zählt erst, wenn eine Wiederherstellung einmal geklappt hat. Nach dem ersten Lauf und danach etwa alle drei Monate: | Was | Befehl am Laptop | |-------------------------------+---------------------------------------------------------------------------| | Sicherungen anzeigen | =sudo restic-fw13 snapshots= | | Letzte Läufe und Fehler | =journalctl -u 'restic-backup@*' --since -3d= | | Nächste Termine | =systemctl list-timers 'restic*'= | | Einzelne Datei zurückholen | =sudo restic-fw13 restore latest --tag home --include /home/benutzer/.zshrc --target /tmp/rtest= | | Durchsuchen wie ein Laufwerk | =sudo restic-fw13 mount /mnt/restic= (Ende mit Strg+C) | | Platzverbrauch | =sudo restic-fw13 stats --mode raw-data= | Meine Test-Datei war die =.zshrc=. Ein =diff= mit dem Original war leer, also alles gut. Auch append-only sollte man einmal testen: =sudo restic-fw13 forget = mit einer ID aus =snapshots= muss am Laptop mit einem Fehler abbrechen. *** Im Notfall Dann brauche ich nur zwei Einträge aus dem Passwortmanager: das restic-Passwort und das Passwort für den rest-server. Auf dem neuen System oder einem Live-USB installiere ich restic (und für unterwegs Tailscale) und lege =/etc/restic/zugang.env=, =/etc/restic/repo-kennwort= und =restic-fw13= wie oben an. Der einfachste Weg ist eine frische Installation plus Daten: #+BEGIN_SRC sh sudo restic-fw13 restore latest --tag home --target /var/tmp/r xargs -a /var/tmp/r/var/backups/pakete/apt-manuell.txt sudo apt install -y xargs -a /var/tmp/r/var/backups/pakete/flatpak.txt flatpak install -y sudo rsync -aH /var/tmp/r/home/benutzer/ /home/benutzer/ #+END_SRC Aus =/var/tmp/r/etc= übernehme ich dann gezielt, was ich selbst geändert hatte. =fstab=, =crypttab= und =/etc/default/grub= kopiere ich *nicht* blind, die gehören zur neuen Installation. Alternativ geht auch das komplette System 1:1 aus der wöchentlichen Sicherung: vom Live-USB Partitionen anlegen (Vorlage ist die gesicherte =lsblk.txt=), erst =system= und dann =home= nach =/mnt= wiederherstellen, die UUIDs in =fstab= und =crypttab= anpassen und im chroot Initramfs und GRUB neu bauen. Das lohnt sich aber nur, wenn viel am System selbst eingerichtet war. ** Fehlersuche: Warum war der erste Lauf so langsam? Eingerichtet war alles in einer guten Stunde. Dann kam der erste Home-Lauf mit knapp 400 GiB, und restic meldete: #+BEGIN_EXAMPLE [41:01] 1.37% 32650 files 5.680 GiB, total 276787 files 414.167 GiB, 0 errors ETA 84:11:57 #+END_EXAMPLE 84 Stunden, also rund 2 MB/s. Bis zur Lösung hat es mehrere Runden gebraucht. Weil die einzelnen Schritte für andere vielleicht nützlich sind, schreibe ich sie hier auf. *** Runde 1: Ist es der Laptop? Die naheliegende Vermutung war meine Einstellung ~IOSchedulingClass=idle~ im Dienst. Ein =ionice= und =renice= auf den laufenden Prozess hat aber kaum etwas gebracht. =top= zeigte den Laptop zu 99 % im Leerlauf, kein Warten auf die SSD. Und ein =dd= auf eine große Datei las mit mehreren GB/s. Der Laptop wartet also auf das NAS. *** Runde 2: Eine Netzwerkdose mit 100 Mbit Auf dem NAS lieferte der rest-server lokal 233 MB/s: #+BEGIN_SRC sh curl -s -u fw13 -o /dev/null -w '%{speed_download}\n' http://127.0.0.1:8000/fw13/data/ #+END_SRC Vom Laptop aus, egal ob über Tailscale oder direkt im LAN, kamen nur etwa 11 MB/s an. Das ist ziemlich genau die Grenze einer 100-Mbit-Verbindung. Dabei zeigten beide Netzwerkkarten 1000 Mbit (=cat /sys/class/net//speed=). Der Engpass lag also dazwischen, in meinem Fall in der Hausverkabelung. Mit einer anderen Netzwerkdose kamen über Tailscale plötzlich 75 MB/s an. Problem gelöst, dachte ich. *** Runde 3: Hochladen ist nicht Herunterladen Nach einigen Minuten lag restic aber wieder bei nur 3 MB/s. Mein Test hatte nur die Richtung NAS → Laptop gemessen, das Backup schreibt aber in die andere Richtung. Also alles einzeln durchgemessen: | Test | Ergebnis | |-------------------------------------------+-------------------------------------| | Schreiben auf die NAS-Platten (=dd=, 2 GB) | 128 MB/s | | Netz Laptop → NAS (=iperf3=) | ca. 500 Mbit/s (≈ 60 MB/s) | | =fsync= für ein 16-MB-Paket am NAS | ca. 0,3 s | | restic mit 20 statt 5 Verbindungen | kein Unterschied | Alles einzeln schnell genug, und trotzdem kamen über Tailscale nur 3 MB/s an. Auffällig war, dass der Tailscale-Container auf dem NAS dabei 25 bis 30 % CPU brauchte. Zwei Dinge sind mir in den Ausgaben von =iperf3= aufgefallen. Erstens war die Absenderadresse meines Laptops eine Tailscale-Adresse (100.x.x.x), obwohl ich die LAN-Adresse des NAS angesprochen hatte. Der Grund: Mein Home Assistant bietet im Tailnet eine Subnetz-Route ins Heimnetz an, und der Laptop hat sie übernommen. Selbst der „direkte“ Verkehr im LAN lief also verschlüsselt durch Tailscale. Abhilfe am Laptop: #+BEGIN_SRC sh sudo tailscale set --accept-routes=false ip route get 192.168.1.10 # sollte jetzt die LAN-Schnittstelle zeigen #+END_SRC Zweitens fehlte noch genau ein Test: das Hochladen durch =tailscale serve=. Also den rest-server-Port testweise im LAN geöffnet (~"8000:8000"~ in der Compose-Datei), die Repository-Adresse am Laptop auf : rest:http://192.168.1.10:8000/fw13/ umgestellt und neu gestartet: #+BEGIN_EXAMPLE [4:00] 7.09% 37763 files 28.291 GiB, total 276774 files 398.835 GiB, 0 errors ETA 1:48:29 [5:01] 8.05% 39008 files 32.113 GiB, total 276774 files 398.835 GiB, 0 errors ETA 1:45:58 #+END_EXAMPLE Rund 3,7 GiB pro Minute, also etwa 60 MB/s, statt vorher 0,19. Der Upload durch den Proxy von =tailscale serve= auf dem NAS war die Bremse. Beim Herunterladen ist er schnell, beim Hochladen der vielen 16-MB-Pakete von restic aber nicht. *** Die Lösung: zwei Wege Daher der zweite Weg im Skript: Zuhause geht restic direkt per HTTP an den rest-server, unterwegs weiter über Tailscale. Unterwegs sind die täglichen Änderungen klein, da reichen die 3 MB/s. Damit das Skript nicht in einem fremden Netz mit zufällig gleicher Adresse das Passwort verschickt, prüft es vorher die MAC-Adresse des NAS. Ein Kompromiss bleibt: Im Heimnetz geht das Passwort für den rest-server unverschlüsselt über HTTP. Die Backup-Daten selbst verschlüsselt restic immer, und löschen kann man wegen append-only ohnehin nichts. Für ein Heimnetz halte ich das für vertretbar. Mit ~accept-routes=false~ erreiche ich Geräte im Heimnetz unterwegs nur noch über ihre Tailscale-Namen, nicht mehr über 192.168.1.x. Für mich passt das. Der erste Home-Lauf mit knapp 399 GiB dauerte danach etwa zwei Stunden, der erste System-Lauf mit 32 GiB nur sechseinhalb Minuten. Gespeichert wurden davon dank Kompression 16,8 GiB. ** Fazit Das Grundgerüst aus restic, rest-server und zwei systemd-Timern war schnell aufgebaut und läuft ab jetzt ohne mein Zutun. Den meisten Aufwand hat die Ursachenforschung für die langsame Übertragungsgeschwindigkeit gemacht. Dabei habe ich gelernt, Up- und Download getrennt zu messen und jedes Stück der Kette einzeln zu prüfen, statt beim ersten guten Messwert aufzuhören. Und das wichtigste: Ich habe jetzt endlich ein Backup vom Laptop, das ich auch schon einmal zurückgespielt habe.