
A short follow-up to the RAIDZ1 NAS build. The cheatsheet in that post gave you zpool scrub storage as a command you can run. What it didn’t answer is whether anything runs it for you — and, since a scrub is only useful if you actually find out when it turns something up, whether the system tells you when it does. Both questions turned out to have more interesting answers than “yes, obviously.”
🔍 Scrub and trim, in one paragraph each
zpool scrub reads every allocated block in the pool and checks it against its stored checksum. A mismatch — a bit that quietly flipped somewhere on the physical media — gets reconstructed from RAIDZ parity and rewritten automatically. This is the entire mechanism that makes ZFS immune to the silent bit-rot problem the original post spent several paragraphs on: it’s not that corruption can’t happen, it’s that scrub is what turns “corruption nobody will ever notice” into “corruption that gets fixed before anyone notices.”
zpool trim is SSD-specific. It tells the drive’s firmware which blocks ZFS has stopped using (deleted files, overwritten data), so the SSD can erase and prepare that space ahead of time instead of scrambling to do it mid-write. Skip it long enough and write performance degrades quietly as the drive runs out of pre-erased blocks — the SSD equivalent of never taking out the trash and wondering why the kitchen got slower to work in.
Neither of these is useful as a one-off. The whole point is that they run on a schedule.
📅 Who’s actually pulling the trigger
I went looking for the answer instead of assuming it, and it’s a good thing I did — the obvious place to look was wrong.
OpenZFS on Debian ships two scheduling mechanisms side by side, and only one of them was doing anything:
$ systemctl list-unit-files | grep zfs-scrub
zfs-scrub-monthly@.timer disabled enabled
zfs-scrub-weekly@.timer disabled enabledBoth disabled. If I’d stopped here, the honest answer would have been “no, nothing runs it.” But zfsutils-linux also drops a plain cron file, independent of those timers, and that one is live:
# /etc/cron.d/zfsutils-linux
# TRIM the first Sunday of every month.
24 0 1-7 * * root if [ $(date +\%w) -eq 0 ] && [ -x /usr/lib/zfs-linux/trim ]; then /usr/lib/zfs-linux/trim; fi
# Scrub the second Sunday of every month.
24 0 8-14 * * root if [ $(date +\%w) -eq 0 ] && [ -x /usr/lib/zfs-linux/scrub ]; then /usr/lib/zfs-linux/scrub; fiTrim on the first Sunday, scrub on the second, both at 00:24. The /usr/lib/zfs-linux/scrub script it calls isn’t a blind zpool scrub $everything either — it walks every pool, checks its health is ONLINE, and reads a user-defined property called org.debian:periodic-scrub per pool to decide whether to include it:
$ zfs get -H -o value org.debian:periodic-scrub storage
-- means unset, which the script treats as auto — scrub goes ahead. Setting it to disable on a given pool would opt that pool out without touching the cron job itself, which is a tidier knob than editing the script. Point being: two scheduling systems are installed, only the older cron-based one is switched on, and the actual per-pool decision is a property, not a hardcoded pool name. None of that is discoverable from zpool status — you have to go check systemctl, /etc/cron.d, and the property directly, which is exactly what I ended up doing after being asked the very reasonable question “is this actually automated, or did you just show me the manual command?”
Monthly is also the correct interval to want here, for what it’s worth — these are SSDs, and a weekly scrub cadence is more of an HDD-era default that mostly just adds wear for no extra benefit on flash.
🔔 Does anything warn you when scrub finds a problem?
This is the part that actually surprised me. The infrastructure to warn you exists, is installed, and is running:
$ systemctl status zfs-zed
● zfs-zed.service - ZFS Event Daemon (zed)
Active: active (running)ZED (the ZFS Event Daemon) watches for pool events — scrub finishing, a resilver completing, a device changing state — and its config even names a recipient:
# /etc/zfs/zed.d/zed.rc
ZED_EMAIL_ADDR="root"
ZED_NOTIFY_INTERVAL_SECS=3600Looks configured. It is configured — as far as ZED itself is concerned. But the actual delivery mechanism, buried in zed-functions.sh, is unglamorous:
: "${ZED_EMAIL_PROG:="mail"}"zed_notify_email shells out to a binary literally called mail. And on this box:
$ which mail mailx msmtp
$Nothing. No mailutils, no msmtp, no MTA of any kind installed. zpool create never pulls one in as a dependency, so unless you separately think to install mail delivery, ZED has a recipient and no way to reach them. The scrub_finish-notify.sh zedlet would fire correctly on the next scrub, build its message, hand it to zed_notify_email, which would try to run mail, get “command not found,” and fail — silently, from the perspective of anyone watching for actual mail to show up. journalctl -u zed will have the failure in it if you go looking. Nothing will surface it if you don’t.
I want to sit with that for a second, because it’s a genuinely good punchline: the entire first post was about a filesystem that refuses to let corruption go unnoticed — checksums instead of blind trust, self-healing instead of silent failure. And the layer sitting on top of it, whose only job is to tell a human when that machinery does its job, was itself failing in exactly the silent way ZFS was built to prevent. The lesson from the last post generalizes further than I expected: “silently OK-looking” is a property of infrastructure, not just of disks.
🛠️ Fixing it, for real
The gap is just a missing MTA, not a ZED misconfiguration, so the fix is genuinely three steps: install a mail client, point it at a relay, tell ZED who to email.
apt-get install -y msmtp msmtp-mta bsd-mailxmsmtp-mta symlinks msmtp in as /usr/sbin/sendmail, which is exactly what mail/mailx — and therefore ZED — shell out to under the hood. This homelab already has a working outbound relay (Strato, used elsewhere for other host notifications), so I pointed /etc/msmtprc at the same account instead of standing up something new:
# /etc/msmtprc — root-owned, chmod 600 (it holds a password)
defaults
auth on
tls on
tls_starttls on
logfile /var/log/msmtp.log
account homelab
host smtp.strato.de
port 587
from homelab@apt-upgrade.me
user homelab@apt-upgrade.me
password ██████████████████
account default : homelabchown root:root /etc/msmtprc && chmod 600 /etc/msmtprc right after — that file has a plaintext password in it, and zfs-zed.service runs as root anyway, so root-only is both correct and sufficient. Same treatment for the log file.
Then the actual point of this whole exercise — telling ZED to stop addressing mail to a local root account nothing will ever read, and send it somewhere I’ll actually see it:
sed -i 's/^ZED_EMAIL_ADDR=.*/ZED_EMAIL_ADDR="raphael.muench@proton.me"/' /etc/zfs/zed.d/zed.rc
systemctl restart zfs-zedNo aliases, no local mail spool, no forwarding rule to keep track of — ZED_EMAIL_ADDR is the literal recipient mail gets invoked with, so pointing it straight at a real inbox is simpler than making local delivery work first and layering forwarding on top.
✅ Confirmed working
Rather than wait for the second Sunday of the month to find out whether any of this actually works, I sent a manual test through the exact same path ZED uses — mail, which resolves to msmtp, which authenticates to Strato:
$ echo "This is a test message from the storage NAS (192.168.10.50) confirming that
msmtp + ZED mail delivery is now working via the Strato relay." | \
mail -s "ZFS ZED delivery check - storage NAS" raphael.muench@proton.me
$ tail -1 /var/log/msmtp.log
host=smtp.strato.de tls=on auth=on user=homelab@apt-upgrade.me from=homelab@apt-upgrade.me
recipients=raphael.muench@proton.me mailsize=436 smtpstatus=250
smtpmsg='250 2.0.0 OK queued with id 990330282JJ9ENX' exitcode=EX_OKsmtpstatus=250 / exitcode=EX_OK means Strato accepted the message for delivery — that’s msmtp’s side of the story. The inbox side confirmed it a few seconds later:
From: homelab@apt-upgrade.me
To: raphael.muench@proton.me
Subject: ZFS ZED delivery check - storage NAS
Date: Wed, 2 Sep 2026 21:19
This is a test message from the storage NAS (192.168.10.50) confirming that
msmtp + ZED mail delivery is now working via the Strato relay.End to end, relay to inbox, in about the time it took to read the log line. That’s the whole fix verified — not “should work now,” but an actual email that actually arrived.
Small thing, but worth saying plainly: the fix here isn’t really “install msmtp.” It’s noticing that a component whose entire job is raising the alarm can fail exactly as silently as the thing it’s supposed to be watching for — and that the only way to know it actually works is to trigger it and check, not to read the config and assume.






