blog/public/backing-up-nixos-state-with-restic/index.html
2026-03-18 20:32:25 +00:00

119 lines
10 KiB
HTML

<!doctype html>
<html lang="en"><head><script src="/livereload.js?mindelay=10&amp;v=2&amp;port=1313&amp;path=livereload" data-no-instant defer></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="canonical" href="http://localhost:1313/backing-up-nixos-state-with-restic/" />
<title>Backing up nixos state with restic - James&#39; Blog</title>
<link rel="stylesheet" href="/css/style.css">
<script>
document.documentElement.setAttribute('data-theme', 'dark');
</script>
</head>
<body><header class="site-header">
<nav>
<a href="http://localhost:1313/" class="home">~</a>
<div class="nav-links">
<a href="/search">/</a>
</div>
</nav>
</header>
<main id="content">
<article class="post">
<header>
<h1>Backing up nixos state with restic</h1>
<time>Feb 16, 2026</time>
<div class="tags">
<a href="/tags/nixos">nixos</a>
<a href="/tags/restic">restic</a>
</div>
</header>
<div class="content">
<p>I&rsquo;m writing this so I can hopefully remember what I did in six months.</p>
<p>As hard as you try to eliminate all state from your computing life with nixos, the fact remains that you can&rsquo;t get rid of all of it.
For example, I run forgejo on my VPS.
I have my config which means I could set up a forgejo instance just how I like it if everything went to pot.
But that wouldn&rsquo;t bring back any of the repos I had there previously.</p>
<p>This is the method I cooked up for backing up some of those important bits and bob from my VPS.</p>
<h3 id="restic">Restic</h3>
<p><a href="https://restic.net/">Restic</a> is a project which facilitates the encrypted, deduplicated backing up of your data to SFTP, S3, and various other cloud providers.
My backup target is a hetzner storage box which is compatible with SFTP so that&rsquo;s the route I chose.
After browsing the <a href="https://search.nixos.org/options?channel=unstable&amp;query=restic">available nixos options</a>, I came up with this little config.
It sets up a restic job to be run daily by root backing up some of <code>/var/lib</code> over sftp to the hetzner storage box (I&rsquo;ve put in placeholder values).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">services</span><span class="o">.</span><span class="n">restic</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">backups</span><span class="o">.</span><span class="s2">&#34;hetzner-storage-box&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">initialize</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">user</span> <span class="o">=</span> <span class="s2">&#34;root&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">passwordFile</span> <span class="o">=</span> <span class="s2">&#34;/etc/nixos/secrets/restic&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">paths</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;/var/lib/important&#34;</span>
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;/var/lib/stuff&#34;</span>
</span></span><span class="line"><span class="cl"> <span class="p">];</span>
</span></span><span class="line"><span class="cl"> <span class="n">repository</span> <span class="o">=</span> <span class="s2">&#34;sftp:user@storagebox:/payload&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">extraOptions</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;sftp.command=&#39;ssh user@storagebox -i /root/.ssh/id_ed25519 -s sftp&#39;&#34;</span>
</span></span><span class="line"><span class="cl"> <span class="p">];</span>
</span></span><span class="line"><span class="cl"> <span class="n">timerConfig</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">OnCalendar</span> <span class="o">=</span> <span class="s2">&#34;daily&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">Persistent</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="security-concerns">Security concerns</h3>
<p>The eagle-eyed among you will have noticed some GAPING security flaws in this setup.
Firstly, my root user needs passwordless ssh access to the storage box.
Secondly, the password used to encrypt the backup is sitting in plaintext on my server at <code>/etc/nixos/secrets/restic</code>.</p>
<p>The first issue seems a little tricky to solve.
As far as I know there&rsquo;s no way round the passwordless detail if you want an automated backup.
It would be better to run the backup as a less privileged user that still has permissions to the stuff you want to backup.
I&rsquo;m not sure how to do that though and the whole thing seemed sufficiently complicated alraedy for me.
I found something about it in the docs <a href="https://restic.readthedocs.io/en/stable/080_examples.html#full-backup-without-root">here</a></p>
<p>The second issue is extremely solvable with something like <a href="https://github.com/Mic92/sops-nix">sops-nix</a> which I really do intend to setup at some point I promise!</p>
<h3 id="bonus-notifications">Bonus: notifications</h3>
<p>Seeing as these backups are going to be chugging away in the background in the middle of the night, it would be useful to be notified if they went wrong.
The nixos service sets up a systemd service for the restic job.
We can piggyback off this one with another systemd service which runs when the restic service fails.
This new service simply curls my <a href="https://ntfy.sh">ntfy</a> server with an uh oh pay attention message.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"> <span class="n">systemd</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">restic-backups-vps-storage-box</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">wantedBy</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;multi-user.target&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl"> <span class="n">unitConfig</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">OnFailure</span> <span class="o">=</span> <span class="s2">&#34;restic-backups-failure-notify.service&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">systemd</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">restic-backups-failure-notify</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;Notify on restic backup failure&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">serviceConfig</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">Type</span> <span class="o">=</span> <span class="s2">&#34;oneshot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">ExecStart</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl"> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">curl</span><span class="si">}</span><span class="s2">/bin/curl -s -X POST https://ntfy.sh/restic-backups-topic &#34;</span>
</span></span><span class="line"><span class="cl"> <span class="o">+</span> <span class="s2">&#34;-d &#39;Restic backup from VPS to storage box failed!&#39; &#34;</span>
</span></span><span class="line"><span class="cl"> <span class="o">+</span> <span class="s2">&#34;-H &#39;Title: Backup Failed&#39; &#34;</span>
</span></span><span class="line"><span class="cl"> <span class="o">+</span> <span class="s2">&#34;-H &#39;Priority: high&#39; &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">User</span> <span class="o">=</span> <span class="s2">&#34;root&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span></code></pre></div><p>Thanks for reading :)</p>
</div>
</article>
</main><footer class="site-footer">
<small>
<a href="/index.xml">RSS</a>
·
<a href="https://gohugo.io">Hugo</a>
</small>
<script src="/js/script.js"></script>
</footer>
</body>
</html>