<?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/en/tags/restic/</link><description>Restic – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>en-US</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/en/tags/restic/index.xml" rel="self" type="application/rss+xml"/><item><title>Restic backups: encrypted and off-site</title><link>https://serverkueche.de/en/tutorials/restic-backups/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/restic-backups/</guid><description>Off-site backups with Restic: set up encrypted, restore snapshots, prune with retention and automate via a systemd timer.</description><content:encoded><![CDATA[<p>A snapshot at the provider is <strong>not a backup</strong>: it sits on the same infrastructure and
is gone too if your account is suspended, the data center burns down, or you
accidentally delete the wrong project. <strong>Restic</strong> turns it into a real backup –
encrypted, deduplicated and in a place <strong>outside</strong> your server.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end, <strong>Restic</strong> backs up your important data – app volumes, configurations,
database dumps – <strong>end-to-end encrypted</strong> and <strong>deduplicated</strong> into a repository,
ideally <strong>off-site</strong> (a second server or storage). You can restore individual files or
whole snapshots at any time, automatically thin out old backups by a retention policy,
and check integrity. Finally, everything runs automatically via a systemd timer and
alerts you if a run fails to happen.</p>
<p>Tested with <strong>Restic 0.18</strong> (the version from the Debian 13 package sources); upstream
already has 0.19.x – how to update is in step 1. Restic <strong>always</strong> encrypts – there is
no unencrypted 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">The <a href="/en/tutorials/netcup-snapshots-scp/">snapshot in the provider panel</a> is a
convenient &ldquo;rewind&rdquo; – but it sits on the <strong>same</strong> infrastructure and under the <strong>same</strong>
account. If the provider, the data center or your access fails, the snapshot is gone
too. A real backup lives <strong>outside</strong> (another provider/location), is <strong>encrypted</strong> and
you can restore it <strong>independently</strong>. That&rsquo;s exactly Restic&rsquo;s job – the snapshot stays
the quick safety net for small mishaps.</div>
</div>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A server (Debian 13) with the data you want to back up – e.g. the volumes and
Compose files of your <a href="/en/tutorials/install-docker/">Docker apps</a>.</li>
<li>An <strong>off-site target</strong> for the backups. Restic speaks, among others:
<ul>
<li><strong>SFTP</strong> – a second server or a storage box (easiest to start with),</li>
<li><strong>S3-compatible</strong> – e.g. an object-storage bucket,</li>
<li><strong>rclone</strong> – as a bridge to Backblaze B2, Google Drive and dozens more.</li>
</ul>
</li>
<li>Basic familiarity with the 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>🍳 Recommendation</span>
    <span title="Links marked with * are affiliate links.">Ad</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 vCores · 8 GB RAM · 256 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">from €10.36/month</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">A small second VPS at a different location is a cheap, dedicated backup target via SFTP.</p>
    </div>
    <a href="https://www.netcup.com/en/server/vps/vps-1000-g12-iv-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">
  Go to 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 € voucher</strong> for new netcup customers:</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">(new customers only, no domains)</span>
</p></div>
</div>

<h2 id="step-by-step">Step by step</h2>
<h3 id="step-1-install-restic">Step 1: Install Restic</h3>
<p>Restic is in the package sources and is updated with the system:</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>Check the 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>A newer version if needed
  </p>
  <div class="prose-kitchen text-sm">Upstream, Restic is sometimes ahead (currently 0.19.x). If you need a newer feature,
download the official binary from the
<a href="https://github.com/restic/restic/releases">Restic release page</a> and place it in
<code>/usr/local/bin</code> (that takes precedence over <code>/usr/bin</code>). The built-in command
<code>restic self-update</code> does <strong>not</strong> work with the Debian package version – the package is
built without that feature (<code>unknown command &quot;self-update&quot;</code>). For most people the
package version is entirely enough.</div>
</div>
<h3 id="step-2-create-the-encrypted-repository">Step 2: Create the encrypted repository</h3>
<p>A Restic <strong>repository</strong> is the encrypted storage location of your backups. It&rsquo;s
protected with a password – and this password does <strong>not</strong> belong in a script and
<strong>not</strong> next to the repository. Put it in a file only root may read:</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>The <code>openssl</code> command generates a long random password and stores it under
<code>/etc/restic/password</code> (the <code>install</code> command before it creates the directory, the
<code>chmod</code> after it protects the file). Tell Restic via environment variables where the
repository is and where the password is – here first an off-site target via 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_USER@YOUR_BACKUP_HOST:/backups/YOUR_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>Replace <code>BACKUP_USER</code>, <code>YOUR_BACKUP_HOST</code> and the path with your real target. If you
want to practice locally first, use a folder instead:
<code>export RESTIC_REPOSITORY=&quot;/srv/restic-repo&quot;</code>. The commands afterwards are
<strong>identical</strong> – only the repository address differs.</p>
<p>You use other off-site targets via the same variable, only with a different prefix:</p>
<ul>
<li><strong>S3-compatible:</strong> <code>s3:https://ENDPOINT/BUCKET</code> – plus the credentials as
<code>AWS_ACCESS_KEY_ID</code> and <code>AWS_SECRET_ACCESS_KEY</code> in the environment (or in the
environment file from step 7).</li>
<li><strong>rclone:</strong> <code>rclone:REMOTE:path</code> – the bridge to Backblaze B2, Google Drive and many
more, once you&rsquo;ve set up the rclone remote.</li>
</ul>
<p>No matter which backend: encryption, deduplication and all the following commands stay
the same.</p>
<p>Now initialize the repository (the example output shows the local practice repository –
with the SFTP target, your <code>sftp:</code> address appears there instead):</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>Password lost = data lost
  </p>
  <div class="prose-kitchen text-sm">There is <strong>no</strong> back door. Without the repository password your backups are
irrecoverably encrypted. Back up the contents of <code>/etc/restic/password</code> additionally in
a <strong>password manager</strong> – separate from the server, otherwise it&rsquo;s no use to you if the
server is lost.</div>
</div>
<h3 id="step-3-the-first-backup">Step 3: The first backup</h3>
<p>Back up the directory with your data. The <code>--tag</code> helps with filtering later:</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>On the <strong>second</strong> run, Restic&rsquo;s strength shows: it only backs up the changes
(deduplication). If a file changes and you back up again:</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>Instead of 2 MiB again, only <strong>1.1 KiB</strong> land in the repository – just the changed
part.</p>
<p>What belongs in the backup? For Docker apps that&rsquo;s three things – recipe, payload, DB:</p>
<ul>
<li><strong>Compose files</strong> (your &ldquo;recipe&rdquo;) and <code>.env</code>: small but indispensable for rebuilding
the stack.</li>
<li><strong>Bind mounts</strong> (<code>./data:/…</code>) live directly in the project folder – you back those up
right along. <strong>Named volumes</strong> you&rsquo;ll find under
<code>/var/lib/docker/volumes/&lt;name&gt;/_data</code>; include the path.</li>
<li><strong>Databases</strong> never as a live file – a backup mid-write is potentially unusable.
Instead create a <strong>consistent dump</strong> (<code>pg_dump</code>/<code>mariadb-dump</code>) and back that up (see
step 7).</li>
</ul>
<p>You exclude the unnecessary:</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="step-4-restore">Step 4: Restore</h3>
<p>A backup is only as good as its restore. First list the 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>Restore a complete snapshot to a target directory (use <code>latest</code> for the newest or a
specific 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>If you only need <strong>one</strong> file, there are two ways – restore that specific path:</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>… or stream the file directly to standard output, without any detour:</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>To <strong>browse</strong>, you can even mount the repository as a filesystem (<code>restic mount /mnt/backup</code>, requires FUSE) and copy out what you need in the file manager – handy when
you no longer know which snapshot the version you&rsquo;re looking for is in.</p>
<h3 id="step-5-check-integrity">Step 5: Check integrity</h3>
<p><code>check</code> verifies that the repository&rsquo;s structure is intact:</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>That checks the metadata, but not the actual data. So regularly read part of the real
data along with it – e.g. 10% per run, i.e. everything over the course of weeks:</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="step-6-prune-with-a-retention-policy">Step 6: Prune with a retention policy</h3>
<p>Without pruning, the repository grows forever. <code>forget</code> applies a <strong>retention policy</strong>,
<code>--prune</code> frees the storage of the removed 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 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>This keeps the last 7 daily, 4 weekly and 6 monthly snapshots – a good default. The
tiers interlock: fine resolution for the recent past (one per day), coarser the further
back (one per week, then one per month). So, if in doubt, you can get close to the
desired state without the repository growing unbounded. Older snapshots drop off
automatically. <strong><code>--prune</code> is the expensive part</strong> (it repacks data); on large
repositories you often run it only once or twice a week, not on every backup.</p>
<h3 id="step-7-automate-everything-systemd-timer">Step 7: Automate everything (systemd timer)</h3>
<p>A backup you have to start by hand is one you forget. Put the credentials in an
environment file (so the service knows them):</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_USER@YOUR_BACKUP_HOST:/backups/YOUR_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>Then a wrapper script <code>/usr/local/bin/restic-backup.sh</code> – it first dumps the database
(PostgreSQL example), backs up, prunes and finally reports success to
<a href="/en/tutorials/uptime-kuma-monitoring/">Uptime Kuma</a> (the push monitor as a &ldquo;dead man&rsquo;s
switch&rdquo;):</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) Ensure the target directory for the dump (otherwise the first run aborts)</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) Dump the database consistently (never back up the live DB file)</span>
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> YOUR_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) Prune (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) Report success to Uptime Kuma</span>
</span></span><span class="line"><span class="cl">curl -fsS <span class="s2">&#34;https://status.YOUR_DOMAIN/api/push/YOUR_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>The service loads the environment file and runs the script –
<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>And the timer <code>/etc/systemd/system/restic-backup.timer</code> starts it nightly:</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> catches up a missed run (e.g. because the server was off at night) on
the next start. Enable it:</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>Check with <code>systemctl list-timers restic-backup.timer</code> when the next run is due.</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>Don&#39;t let backup time collide with the automatic reboot
  </p>
  <div class="prose-kitchen text-sm">If the server runs an <a href="/en/tutorials/unattended-upgrades-automatic-updates/">automatic reboot via
unattended-upgrades</a> (in that
example <code>04:00</code>), the backup must be done <strong>before</strong> then. If the running backup run
(<code>restic backup</code> or the subsequent <code>restic forget --prune</code>) is cut off mid-operation by
the reboot, a <strong>stale lock</strong> remains in the repository (<code>repository is already locked</code>,
see below). That&rsquo;s why the timer here starts at <code>02:30</code> – enough headroom for a larger
backup plus pruning. If you adjust either time, deliberately keep the gap large.</div>
</div>
<h3 id="step-8-rehearse-the-worst-case-once">Step 8: Rehearse the worst case once</h3>
<p>A backup you&rsquo;ve never restored is just a hope. So deliberately run through a complete
restore once – ideally on a <strong>different</strong> or freshly set-up server, so you also need the
credentials (repository address <strong>and</strong> password) under real conditions:</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_USER@YOUR_BACKUP_HOST:/backups/YOUR_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>Then compare samples with the original (<code>diff</code>, file hashes) and boot the app from the
restored data as a test. Only once that works do you know your backup holds up in an
emergency – and that you&rsquo;ll remember the procedure when it really counts.</p>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong><code>subprocess ssh: Host key verification failed</code> with the SFTP target.</strong> Restic can&rsquo;t
confirm the SSH host key of the backup server because it isn&rsquo;t known yet. Connect
<strong>once manually</strong> (<code>ssh BACKUP_USER@YOUR_BACKUP_HOST</code>) and confirm the fingerprint – or
store it with <code>ssh-keyscan YOUR_BACKUP_HOST &gt;&gt; ~/.ssh/known_hosts</code>. After that Restic
runs through.</p>
<p><strong><code>wrong password</code> or <code>repository does not exist</code>.</strong> <code>RESTIC_REPOSITORY</code> or
<code>RESTIC_PASSWORD_FILE</code> isn&rsquo;t set or points nowhere – so check the <code>EnvironmentFile</code> in
the systemd service. A manually set <code>export</code> only applies in the current shell.</p>
<p><strong><code>repository is already locked</code>.</strong> An aborted run left a lock. Check that <strong>really
no</strong> backup is running anymore, then <code>restic unlock</code>. Never unlock blindly while a run
is active in parallel.</p>
<p><strong>The backup gets huge / backs up nonsense.</strong> Restrict it with
<code>--exclude</code>/<code>--exclude-file</code> (caches, logs, temporary files) and use
<code>--one-file-system</code> so Restic doesn&rsquo;t dive into mounted foreign filesystems.</p>
<p><strong><code>prune</code> takes forever or was aborted.</strong> No reason to panic – <code>prune</code> is resumable and
the repository stays valid. Start it again and afterwards run <code>restic check</code> once.
Separate <code>--prune</code> from the daily backup on large repos (see step 6).</p>
<p><strong>The timer runs, but a success message never arrives in Uptime Kuma.</strong> Look at the
last run with <code>journalctl -u restic-backup.service</code>. Usually the script aborts
<strong>before</strong> the <code>curl</code> call (e.g. the DB dump failed) – thanks to <code>set -euo pipefail</code> it
then stops cleanly instead of reporting a broken backup as success. So the missing push
isn&rsquo;t a bug, but exactly the alarm signal you want.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li><strong>Test restores regularly</strong> – that&rsquo;s the single most important point. A backup you&rsquo;ve
never restored is not a backup but a hope. Schedule a test restore into an empty
directory <strong>monthly</strong>.</li>
<li><strong>3-2-1 rule:</strong> at least 3 copies, on 2 different media, of which 1 is <strong>off-site</strong>.
Restic covers the off-site piece – but &ldquo;off-site&rdquo; really means a <strong>different
provider/location</strong>, not the same data center.</li>
<li><strong>Monitor backups:</strong> without monitoring, you only notice a dead backup in an
emergency. The push call from step 7 to
<a href="/en/tutorials/uptime-kuma-monitoring/">Uptime Kuma</a> raises the alarm when a run fails
to happen.</li>
<li><strong>Check integrity:</strong> run <code>restic check --read-data-subset</code> regularly so silent bit rot
is caught before you need the data.</li>
<li><strong>Password/key management:</strong> keep the repository password separate from the server.
For individual clients you can assign additional passwords with <code>restic key add</code>
without sharing the main password.</li>
</ul>
]]></content:encoded></item></channel></rss>