A comic version of a white guy with short hair, smiling.

I built a NAS

Without going too deep into it, in light of our late-stage surveillance capitalism and political escalations, I do not want to depend on tech companies for my personal data, especially those from the United States.

In lieu of that, I’ve decided to take matters into my own hands and build a NAS.

Table of contents

History

I’ve been hosting my own music since 2024, and I moved my personal data to a hosted NextCloud on Hetzner shortly after. Cryptomator helped me end-to-end encrypt my files on the Hetzner servers, and while I would recommend this, the workflow is a bit convoluted—for example, on macOS it mounts a volume that is harder to access.

The biggest problem for me was photos—I was using iCloud photos, and even though something like Immich get’s really close to the functionality I’m interested in, it’s not automatically end-to-end encrypted.

Hardware

Let’s talk about constraints:

  • No pre-built, closed-source solution (Synology, QNAP, etc.): If I do this, I’m going to do it the right way.
  • Ideally SSD-only: Using HDDs is cheap (see next point), but they’re more prone to fail and wayyy slower.
  • Enough power: Because I wanted to run apps like Immich or NextCloud, I needed a fast-enough processor and enough memory, at least 8 GB.
  • Reasonably priced: My budget was around 700 €.

Things I considered:

  • Old QNAP stations: Some of these come with SATA slots that also support SSD, but ultimately the processors of the ones I looked at were too weak.
  • Framework/Intel NUC/whatever with an external NVMe/M.2 case for my storage SSDs: There aren’t a lot of external NVMe RAID cases out there that look trustworthy, plus it felt wrong to have them connected over USB; it’s probably a lot slower as well.

At this point, I felt like there was no good solution, until I found the Intel NUC NUC6i7KYK, a.k.a. Skull Canyon. It has:

  • Intel Core i7 (6MB cache, up to 3.5 GHz)
  • 16 GB memory (at least the one I got on eBay)
  • 2 internal M.2 SSD slots

This sounded perfect, and I got it used for around 200 €, with an internal 500 GB SSD already built in, which I used to test the setup.

My plan is to then get 2× 4 TB SSDs and mirror them for maximum security. This is more than enough storage for our family for the next few years. Also have you looked at SSD prices lately? 100 €/TB is a good deal.

System

From my research, these were the three top options:

  • TrueNAS Community Edition (Scale): Open-source NAS system based on ZFS, with support for many apps using Docker.
  • Unraid: Closed source; supports ZFS; BTRFS and XFS; needs a license ($249 for Lifetime)
  • Handrolled: Build everthing myself using Arch/NixOS w/ Docker

While Unraid looked promising, I did not want any closed-source software. I thought about hand-rolling, but since I wanted this setup to be as stable as possible and I have very little experience with ZFS, I eventually decided on TrueNAS Scale.

One annoyance with TrueNAS1 was that the installation could not be on a drive used for data. After some research, I decided to install on an SSD in a SATA case connected via USB. Booting from an external drive is not great and should be avoided if possible, but this is a common setup for people building NAS systems using mini-computers.

One problem I encountered was that initramfs failed to find the boot pool on boot because the (external) SSD was not discovered when it tried. I solved this problem by running this on the TrueNAS shell:

midclt call system.advanced.update '{"kernel_extra_options": "rootdelay=20"}'

This pauses the boot process for 20s, giving the USB controller time to set up the SSD.

Security

Security is all about attack vectors. My setup is meant to protect against theft and automated attacks. There is room to tighten this up even more, but this is more than good enough for me.

ZFS encryption

TrueNAS supports ZFS-native encryption. You can choose to encrypt the root pool, which I did; the encryption key is stored on the system itself for auto-unlock. This is great, but it offers no protection against the whole system being stolen.

Because of this, I have a top-level dataset under tank called encrypted, which is encrypted with a password. This means the system boots, but no data (including app data) is accessible until I log in and decrypt the dataset.

For this reason, I’m also not using the built-in backup solution; I don’t want the encryption key and password to be accessible on boot. More about backups further down.

I disabled the text console without a password prompt to prevent someone with physical access to the running, decrypted system from extracting data.

Headscale

There’s no way I’m exposing this system to the public internet. This is far too dangerous, even if you know what you’re doing. I still want to be able to access all my data from on the go, so I set up Headscale, an open-source, self-hosted version of Tailscale and compatible with the Tailscale apps. With this, I can access my NAS from anywhere via a self-hosted VPN.

SSL

But what if a machine in the VPN or on my local Wi-Fi gets compromised? The attacker would be able to sniff all HTTP traffic, extract credentials, and access all my data. This is why we encrypt our HTTP traffic.

For the TrueNAS system, I forward HTTP to HTTPS and use the self-signed certificate that comes pre installed.

But most TrueNAS apps expose ports without SSL, which means we need a reverse proxy. While TrueNAS supports the option to expose ports for inter-container communication, each app gets its own Docker network, which means a reverse proxy can’t access other apps by default2. To fix this, I use Dragonify (which I forked for security reasons). It connects all apps to a single network, so I can use Nginx Proxy Manager3 to serve apps with SSL on a subdomain.

I bought a domain solely for this setup and use deSEC with an account created specifically for this domain. This means when a token gets compromised, only that single domain is affected. But that token lives on the encrypted dataset, soo it’s unlikely.

Backups

Because I don’t want any encryption keys or access credentials on the system partition, I’m not using the native TrueNAS backup systems. After some research, I’ve landed on Backrest, a configuration interface for Restic.

Here’s what Filippo Valsorda has to say about Restic encryption:

“The design might not be perfect, but it’s good. Encryption is a first-class feature, the implementation looks sane and I guess the deduplication trade-off is worth it. So… I’m going to use restic for my personal backups.”

For storage, I’m using Hetzner Object Storage, mostly because it’s environmentally friendly and hosted in the EU.

Apps

We already talked about Tailscale, Dragonify, Nginx Proxy Manager, and Backrest.

Here are the apps I’m using

Conclusion

So that’s my setup. It works really well, and I’m happy. I’ll try to edit this post in six months to give an update.

Am I missing something? Let me know: hey@arne.me

Thanks to Eric & Jan for proof-reading this post and listening to my ramblings!

  1. Not sure if this restriction also applies to Unraid/other systems.

  2. Apparently this was possible with 24.04 (Dragonfish)

  3. I might switch to a boring Caddy configuration soon.

  4. E.g. syncing my Obsidian vault or Steamdeck screenshots