Notfallprobe: VPS ins Heimnetz sichern und Dienste wiederherstellen
Teil 19 der Reihe Server im Netz: Der VPS sichert mit restic über einen frp-Tunnel in einen append-only rest-server im Heimnetz. Dann werden Webstack und Forgejo gelöscht und zurückgeholt, mit gemessener Zeit bis zur ersten Antwort und einer Falle aus dem echten Betrieb.
Geprüft am 05.11.2026 · für restic 0.18.1, rest-server 0.14.0, frp 0.72.0
Mit KI erstellt – redaktionelle Prüfung ausstehend
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Ein Backup ist erst etwas wert, wenn die Wiederherstellung einmal geklappt hat. Diese Anleitung sichert den VPS der Reihe mit restic in das eigene Heimnetz, ohne dort einen Port zu öffnen, und spielt danach den Ernstfall durch: Der Webserver mit allen Zertifikaten wird gelöscht, dann die Daten von Forgejo samt Datenbank. Beide Male läuft die Stoppuhr, bis der Dienst wieder antwortet. Dabei zeigte sich eine Falle, die ein Testlauf auf einem leeren System kaum gefunden hätte.
Dies ist Teil 19 der Reihe „Server im Netz“. Als Beispiel dient ein netcup-VPS mit Ubuntu 24.04 und Docker, auf dem die Dienste der Reihe im Host-Netz laufen. Teil 16 hat die umgekehrte Richtung gebaut: dort sichert das HomeLab auf den VPS.
Voraussetzungen
- Ein VPS mit Docker und frps aus Teil 8, dort mit
proxyBindAddr = "127.0.0.1"undallowPorts = [ { single = 2222 } ]. - Ein Rechner mit Docker im Heimnetz, der dauerhaft läuft, zum Beispiel ein NAS oder ein kleiner Server.
- Der Token von frps als Datei
frp-tokenauf dem Rechner im Heimnetz. - Grundwissen zu Docker Compose, siehe Docker Compose unter Linux.
Schritt 1: Das Ziel im Heimnetz
Im Heimnetz laufen zwei Container: der rest-server von restic als Speicher und frpc, der eine ausgehende Verbindung zum VPS aufbaut. Von außen ist im Heimnetz nichts erreichbar. Der rest-server läuft mit --append-only: Neue Daten darf der VPS schreiben, löschen darf er nichts. Wer den VPS übernimmt, kann also die Sicherungen nicht vernichten.
# compose.yaml im Heimnetz
services:
rest-server:
image: restic/rest-server:0.14.0
restart: unless-stopped
environment:
OPTIONS: "--append-only --private-repos"
volumes:
- ./daten:/data
frpc:
image: fatedier/frpc:v0.72.0
restart: unless-stopped
command: ["-c", "/etc/frp/frpc.toml"]
volumes:
- ./frpc.toml:/etc/frp/frpc.toml:ro
- ./frp-token:/etc/frp/frp-token:ro
depends_on: [rest-server]
# frpc.toml
serverAddr = "sedv-lab.de"
serverPort = 7000
loginFailExit = false
auth.method = "token"
auth.tokenSource.type = "file"
auth.tokenSource.file.path = "/etc/frp/frp-token"
[[proxies]]
name = "heim-backup"
type = "tcp"
localIP = "rest-server"
localPort = 8000
remotePort = 2222
Den Zugang legen Sie mit einem zufälligen Passwort für den Benutzer vps an. Wegen --private-repos darf dieser Benutzer nur in das Repository /vps/ schreiben.
umask 077; openssl rand -hex 16 > vps-pw
mkdir -p daten
docker run --rm --entrypoint htpasswd httpd:2.4-alpine -B -C 10 -nb vps "$(cat vps-pw)" > daten/.htpasswd
docker compose up -d
Verifizieren: docker compose logs frpc meldet start proxy success, der rest-server meldet Append only mode enabled und Private repositories enabled.
Schritt 2: Den Tunnel vom VPS aus prüfen
Auf dem VPS lauscht frps jetzt auf 127.0.0.1:2222, von außen ist der Port nicht erreichbar. Legen Sie das Passwort aus Schritt 1 auf dem VPS als /root/sin/teil19/heim-http-pw ab, mit Rechten 600.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:2222/vps/config
curl -s -o /dev/null -w '%{http_code}\n' -u vps:"$(cat /root/sin/teil19/heim-http-pw)" http://127.0.0.1:2222/vps/config
Verifizieren: Ohne Anmeldung kommt 401, mit Anmeldung 404, weil es noch kein Repository gibt.
Schritt 3: restic auf dem VPS
restic läuft als Container, ein kleines Skript setzt Ziel, Passwort und die Ordner, die gesichert werden. Gesichert werden die Ordner aller Dienste und die beiden Volumes von Caddy mit den Zertifikaten.
#!/bin/sh
# /root/sin/teil19/restic.sh: restic ins Heimnetz, Ziel ist der frp-Tunnel auf 127.0.0.1:2222
exec docker run --rm --network host \
-e RESTIC_REPOSITORY="rest:http://vps:$(cat /root/sin/teil19/heim-http-pw)@127.0.0.1:2222/vps/" \
-e RESTIC_PASSWORD_FILE=/pw -v /root/sin/teil19/restic-pw:/pw:ro \
-v /root/sin:/quelle/sin:ro \
-v /var/lib/docker/volumes/web_caddy_data/_data:/quelle/caddy_data:ro \
-v /var/lib/docker/volumes/web_caddy_config/_data:/quelle/caddy_config:ro \
-v /root/sin/teil19/restore:/restore \
-v /root/sin/teil19/cache:/root/.cache/restic \
-h sedv-vps restic/restic:0.18.1 "$@"
cd /root/sin/teil19
umask 077; openssl rand -hex 24 > restic-pw
chmod 700 restic.sh; mkdir -p restore
./restic.sh init
./restic.sh backup /quelle --exclude /quelle/sin/teil16/data --exclude /quelle/sin/teil19
Die erste Sicherung las 1354 Dateien mit 59,5 MiB und war nach gut 3 Sekunden fertig. Im Heimnetz kamen 20,8 MiB an, restic komprimiert und entfernt doppelte Blöcke. Den Ordner mit dem eigenen Repository aus Teil 16 haben wir ausgenommen, er ist selbst schon eine Sicherung.
Verifizieren: ./restic.sh snapshots listet den neuen Snapshot mit Host sedv-vps.
Schritt 4: Append-only beweisen
Ein Angreifer mit Root auf dem VPS hat Passwort und Zugang. Er versucht als Erstes, die Sicherungen zu löschen:
./restic.sh forget latest
Remove(<snapshot/e1e47af536>) failed: unexpected HTTP response (403): 403 Forbidden
unable to remove snapshot/e1e47af5363c1d444f7cc6f21eea152010d77899d9f7dc4c26f64a803f16d870 from the repository

Verifizieren: ./restic.sh snapshots zeigt den Snapshot weiterhin. Alte Snapshots räumen Sie im Heimnetz auf, mit einem Zugang ohne append-only, so wie in Teil 16 auf dem VPS. Nehmen Sie dafür forget --keep-within: Ein Angreifer könnte vom VPS aus viele leere Snapshots mit frischem Zeitstempel anlegen, und Regeln wie --keep-last würden dann die echten Snapshots wegräumen. Löschen kann er nichts, die Platte im Heimnetz füllen aber schon, behalten Sie den Platz im Blick.
Schritt 5: Der Notfallzettel
Ist der VPS weg, sind auch restic-pw und heim-http-pw weg. Ohne das Passwort des Repositorys sind die Daten im Heimnetz nicht lesbar. Legen Sie deshalb außerhalb des VPS ab, zum Beispiel im Passwortmanager:
- das Passwort des Repositorys (
restic-pw), - das Passwort des Benutzers
vpsund den Token von frps, - das Skript
restic.shund die Liste der Volumes, die es sichert.
Verifizieren: Prüfen Sie, ob Sie jeden Punkt ohne Zugriff auf den VPS finden.
Schritt 6: Probe 1, der Webstack ist weg
Caddy ist die Eingangstür aller Dienste der Reihe. Die folgenden Befehle stoppen den Stack, löscht den Ordner mit Caddyfile und Website sowie beide Volumes mit den Zertifikaten, holt alles aus dem Heimnetz zurück und misst bis zur ersten Antwort mit HTTP 200.
cd /root/sin/web && docker compose down
rm -rf /root/sin/web; docker volume rm web_caddy_data web_caddy_config
cd /root/sin/teil19; rm -rf restore/*
./restic.sh restore latest --path /quelle --target /restore --include /quelle/sin/web \
--include /quelle/caddy_data --include /quelle/caddy_config
mkdir -p /root/sin/web && cp -a restore/quelle/sin/web/. /root/sin/web/
docker volume create web_caddy_data; docker volume create web_caddy_config
cp -a restore/quelle/caddy_data/. /var/lib/docker/volumes/web_caddy_data/_data/
cp -a restore/quelle/caddy_config/. /var/lib/docker/volumes/web_caddy_config/_data/
cd /root/sin/web && docker compose up -d
Der erste Anlauf scheiterte. Während des Restores, noch vor dem Zurückkopieren, lief der Cron-Job von GoAccess aus Teil 7 und legte /root/sin/web/log neu an. Weil der Zielordner damit schon da war, kopierte cp -a restore/quelle/sin/web /root/sin/web alles nach /root/sin/web/web, und docker compose up meldete no configuration file provided: not found. Mit mkdir -p und dem Punkt am Ende der Quelle wie oben landen die Dateien immer richtig.
Der zweite Anlauf brauchte 2,7 Sekunden vom Start des Restores bis HTTP 200. restic war nach 1,7 Sekunden fertig, der Rest war das Kopieren und der Start von Caddy. Weil die Zertifikate aus dem Volume zurückkamen, holte Caddy kein neues Zertifikat bei Let's Encrypt. Bei vielen Subdomains und mehreren Neuaufbauten am Tag wäre das sonst eine Frage der Rate-Limits.

Verifizieren: curl -sI https://sedv-lab.de/ antwortet mit 200, und docker logs caddy enthält keine Zeile certificate obtained.
Schritt 7: Probe 2, Forgejo mit Datenbank
Forgejo aus Teil 15 speichert in SQLite. Eine Kopie der Datenbankdatei im laufenden Betrieb kann unvollständig sein. Deshalb stoppt die Sicherung Forgejo kurz. Vorher legten wir ein Ticket mit dem Titel „Notfallprobe Marker 1638“ an.
docker stop forgejo
./restic.sh backup /quelle/sin/teil15 --tag forgejo
docker start forgejo
Vom Stopp bis Forgejo wieder antwortete, vergingen 3,9 Sekunden. Dann der Ausfall: docker compose down und rm -rf /root/sin/teil15/forgejo. Der Restore läuft wie in Schritt 6, mit --tag forgejo statt --path und dem Ordner von Forgejo als --include.
Nach 4,4 Sekunden meldete /api/healthz wieder 200. Das Ticket „Notfallprobe Marker 1638“ war da, und git ls-remote lieferte denselben Commit wie vorher. Der Runner startete schneller als Forgejo, brach einmal mit unavailable: 502 Bad Gateway ab und meldete sich nach dem automatischen Neustart mit declared successfully zurück.
Für den Alltag gilt dasselbe: Die regelmäßige Sicherung sollte Forgejo kurz stoppen oder vorher einen Dump ziehen, etwa mit forgejo dump. Das Skript rufen Sie dann per Cron oder systemd-Timer auf, wie den Sicherheitsbericht in Teil 18.
Verifizieren: docker ps zeigt forgejo und forgejo-runner mit Status Up.
Schritt 8: Alles zurück in einen leeren Ordner
Für den Fall, dass nichts mehr da ist, sicherten wir noch einmal alles und holten den ganzen Snapshot in einen leeren Ordner:
./restic.sh backup /quelle --exclude /quelle/sin/teil16/data --exclude /quelle/sin/teil19
rm -rf restore/*
./restic.sh restore latest --path /quelle --target /restore
./restic.sh check
2149 Dateien und Ordner mit 59,5 MiB waren nach 5,9 Sekunden da, restic check meldete no errors were found. Über alle Snapshots lag die Kompressionsrate bei 3,41 zu 1.

Verifizieren: du -sh /root/sin/teil19/restore zeigt die Größe, danach den Ordner wieder leeren.
Typische Fehler
no configuration file provided: not foundnach dem Restore: Der Zielordner existierte schon,cp -ahat einen Unterordner angelegt. Prüfen Sie mitls, kopieren Sie mitquelle/.in den Zielordner, und stoppen Sie Cron-Jobs, die in den Ordner schreiben, während der Probe.unexpected HTTP response (403)beiforgetoderprune: Das ist append-only und gewollt. Aufräumen im Heimnetz.401beim Zugriff auf den Tunnel: Benutzer oder Passwort passen nicht zur.htpasswd, oder der Pfad ist nicht/vps/. Mit--private-reposmuss der Pfad dem Benutzernamen entsprechen.fail to invoke Declare ... 502 Bad Gatewaybeim Runner: Forgejo war noch nicht bereit. Die Neustart-Regelrestart: unless-stoppeddes Containers fängt das ab, ein Healthcheck mitdepends_onmacht es sauber.
Häufige Fragen
Warum sichert der VPS ins Heimnetz und nicht in die Cloud?
Der VPS liegt schon in einem Rechenzentrum. Eine Kopie zu Hause liegt außerhalb des Anbieters, an einem anderen Ort und unter Ihrer Kontrolle. Mit dem Tunnel braucht der Router dafür keine Portfreigabe.
Wie schnell geht das bei mehr Daten?
Hier waren es 59,5 MiB. Beim Restore fließen die Daten vom Heimnetz zum VPS, also über den Upload Ihres Anschlusses. Der begrenzt bei großen Datenmengen die Zeit.
Reicht ein Snapshot des Anbieters nicht?
Ein Snapshot liegt beim selben Anbieter und oft im selben Konto. Ist das Konto gesperrt oder übernommen, ist er mit weg. Er ist eine gute Ergänzung für schnelle Rückwege, aber keine Kopie außer Haus.
Fazit
Mit rest-server im Heimnetz und einem frp-Tunnel bekommt der VPS ein Ziel, das er beschreiben, aber nicht löschen kann. Die Probe hat sich gelohnt: Der Restore selbst dauerte Sekunden, gescheitert wäre er an einem Cron-Job, der den Zielordner wieder anlegt. Solche Fehler findet nur, wer den Ernstfall auf dem echten System durchspielt. Ein kompletter Neuaufbau auf leerem Server folgt kurz vor Teil 20.


