<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Restic – Serverküche</title><link>https://serverkueche.de/tags/restic/</link><description>Restic – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>de-DE</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Sat, 18 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/tags/restic/index.xml" rel="self" type="application/rss+xml"/><item><title>Restic-Backups einrichten: verschlüsselt und off-site</title><link>https://serverkueche.de/tutorials/backups-mit-restic/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/tutorials/backups-mit-restic/</guid><description>Off-Site-Backups mit Restic: verschlüsselt einrichten, Snapshots wiederherstellen, mit Retention aufräumen und per systemd-Timer automatisieren.</description><content:encoded><![CDATA[<p>Ein Snapshot beim Anbieter ist <strong>kein Backup</strong>: Er liegt auf derselben Infrastruktur
und ist mit weg, wenn dein Account gesperrt wird, das Rechenzentrum brennt oder du
versehentlich das falsche Projekt löschst. <strong>Restic</strong> macht daraus ein echtes
Backup – verschlüsselt, dedupliziert und an einem Ort <strong>außerhalb</strong> deines Servers.</p>
<h2 id="was-bauen-wir">Was bauen wir?</h2>
<p>Am Ende sichert <strong>Restic</strong> deine wichtigen Daten – App-Volumes, Konfigurationen,
Datenbank-Dumps – <strong>Ende-zu-Ende-verschlüsselt</strong> und <strong>dedupliziert</strong> in ein
Repository, idealerweise <strong>off-site</strong> (zweiter Server oder Storage). Du kannst
jederzeit einzelne Dateien oder ganze Snapshots wiederherstellen, alte Backups nach
einer Aufbewahrungsregel automatisch ausdünnen und die Integrität prüfen. Zum Schluss
läuft alles automatisch per systemd-Timer und meldet sich, wenn ein Lauf ausbleibt.</p>
<p>Getestet mit <strong>Restic 0.18</strong> (die Version aus den Debian-13-Paketquellen); upstream
gibt es bereits 0.19.x – wie du aktualisierst, steht in Schritt 1. Restic verschlüsselt
<strong>immer</strong> – es gibt kein unverschlüsseltes Repository.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-sky-300 bg-sky-50 dark:border-sky-800 dark:bg-sky-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">ℹ️</span>Snapshot ≠ Backup
  </p>
  <div class="prose-kitchen text-sm">Der <a href="/tutorials/netcup-snapshots-scp/">Snapshot im Anbieter-Panel</a> ist ein bequemes
„Zurückspulen&quot; – aber er liegt auf
<strong>derselben</strong> Infrastruktur und unter <strong>demselben</strong> Account. Fällt der Anbieter, das
Rechenzentrum oder dein Zugang aus, ist der Snapshot mit weg. Ein echtes Backup liegt
<strong>außerhalb</strong> (anderer Anbieter/Standort), ist <strong>verschlüsselt</strong> und du kannst es
<strong>unabhängig</strong> wiederherstellen. Genau das ist Restics Aufgabe – der Snapshot bleibt
das schnelle Netz für kleine Missgeschicke.</div>
</div>
<h2 id="voraussetzungen">Voraussetzungen</h2>
<ul>
<li>Ein Server (Debian 13) mit den Daten, die du sichern willst – z. B. die Volumes und
Compose-Dateien deiner <a href="/tutorials/docker-installieren/">Docker-Apps</a>.</li>
<li>Ein <strong>Off-Site-Ziel</strong> für die Backups. Restic spricht u. a.:
<ul>
<li><strong>SFTP</strong> – ein zweiter Server oder eine Storage-Box (am einfachsten zu starten),</li>
<li><strong>S3-kompatibel</strong> – z. B. ein Object-Storage-Bucket,</li>
<li><strong>rclone</strong> – als Brücke zu Backblaze B2, Google Drive und Dutzenden weiteren.</li>
</ul>
</li>
<li>Grundvertrautheit mit der Shell.</li>
</ul>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Empfehlung</span>
    <span title="Mit * markierte Links sind Affiliate-Links.">Anzeige</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 1000 G12</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">4 vCore · 8 GB RAM · 256 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">ab 10,36 €/Monat</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Ein kleiner zweiter VPS an einem anderen Standort ist ein günstiges, dediziertes Backup-Ziel per SFTP.</p>
    </div>
    <a href="https://www.netcup.com/de/server/vps/vps-1000-g12-12m?ref=44083" rel="sponsored noopener" target="_blank"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Zu netcup →
</a>

  </div><div class="px-4 pb-4"><p class="not-prose my-3 flex flex-wrap items-center gap-x-2 gap-y-1 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200">
  <span>💶 <strong>5 € Gutschein</strong> für netcup-Neukunden:</span>
  <code class="rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</code>
  <span class="text-xs text-slate-500 dark:text-slate-400">(nur Neukunden, keine Domains)</span>
</p></div>
</div>

<h2 id="schritt-für-schritt">Schritt für Schritt</h2>
<h3 id="schritt-1-restic-installieren">Schritt 1: Restic installieren</h3>
<p>Restic liegt in den Paketquellen und wird so mit dem System aktualisiert:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt update
</span></span><span class="line"><span class="cl">sudo apt install -y restic</span></span></code></pre></div>
</div>
<p>Prüfe die Version:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">restic version</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">restic 0.18.0 compiled with go1.24.4 on linux/amd64</span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Neuere Version bei Bedarf
  </p>
  <div class="prose-kitchen text-sm">Upstream ist Restic teils voraus (aktuell 0.19.x). Brauchst du eine neuere Funktion,
lade das offizielle Binary von der
<a href="https://github.com/restic/restic/releases">Restic-Release-Seite</a> herunter und lege
es nach <code>/usr/local/bin</code> (das hat Vorrang vor <code>/usr/bin</code>). Der eingebaute Befehl
<code>restic self-update</code> funktioniert mit der Debian-Paketversion <strong>nicht</strong> – das Paket
ist ohne diese Funktion gebaut (<code>unknown command &quot;self-update&quot;</code>). Für die meisten
reicht die Paketversion völlig.</div>
</div>
<h3 id="schritt-2-das-verschlüsselte-repository-anlegen">Schritt 2: Das verschlüsselte Repository anlegen</h3>
<p>Ein Restic-<strong>Repository</strong> ist der verschlüsselte Speicherort deiner Backups. Es wird
mit einem Passwort geschützt – und dieses Passwort gehört <strong>nicht</strong> in ein Skript und
<strong>nicht</strong> neben das Repository. Lege es in eine Datei, die nur root lesen darf:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo install -d -m <span class="m">700</span> /etc/restic
</span></span><span class="line"><span class="cl">openssl rand -base64 <span class="m">32</span> <span class="p">|</span> sudo tee /etc/restic/password &gt; /dev/null
</span></span><span class="line"><span class="cl">sudo chmod <span class="m">600</span> /etc/restic/password</span></span></code></pre></div>
</div>
<p>Der <code>openssl</code>-Befehl erzeugt ein langes Zufallspasswort und legt es unter
<code>/etc/restic/password</code> ab (der <code>install</code>-Befehl davor erstellt das Verzeichnis,
<code>chmod</code> danach schützt die Datei). Sag Restic über Umgebungsvariablen, wo das Repository liegt
und wo das Passwort steht – hier zunächst ein Off-Site-Ziel per SFTP:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">RESTIC_REPOSITORY</span><span class="o">=</span><span class="s2">&#34;sftp:BACKUP_BENUTZER@DEIN_BACKUP_HOST:/backups/DEIN_SERVER&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">RESTIC_PASSWORD_FILE</span><span class="o">=</span><span class="s2">&#34;/etc/restic/password&#34;</span></span></span></code></pre></div>
</div>
<p>Ersetze <code>BACKUP_BENUTZER</code>, <code>DEIN_BACKUP_HOST</code> und den Pfad durch dein echtes Ziel.
Willst du erst lokal üben, nimm stattdessen einen Ordner:
<code>export RESTIC_REPOSITORY=&quot;/srv/restic-repo&quot;</code>. Die Befehle danach sind <strong>identisch</strong> –
nur die Repository-Adresse unterscheidet sich.</p>
<p>Andere Off-Site-Ziele nutzt du über dieselbe Variable, nur mit anderem Präfix:</p>
<ul>
<li><strong>S3-kompatibel:</strong> <code>s3:https://ENDPUNKT/BUCKET</code> – dazu die Zugangsdaten als
<code>AWS_ACCESS_KEY_ID</code> und <code>AWS_SECRET_ACCESS_KEY</code> in der Umgebung (bzw. in der
Environment-Datei aus Schritt 7).</li>
<li><strong>rclone:</strong> <code>rclone:REMOTE:pfad</code> – die Brücke zu Backblaze B2, Google Drive und
vielen weiteren, sobald du das rclone-Remote einmal eingerichtet hast.</li>
</ul>
<p>Egal welches Backend: Verschlüsselung, Deduplizierung und alle folgenden Befehle
bleiben gleich.</p>
<p>Jetzt das Repository initialisieren (die Beispiel-Ausgabe zeigt das lokale
Übungs-Repository – beim SFTP-Ziel steht dort entsprechend deine <code>sftp:</code>-Adresse):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">restic init</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">created restic repository d29613209a at /srv/restic-repo
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Please note that knowledge of your password is required to access
</span></span><span class="line"><span class="cl">the repository. Losing your password means that your data is
</span></span><span class="line"><span class="cl">irrecoverably lost.</span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-paprika-400 bg-paprika-50 dark:border-paprika-700 dark:bg-paprika-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🔥</span>Passwort verloren = Daten verloren
  </p>
  <div class="prose-kitchen text-sm">Es gibt <strong>keine</strong> Hintertür. Ohne das Repository-Passwort sind deine Backups
unwiederbringlich verschlüsselt. Sichere den Inhalt von <code>/etc/restic/password</code>
zusätzlich in einem <strong>Passwortmanager</strong> – getrennt vom Server, sonst nützt es dir bei
einem Serververlust nichts.</div>
</div>
<h3 id="schritt-3-das-erste-backup">Schritt 3: Das erste Backup</h3>
<p>Sichere das Verzeichnis mit deinen Daten. Der <code>--tag</code> hilft später beim Filtern:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">restic backup /root/backup-demo --tag demo</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">no parent snapshot found, will read all files
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Files:           3 new,     0 changed,     0 unmodified
</span></span><span class="line"><span class="cl">Dirs:            2 new,     0 changed,     0 unmodified
</span></span><span class="line"><span class="cl">Added to the repository: 2.002 MiB (2.001 MiB stored)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">processed 3 files, 2.000 MiB in 0:00
</span></span><span class="line"><span class="cl">snapshot f16c6ffd saved</span></span></code></pre></div>
</div>
<p>Beim <strong>zweiten</strong> Lauf zeigt sich Restics Stärke: Es sichert nur die Änderungen
(Deduplizierung). Ändert sich eine Datei und du sicherst erneut:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">using parent snapshot f16c6ffd
</span></span><span class="line"><span class="cl">Files:           0 new,     1 changed,     2 unmodified
</span></span><span class="line"><span class="cl">Added to the repository: 1.820 KiB (1.121 KiB stored)
</span></span><span class="line"><span class="cl">processed 3 files, 2.000 MiB in 0:00
</span></span><span class="line"><span class="cl">snapshot 0bb5e679 saved</span></span></code></pre></div>
</div>
<p>Statt wieder 2 MiB landen nur <strong>1,1 KiB</strong> im Repository – nur der geänderte Teil.</p>
<p>Was gehört ins Backup? Für Docker-Apps sind das drei Dinge – Rezept, Nutzdaten, DB:</p>
<ul>
<li><strong>Compose-Dateien</strong> (dein „Rezept&quot;) und <code>.env</code>: klein, aber unverzichtbar, um den
Stack wieder aufzubauen.</li>
<li><strong>Bind-Mounts</strong> (<code>./data:/…</code>) liegen direkt im Projektordner – die sicherst du
einfach mit. <strong>Named Volumes</strong> findest du unter
<code>/var/lib/docker/volumes/&lt;name&gt;/_data</code>; nimm den Pfad mit auf.</li>
<li><strong>Datenbanken</strong> niemals als laufende Datei sichern – ein Backup mitten im
Schreibvorgang ist womöglich unbrauchbar. Erzeuge stattdessen einen <strong>konsistenten
Dump</strong> (<code>pg_dump</code>/<code>mariadb-dump</code>) und sichere den (siehe Schritt 7).</li>
</ul>
<p>Unnötiges schließt du aus:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">restic backup /opt --tag apps --exclude<span class="o">=</span><span class="s1">&#39;*.log&#39;</span> --exclude<span class="o">=</span><span class="s1">&#39;**/cache/**&#39;</span></span></span></code></pre></div>
</div>
<h3 id="schritt-4-wiederherstellen">Schritt 4: Wiederherstellen</h3>
<p>Ein Backup ist nur so gut wie sein Restore. Liste zunächst die Snapshots:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">restic snapshots</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ID        Time                 Host                  Tags        Paths              Size
</span></span><span class="line"><span class="cl">---------------------------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">f16c6ffd  2026-07-16 20:52:35  v2200000000000000000  demo        /root/backup-demo  2.000 MiB
</span></span><span class="line"><span class="cl">0bb5e679  2026-07-16 20:52:36  v2200000000000000000  demo        /root/backup-demo  2.000 MiB
</span></span><span class="line"><span class="cl">---------------------------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">2 snapshots</span></span></code></pre></div>
</div>
<p>Stelle einen kompletten Snapshot in ein Zielverzeichnis wieder her (nutze <code>latest</code>
für den neuesten oder eine konkrete ID):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">restic restore latest --target /root/restore-test</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">restoring snapshot 0bb5e679 of [/root/backup-demo] ... to /root/restore-test
</span></span><span class="line"><span class="cl">Summary: Restored 5 files/dirs (2.000 MiB) in 0:00</span></span></code></pre></div>
</div>
<p>Brauchst du nur <strong>eine</strong> Datei, gibt es zwei Wege – gezielt diesen Pfad
wiederherstellen:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">restic restore latest --target /tmp/wiederher --include /root/backup-demo/config.yaml</span></span></code></pre></div>
</div>
<p>… oder die Datei direkt auf die Standardausgabe streamen, ganz ohne Umweg:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">restic dump latest /root/backup-demo/config.yaml &gt; /tmp/config.yaml</span></span></code></pre></div>
</div>
<p>Zum <strong>Stöbern</strong> hängst du das Repository sogar als Dateisystem ein (<code>restic mount /mnt/backup</code>, benötigt FUSE) und kopierst im Dateimanager heraus, was du brauchst –
praktisch, wenn du nicht mehr weißt, in welchem Snapshot die gesuchte Version steckt.</p>
<h3 id="schritt-5-die-integrität-prüfen">Schritt 5: Die Integrität prüfen</h3>
<p><code>check</code> verifiziert, dass die Struktur des Repositorys intakt ist:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">restic check</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">load indexes
</span></span><span class="line"><span class="cl">check all packs
</span></span><span class="line"><span class="cl">check snapshots, trees and blobs
</span></span><span class="line"><span class="cl">[0:00] 100.00%  2 / 2 snapshots
</span></span><span class="line"><span class="cl">no errors were found</span></span></code></pre></div>
</div>
<p>Das prüft die Metadaten, aber nicht die eigentlichen Daten. Lies deshalb regelmäßig
einen Teil der echten Daten mit – z. B. 10 % pro Lauf, über Wochen also alles:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">restic check --read-data-subset<span class="o">=</span>10%</span></span></code></pre></div>
</div>
<h3 id="schritt-6-aufräumen-mit-einer-aufbewahrungsregel">Schritt 6: Aufräumen mit einer Aufbewahrungsregel</h3>
<p>Ohne Aufräumen wächst das Repository ewig. <code>forget</code> wendet eine <strong>Retention-Policy</strong>
an, <code>--prune</code> gibt den Speicher der entfernten Snapshots frei:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">restic forget --keep-daily <span class="m">7</span> --keep-weekly <span class="m">4</span> --keep-monthly <span class="m">6</span> --prune</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Applying Policy: keep 7 daily, 4 weekly, 6 monthly snapshots
</span></span><span class="line"><span class="cl">keep 2 snapshots:
</span></span><span class="line"><span class="cl">2 snapshots</span></span></code></pre></div>
</div>
<p>Das behält die letzten 7 täglichen, 4 wöchentlichen und 6 monatlichen Snapshots –
ein guter Standard. Die Stufen greifen ineinander: feine Auflösung für die jüngste
Vergangenheit (jeden Tag einer), gröber je weiter zurück (einer pro Woche, dann einer
pro Monat). So kommst du im Zweifel nah an den gewünschten Stand heran, ohne dass das
Repository unbegrenzt wächst. Ältere Snapshots fallen automatisch weg. <strong><code>--prune</code> ist der aufwändige
Teil</strong> (es packt Daten um); bei großen Repositories lässt man es oft nur ein- bis
zweimal pro Woche laufen, nicht bei jedem Backup.</p>
<h3 id="schritt-7-alles-automatisieren-systemd-timer">Schritt 7: Alles automatisieren (systemd-Timer)</h3>
<p>Ein Backup, das du von Hand starten musst, vergisst du. Lege die Zugangsdaten als
Environment-Datei ab (damit der Dienst sie kennt):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># /etc/restic/restic.env</span>
</span></span><span class="line"><span class="cl"><span class="nv">RESTIC_REPOSITORY</span><span class="o">=</span>sftp:BACKUP_BENUTZER@DEIN_BACKUP_HOST:/backups/DEIN_SERVER
</span></span><span class="line"><span class="cl"><span class="nv">RESTIC_PASSWORD_FILE</span><span class="o">=</span>/etc/restic/password</span></span></code></pre></div>
</div>
<p>Dann ein Wrapper-Skript <code>/usr/local/bin/restic-backup.sh</code> – es dumpt zuerst die
Datenbank (Beispiel PostgreSQL), sichert, räumt auf und meldet am Ende Erfolg an
<a href="/tutorials/uptime-kuma-monitoring/">Uptime Kuma</a> (der Push-Monitor als „Dead man&rsquo;s
switch&quot;):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="nb">set</span> -euo pipefail
</span></span><span class="line"><span class="cl"><span class="c1"># 0) Ziel-Verzeichnis für den Dump sicherstellen (sonst bricht der erste Lauf ab)</span>
</span></span><span class="line"><span class="cl">mkdir -p /opt/dumps
</span></span><span class="line"><span class="cl"><span class="c1"># 1) Datenbank konsistent dumpen (nie die laufende DB-Datei sichern)</span>
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> DEIN_DB_CONTAINER pg_dumpall -U postgres &gt; /opt/dumps/db.sql
</span></span><span class="line"><span class="cl"><span class="c1"># 2) Backup</span>
</span></span><span class="line"><span class="cl">restic backup /opt/apps /opt/dumps --tag auto
</span></span><span class="line"><span class="cl"><span class="c1"># 3) Aufräumen (Retention)</span>
</span></span><span class="line"><span class="cl">restic forget --keep-daily <span class="m">7</span> --keep-weekly <span class="m">4</span> --keep-monthly <span class="m">6</span> --prune
</span></span><span class="line"><span class="cl"><span class="c1"># 4) Erfolg an Uptime Kuma melden</span>
</span></span><span class="line"><span class="cl">curl -fsS <span class="s2">&#34;https://status.DEINE_DOMAIN/api/push/DEIN_TOKEN?status=up&amp;msg=restic+OK&#34;</span> &gt; /dev/null</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo chmod <span class="m">700</span> /usr/local/bin/restic-backup.sh</span></span></code></pre></div>
</div>
<p>Der Dienst lädt die Environment-Datei und führt das Skript aus –
<code>/etc/systemd/system/restic-backup.service</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
</span></span><span class="line"><span class="cl"><span class="na">EnvironmentFile</span><span class="o">=</span><span class="s">/etc/restic/restic.env</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/local/bin/restic-backup.sh</span></span></span></code></pre></div>
</div>
<p>Und der Timer <code>/etc/systemd/system/restic-backup.timer</code> startet ihn täglich nachts:</p>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Timer]</span>
</span></span><span class="line"><span class="cl"><span class="na">OnCalendar</span><span class="o">=</span><span class="s">*-*-* 02:30:00</span>
</span></span><span class="line"><span class="cl"><span class="na">Persistent</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">timers.target</span></span></span></code></pre></div>
</div>
<p><code>Persistent=true</code> holt einen verpassten Lauf (z. B. weil der Server nachts aus war)
beim nächsten Start nach. Aktivieren:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl daemon-reload
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now restic-backup.timer</span></span></code></pre></div>
</div>
<p>Prüfe mit <code>systemctl list-timers restic-backup.timer</code>, wann der nächste Lauf ansteht.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Backup-Zeit nicht mit dem automatischen Reboot kollidieren lassen
  </p>
  <div class="prose-kitchen text-sm">Läuft auf dem Server ein <a href="/tutorials/automatische-updates-unattended-upgrades/">automatischer Neustart durch
unattended-upgrades</a> (im dortigen
Beispiel <code>04:00</code>), muss das Backup <strong>vorher</strong> durch sein. Wird der laufende
Backup-Lauf (<code>restic backup</code> bzw. das anschließende <code>restic forget --prune</code>) mitten
im Betrieb vom Reboot abgeschnitten, bleibt eine
<strong>stale Lock</strong> im Repository zurück (<code>repository is already locked</code>, siehe unten). Deshalb
startet der Timer hier um <code>02:30</code> – genug Abstand für ein größeres Backup samt Aufräumen.
Passt du eine der beiden Zeiten an, halte den Abstand bewusst groß.</div>
</div>
<h3 id="schritt-8-den-ernstfall-einmal-proben">Schritt 8: Den Ernstfall einmal proben</h3>
<p>Ein Backup, das du nie zurückgespielt hast, ist nur eine Hoffnung. Spiele deshalb
einmal bewusst einen kompletten Restore durch – idealerweise auf einem <strong>anderen</strong>
oder frisch aufgesetzten Server, damit du auch die Zugangsdaten (Repository-Adresse
<strong>und</strong> Passwort) unter realen Bedingungen brauchst:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">RESTIC_REPOSITORY</span><span class="o">=</span><span class="s2">&#34;sftp:BACKUP_BENUTZER@DEIN_BACKUP_HOST:/backups/DEIN_SERVER&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">RESTIC_PASSWORD_FILE</span><span class="o">=</span><span class="s2">&#34;/etc/restic/password&#34;</span>
</span></span><span class="line"><span class="cl">restic snapshots
</span></span><span class="line"><span class="cl">restic restore latest --target /tmp/dr-test</span></span></code></pre></div>
</div>
<p>Vergleiche danach Stichproben mit dem Original (<code>diff</code>, Datei-Hashes) und fahre die
App aus den wiederhergestellten Daten testweise hoch. Erst wenn das klappt, weißt du,
dass dein Backup im Ernstfall trägt – und dass du dich an den Ablauf erinnerst, wenn
es wirklich darauf ankommt.</p>
<h2 id="wenn-es-nicht-funktioniert">Wenn es nicht funktioniert</h2>
<p><strong>Symptom:</strong> <code>subprocess ssh: Host key verification failed</code> beim SFTP-Ziel.</p>
<p><strong>Ursache &amp; Lösung:</strong> Restic kann den SSH-Hostkey des Backup-Servers nicht bestätigen,
weil er noch nicht bekannt ist. Verbinde dich <strong>einmal manuell</strong>
(<code>ssh BACKUP_BENUTZER@DEIN_BACKUP_HOST</code>) und bestätige den Fingerprint – oder hinterlege
ihn mit <code>ssh-keyscan DEIN_BACKUP_HOST &gt;&gt; ~/.ssh/known_hosts</code>. Danach läuft Restic durch.</p>
<p><strong>Symptom:</strong> <code>wrong password</code> oder <code>repository does not exist</code>.</p>
<p><strong>Ursache &amp; Lösung:</strong> <code>RESTIC_REPOSITORY</code> oder <code>RESTIC_PASSWORD_FILE</code> ist nicht gesetzt
oder zeigt ins Leere – im systemd-Dienst also die <code>EnvironmentFile</code> prüfen. Ein per
Hand gesetztes <code>export</code> gilt nur in der aktuellen Shell.</p>
<p><strong>Symptom:</strong> <code>repository is already locked</code>.</p>
<p><strong>Ursache &amp; Lösung:</strong> Ein abgebrochener Lauf hat eine Sperre hinterlassen. Prüfe,
dass <strong>wirklich kein</strong> Backup mehr läuft, dann <code>restic unlock</code>. Niemals blind
entsperren, während parallel ein Lauf aktiv ist.</p>
<p><strong>Symptom:</strong> Das Backup wird riesig / sichert Unsinn.</p>
<p><strong>Ursache &amp; Lösung:</strong> Grenze mit <code>--exclude</code>/<code>--exclude-file</code> ein (Caches, Logs,
temporäre Dateien) und nutze <code>--one-file-system</code>, damit Restic nicht in gemountete
Fremd-Dateisysteme abtaucht.</p>
<p><strong>Symptom:</strong> <code>prune</code> dauert ewig oder wurde abgebrochen.</p>
<p><strong>Ursache &amp; Lösung:</strong> Kein Grund zur Panik – <code>prune</code> ist wiederaufnehmbar und das
Repository bleibt gültig. Starte es erneut und lass anschließend einmal <code>restic check</code>
laufen. Trenne <code>--prune</code> bei großen Repos vom täglichen Backup (siehe Schritt 6).</p>
<p><strong>Symptom:</strong> Der Timer läuft, aber in Uptime Kuma kommt nie eine Erfolgsmeldung.</p>
<p><strong>Ursache &amp; Lösung:</strong> Sieh dir den letzten Lauf mit
<code>journalctl -u restic-backup.service</code> an. Meist bricht das Skript <strong>vor</strong> dem
<code>curl</code>-Aufruf ab (z. B. der DB-Dump schlug fehl) – dank <code>set -euo pipefail</code> stoppt es
dann sauber, statt ein kaputtes Backup als Erfolg zu melden. Der ausbleibende Push ist
also kein Bug, sondern genau das Alarmsignal, das du haben willst.</p>
<h2 id="wartung--backups">Wartung &amp; Backups</h2>
<ul>
<li><strong>Restores regelmäßig testen</strong> – das ist der wichtigste Punkt überhaupt. Ein Backup,
das du nie zurückgespielt hast, ist kein Backup, sondern eine Hoffnung. Plane
<strong>monatlich</strong> einen Test-Restore in ein leeres Verzeichnis ein.</li>
<li><strong>3-2-1-Regel:</strong> mindestens 3 Kopien, auf 2 verschiedenen Medien, davon 1 <strong>off-site</strong>.
Restic deckt das Off-Site-Stück ab – „off-site&quot; heißt aber wirklich ein <strong>anderer
Anbieter/Standort</strong>, nicht dasselbe Rechenzentrum.</li>
<li><strong>Backups überwachen:</strong> Ohne Monitoring merkst du ein totes Backup erst im
Ernstfall. Der Push-Aufruf aus Schritt 7 an
<a href="/tutorials/uptime-kuma-monitoring/">Uptime Kuma</a> schlägt Alarm, wenn ein Lauf
ausbleibt.</li>
<li><strong>Integrität prüfen:</strong> <code>restic check --read-data-subset</code> regelmäßig laufen lassen,
damit stiller Bit-Rot auffällt, bevor du die Daten brauchst.</li>
<li><strong>Passwort-/Schlüssel-Management:</strong> Das Repository-Passwort getrennt vom Server
aufbewahren. Für einzelne Clients kannst du mit <code>restic key add</code> zusätzliche
Passwörter vergeben, ohne das Haupt-Passwort zu teilen.</li>
</ul>
]]></content:encoded></item></channel></rss>