Aumont.fr

Migrating my NAS from TrueNAS Core to vanilla FreeBSD

8 min read.
Jul 2026
  1. Home
  2. › Posts
  3. › Migrating my NAS from TrueNAS Core to vanilla FreeBSD

TrueNAS Core is dying, long live FreeBSD #

I've already explained why I run FreeBSD on my home servers. My NAS, znas, was the one box that didn't fully follow that philosophy: it ran TrueNAS Core, which is FreeBSD under the hood, wrapped in iXsystems' middleware — their web UI, iocage for jails, a .system dataset, a whole abstraction layer on top of ZFS.

It worked fine for years. But TrueNAS Core's future isn't exactly bright. iXsystems keeps pushing everyone toward TrueNAS SCALE, which is Debian-based. Linux.

Nope. Not under my data.

I don't mind Linux on my laptop, I actually like it there. But the box holding every family photo, every backup, every document I own isn't going to run on top of systemd and a Debian base I didn't choose, just because a vendor decided that's the future now. I'd rather run something I actually understand, with the same stability I already get everywhere else in my infra.

So instead of migrating to SCALE, I migrated away from TrueNAS entirely, to a plain vanilla FreeBSD install, using the same recipe I already use on every other box: Bastille jails, pf, rc.conf, ZFS by hand.

The target architecture #

The NAS boots from a USB stick, and I wanted to treat it like an old-school NanoBSD install: minimal writes, so it actually survives more than a year. The plan :

  • zroot (USB stick) : bootloader, kernel, base system. Basically read-only.
  • zssd (SSD) : /var, /usr/local, /home, /root, swap. Everything that writes constantly.
  • ztank (3× HDD, RAIDZ1) : the actual data — photos, films, backups, Nextcloud, Immich...
  • RAM : /tmp, via tmpfs.

Nothing exotic, it's the same "keep writes off the boot media" trick people used with NanoBSD/embedded FreeBSD years ago. TrueNAS does something similar internally anyway, I just wanted to do it myself, without the middleware in the way.

Why not just boot off the SSD directly ? #

Good question. Honest answer : hardware forced my hand. znas is an old HPE ProLiant MicroServer Gen8, and the SSD is wired into the port originally meant for the optical drive (the classic Gen8 slimline-SATA-to-SATA mod). That port just isn't seen as a bootable device by the BIOS. Whatever I tried, the SSD sitting there stayed invisible at boot time.

So "just boot from the SSD, keep it simple" was never really on the table on this box. I still wanted a USB-stick boot device (zero-write goal, plus that's how the machine has always been set up), while getting the SSD to absorb all the write-heavy paths anyway.

The only way to get both was through ZFS datasets : zroot boots off the USB stick, but the mountpoints that matter (/var, /usr/local, /home, /root) get redirected to datasets that physically live on zssd. That's the whole reason step 6 below exists. It's not an optimization for fun, it's a workaround for a decade-old server that refuses to boot from anything plugged into that particular SATA port.

The migration, condensed #

1. Export cleanly from TrueNAS #

Before touching anything : save the config (UID/GID mapping, NFS exports, network config), stop every jail and service, then export the pools properly so FreeBSD can pick them up cleanly :

zpool export ztank
zpool export zssd

Then swap the TrueNAS USB stick for a FreeBSD installer.

2. Install vanilla FreeBSD #

Nothing fancy : FreeBSD 15.1-RELEASE, Auto-ZFS partitioning (for Boot Environments), no swap at install time (that goes on the SSD later). Two gotchas worth knowing :

warning Boot mode
Check that the installer's boot mode (UEFI vs BIOS/CSM) matches whatever TrueNAS was using on that box, or the new stick won't boot.

warning pkgbase
If the 15.1 installer offers to manage the base system through pkg (pkgbase) instead of freebsd-update, think twice. It can write to different paths than expected, which matters once you lock the boot media read-only later.

3. Import the existing pools #

First real TrueNAS-specific trap. TrueNAS always imports pools with altroot=/mnt, which prefixes every mountpoint. Import while stripping that :

zpool import -o altroot="" -f ztank
zpool import -o altroot="" -f zssd

Good news : pools created under TrueNAS Core's (older) OpenZFS import into FreeBSD 15.1 without any conversion needed. zpool upgrade is optional, and it's a one-way door, so only do it once you're sure you're not going back.

4. Clean up TrueNAS's leftovers #

TrueNAS scatters a few things around that are useless, or actively conflicting, on vanilla FreeBSD :

  • 2 GB swap partitions on every HDD → gone (gpart delete), swap now lives on the SSD instead.
  • ztank/.system, TrueNAS's internal metadata dataset → zfs destroy -r.
  • zssd/iocage/*, the jails managed by TrueNAS's own jail manager → destroyed. Jails get recreated with Bastille, same tool I use everywhere else.

5. Zero-write tuning on the USB stick #

atime=off on zroot, /tmp on tmpfs, and a couple of ZFS sysctls to cut down the write frequency on the boot media (vfs.zfs.txg.timeout, min_auto_ashift). Standard NanoBSD-style stuff.

6. Move the write-heavy folders to the SSD #

/var, /usr/local, /home, /root each get their own dataset on zssd, populated with a tar | tar copy and then mount-swapped in. This is where the auto-partitioner actually got in the way : FreeBSD's Auto-ZFS installer creates its own separate datasets for /home, /var/log, /tmp... on the boot pool itself. Leave them mounted and they silently keep absorbing writes on the USB stick, which defeats the whole point of the exercise. They all need mountpoint=none first, children before parents, or the unmount fails with "device busy".

7. Swap, network, NFS, jails, SMART monitoring #

Swap becomes a zvol on zssd. Network config and NFS exports get restored from the notes I took before shutting TrueNAS down. Jails get recreated one by one with Bastille.

TrueNAS's web UI used to give me a nice little disk health dashboard for free. Vanilla FreeBSD gives you smartd and that's about it, so I had to rebuild that piece myself : a monitoring script that runs the SMART checks, pushes an alert to my ntfy instance the moment something looks wrong (with delta-tracking, flag a drive the moment a counter starts increasing, not once it crosses some arbitrary threshold, because a slow bleed matters more than the absolute number), and also pushes the metrics to InfluxDB so I get proper history and graphs in Grafana instead of a one-shot notification I forget about a week later.

Where AI actually saved me #

I've written before about running Claude Code on FreeBSD, and this migration is a good example of why it's actually useful and not just a gimmick.

None of the steps above are hard on their own. What makes this kind of migration painful is the order of operations and the pile of small, undocumented gotchas that only TrueNAS users hit, the stuff you'd normally discover by breaking your NAS at 11pm and grepping forums for an hour.

Claude Code caught quite a few of these before they turned into a problem : the altroot=/mnt trap on import (I would have spent a while wondering why my datasets kept mounting under /mnt/ztank/...), the children-before-parents rule when neutralizing the installer's auto-created datasets, including a nested zroot/home/mathieu dataset I didn't even know existed (created automatically because I'd made a user account during install), tar happily archiving Unix sockets under /var/run as part of the pax format and then failing outright on restore (needed an explicit --exclude), /var/empty being flag-protected (schg) and needing to be recreated by hand after the copy since tar can't restore an immutable flag and still write inside it, and a reminder to actually check whether NFSv4 ACLs were in use before trusting a tar-based copy to preserve them.

Every one of those is a "you'll find out the hard way" kind of detail. Getting them flagged while planning the steps, instead of while debugging a broken mount at midnight, is basically the whole value proposition. I described the target architecture, pasted the relevant zfs list / mount output at each step, and it kept catching the sequencing issues before I actually ran the command.

The disk-monitoring script is a good example of the speed difference too. Rebuilding TrueNAS's disk-health dashboard by hand, with alerting and proper history, would normally mean digging through smartctl output parsing, the InfluxDB line-protocol reference, and a Grafana dashboard JSON I'd inevitably fight with. I described what TrueNAS used to give me and what I wanted instead, and it wrote the parsing, the ntfy push and the InfluxDB write calls in one go. Script running, data flowing into Grafana, maybe two minutes in.

Total clock time, documentation included : 2 to 3 hours. About an hour of that was just brainstorming with the AI first, front-loading it with as much detail as possible about the existing TrueNAS setup (pool layout, exports, jails, quirks) before writing a single command, so the plan actually matched my box instead of some generic TrueNAS install. The rest was the actual install (copying data onto a USB stick isn't exactly a speed demon, no matter how good your plan is) and testing everything with the monitoring in place.

Judging by the TrueNAS forums, full of people who've bricked their pool doing exactly this, that's normally a full weekend gone.

The payoff : a disaster-recovery doc that actually works #

Because the whole thing was done step by step with an assistant that's good at writing things down, I ended up with a clean reinstall procedure as a side effect. Not something I wrote up two weeks later from memory, but the actual sequence of commands, gotchas included, captured in real time.

I've since tested it : full reinstall of the boot stick, reimport zssd and ztank, restore the mountpoints, re-enable swap, done. No TrueNAS, no GUI, no iocage. Same jails, same NFS shares, same SMART alerts, and one very happy owner of a NAS that boots off a USB stick that will actually last.

Datastore stays FreeBSD, data stays mine, and Linux stays on my laptop where it belongs. 😎

#FreeBSD #self-hosting #jails #infrastructure #AI

Mathieu Aumont - 2026

Posts | Tags | About

Last build : Jul 2026