Prometheus

Time Machine + TrueNAS — Setup Guide

Step-by-step guide to setting up Time Machine backups over SMB to a self-hosted TrueNAS box, with a monthly LaunchDaemon schedule and helper commands.

Full setup guide — dataset creation, SMB share, macOS destination, monthly schedule, and how to adapt every value for your own setup.

This setup replaces a physical Time Machine drive with an SMB share on a self-hosted TrueNAS server on your local network. You get the same hourly-style backups Apple ships, but to a box you own — and the dotfiles override the default hourly schedule with a single backup on the 1st of each month at 03:00 to keep NAS storage usage low.

Prerequisites

ThingNotes
TrueNAS box running

SCALE or CORE, web UI reachable. This guide uses SCALE labels; CORE differs slightly.

A storage pool

With enough free space for ~2-3x your Mac’s data size. Mac uses 290 GB → plan for 600 GB-1 TB.

A TrueNAS user

Created under Credentials → Local Users. Needs read/write on the dataset you’ll create.

Network reachability

Mac and TrueNAS on the same LAN. Wired Ethernet strongly recommended for the first backup.

macOS 13+ (Ventura or newer)Older versions work but the System Settings paths differ.

Setting up a TrueNAS server itself is out of scope. If you don’t have one yet, the official TrueNAS docs are the place to start.

Part 1 — TrueNAS: create the dataset

Time Machine writes to a sparse-bundle disk image inside the share. A dedicated dataset gives you per-feature quotas and snapshot policies separate from your other data.

1.1 — Open the dataset tree

Web UI → Datasets in the left sidebar. Click your pool (e.g. media) to expand it.

1.2 — Add a child dataset

Select the pool, click Add Dataset. Fill the form:

FieldValue
Nametimemachine

Dataset Preset / Share Type

SMB — this sets sensible permissions for SMB clients

Advanced Options → Quota for this dataset

250 GiB (or your chosen ceiling — see customization)

Click Save. The new dataset appears under your pool with path /mnt/<pool>/timemachine.

1.3 — Why the quota matters

Time Machine grows its sparse bundle until it fills the available space. Without a quota, it will eat your entire pool — starving every other share. The quota is a hard ceiling; once hit, TM rotates and deletes its oldest backups automatically.

Part 2 — TrueNAS: create the SMB share

Apple removed NFS as a Time Machine destination years ago — SMB is the only network protocol that works. The share must have the Time Machine purpose flag enabled, or macOS will refuse it as a destination.

2.1 — Add the share

Web UI → SharesWindows (SMB) Shares card → Add.

2.2 — Fill the basic fields

FieldValue
Path/mnt/<pool>/timemachine — the dataset from Part 1
Nametimemachine — this is the SMB share name clients use
Enabledchecked
Purpose (critical)Time Machine Share (or Multi-user Time Machine on newer versions)

2.3 — Save and restart SMB

Click Save. TrueNAS prompts to restart the SMB service so the new advertisement is picked up by Bonjour. Click Yes.

2.4 — What the Purpose flag actually does

Setting Purpose to Time Machine Share tells Samba to enable the vfs_fruit module (Apple SMB extensions) and advertise the share via Bonjour with the _adisk._tcp service record. Without it, the share is just a plain SMB folder — usable for storage but invisible to System Settings → Time Machine.

Part 3 — macOS: mount and test write

Mount the share once via Finder so macOS caches the credentials in your login Keychain. This avoids password prompts later.

3.1 — Connect via Finder

In Finder, press ⌘K and enter the SMB URL:

Connect to Server URL
smb://&lt;NAS-IP&gt;/timemachine

When prompted, choose Registered User, enter your TrueNAS username + password, and tick Remember this password in my keychain. The share mounts at /Volumes/timemachine.

3.2 — Verify it’s writable

Write test from terminal
touch /Volumes/timemachine/.write-test && \
rm /Volumes/timemachine/.write-test && \
echo writable

If you don’t see writable, fix the TrueNAS permissions before proceeding (Datasets → your dataset → Edit Permissions → grant your user write access).

Part 4 — macOS: register as Time Machine destination

tmutil setdestination tells Time Machine where to back up. It does not read from your login Keychain — you must embed credentials in the URL once. After this command, macOS stores them in the System Keychain and you never need them again.

4.1 — URL-encode the password

If your password contains any of these characters, replace them before embedding in the URL:

CharacterReplace with
@%40
:%3A
/%2F
#%23
?%3F
%%25
space%20

Example: password 19March1999@ becomes 19March1999%40.

4.2 — Register the destination

tmutil setdestination
sudo tmutil setdestination -a "smb://USERNAME:URLENCODEDPASSWORD@TRUENAS_IP/timemachine"

-a means add (vs replacing existing destinations).

4.3 — Enable and verify

Enable + check
sudo tmutil enable
tmutil destinationinfo

destinationinfo should print the destination Name, Kind (Network), URL, and a UUID.

Part 5 — Exclusions

By default Time Machine backs up everything. For a developer machine, that means hauling tens of GB of node_modules, build caches, and emulator disk images over the network — every backup. tmutil addexclusion -p permanently excludes a path (the -p flag pins the rule to the path string, so it applies even if the directory is deleted and recreated).

5.1 — Apply the standard exclusions

Run once after registering destination
{`for p in \\
~/Code/*/node_modules \\
~/Code/*/.next \\
~/Code/*/dist \\
~/Code/*/build \\
~/Code/*/.turbo \\
~/.bun/install/cache \\
~/.gradle/caches \\
~/.android/avd \\
~/.t3/worktrees \\
~/.cache \\
~/.npm \\
~/.nvm/.cache \\
~/Library/Caches \\
~/Library/Developer/Xcode/DerivedData \\
~/Library/Developer/CoreSimulator/Caches \\
/Volumes/media; do
[[ -e "$p" ]] && sudo tmutil addexclusion -p "$p"
done`}

5.2 — Why each one

PathWhy excluded
~/Code/*/node_modulesRegenerable from bun.lock / package-lock.json
~/Code/*/{.next,dist,build,.turbo}Build output — rebuildable
~/.bun/install/cachePackage manager cache — re-downloadable
~/.gradle/cachesGradle artifacts — rebuildable
~/.android/avdEmulator disk images, often tens of GB
~/.t3/worktreesGit worktrees — clones of repos already on disk
~/.cache · ~/.npm · ~/.nvm/.cacheTool caches
~/Library/CachesApp caches — every app refills these
~/Library/Developer/Xcode/DerivedDataXcode intermediate build products
~/Library/Developer/CoreSimulator/CachesiOS Simulator caches
/Volumes/mediaDon’t back up the NAS to itself!

5.3 — Verifying and managing exclusions

Check if a path is excluded
tmutil isexcluded ~/Code/somerepo/node_modules
Remove an exclusion
sudo tmutil removeexclusion -p ~/some/path

Part 6 — Monthly LaunchDaemon

Apple’s default TM schedule is one backup per hour. Over a network link to a NAS with finite storage, that’s wasteful and noisy. The dotfiles ship a LaunchDaemon that fires once a month, on the 1st at 03:00, and we disable Apple’s hourly schedule via tmutil disable.

6.1 — The plist

Lives at dotfiles/launchd/com.prometheus.tm-monthly.plist. The key parts:

Schedule fragment
<key>StartCalendarInterval</key>
<dict>
<key>Day</key>    <integer>1</integer>
<key>Hour</key>   <integer>3</integer>
<key>Minute</key> <integer>0</integer>
</dict>

The plist runs /usr/bin/tmutil startbackup --auto. Installed as a LaunchDaemon (not LaunchAgent), so it runs as root — no sudo prompt at fire time.

6.2 — Install it

install.sh handles this — manual version
{`sudo cp ~/dotfiles/launchd/com.prometheus.tm-monthly.plist /Library/LaunchDaemons/
sudo chown root:wheel /Library/LaunchDaemons/com.prometheus.tm-monthly.plist
sudo chmod 644       /Library/LaunchDaemons/com.prometheus.tm-monthly.plist
sudo launchctl load  /Library/LaunchDaemons/com.prometheus.tm-monthly.plist
sudo tmutil disable    # kill the hourly schedule`}

6.3 — Verify

Check it's registered
sudo launchctl list | grep tm-monthly

A line like - 0 com.prometheus.tm-monthly means loaded and waiting. The middle column is the exit code of the last run (- = never run yet).

6.4 — Test fire (without waiting for the 1st)

Fire immediately
sudo launchctl start com.prometheus.tm-monthly

Part 7 — Helper commands

Two thin wrappers over tmutil. Both live in dotfiles/bin/ and get symlinked to ~/.local/bin/ by install.sh.

tm-status

CommandWhat it does
tm-statusOne-shot pretty status — phase, percent, bytes copied, file count
tm-status --watchLive updates every 5 seconds (Ctrl+C to exit)
tm-status --jsonRaw tmutil status, for scripts

tm-backup

CommandWhat it does
tm-backupTrigger a backup if one isn’t already running, then print status
tm-backup --watchTrigger, then live-update progress until done
tm-backup --stopStop the running backup

Use these between monthly fires — before a system update, before unplugging from the home network, after a big batch of work.

Customizing for your setup

Every value in this guide is replaceable. Here’s the map.

ValueReference setupWhere to change it
TrueNAS IP / hostname<NAS-IP>In ~/.config/dotfiles/local.env (NAS_HOST). install.sh picks it up for both the .inetloc auto-mount and the tmutil setdestination URL.
TrueNAS username<USER>In the SMB URLs everywhere (smb://USER@...)
Pool namemediaTrueNAS dataset path becomes /mnt/<your-pool>/timemachine
Dataset / SMB share nametimemachineWherever timemachine appears — in URLs, mount points, and the helper script paths. Just stay consistent.
Quota250 GiBDatasets → Edit → Advanced Options → Quota for this dataset. Rule of thumb: 2-3x your Mac’s data size.
Backup scheduleMonthly, 1st at 03:00Edit StartCalendarInterval in the plist (see below)
Exclusionsnode_modules, build caches, AVDs, …Whatever paths you don’t want backed up — add/remove with tmutil addexclusion -p / removeexclusion -p

Changing the schedule

The plist’s StartCalendarInterval dict is just cron-like keys. Examples:

Daily at 03:00
{`<dict>
<key>Hour</key>   <integer>3</integer>
<key>Minute</key> <integer>0</integer>
</dict>`}
Weekly (Sunday 03:00)
{`<dict>
<key>Weekday</key> <integer>0</integer>
<key>Hour</key>    <integer>3</integer>
<key>Minute</key>  <integer>0</integer>
</dict>`}
Twice a month (1st & 15th)
{`<array>
<dict><key>Day</key><integer>1</integer>
     <key>Hour</key><integer>3</integer></dict>
<dict><key>Day</key><integer>15</integer>
     <key>Hour</key><integer>3</integer></dict>
</array>`}
Back to Apple's hourly default
sudo tmutil enable

After editing the plist, re-install: sudo launchctl unload <path> then re-copy and load. Or just run install.sh again — it does this idempotently.

Troubleshooting

“Backup destination doesn’t support Time Machine”

The SMB share is missing the Time Machine purpose flag on the TrueNAS side. Go to Shares → SMB Shares → Edit on the timemachine share and set Purpose = Time Machine Share. Save, restart SMB. Then re-run tmutil setdestination on the Mac.

“No password specified” from tmutil setdestination

tmutil doesn’t read from your login Keychain — you must embed the password in the URL: smb://user:password@host/share. URL-encode any special characters (see 4.1).

Backup stuck on “Preparing Backup…” forever

Usually Spotlight is indexing the source volume. On the Mac: sudo mdutil -i on / then wait a few minutes. If it persists, sudo tmutil stopbackup && sudo tmutil startbackup --auto.

Backups are painfully slow

Almost always a network issue. Diagnose: 1. system_profiler SPAirPortDataType — if you see PHY Mode: 802.11n / Channel: 2GHz you’re on slow Wi-Fi. Switch to a 5/6 GHz SSID or plug in Ethernet. 2. dd if=/dev/zero of=/Volumes/timemachine/.t bs=1m count=200 — measures real write speed. Under 5 MB/s on local Ethernet → check the NAS pool (HDD pools writing TM sparse bundles are slow), check the Dell/NAS NIC speed, and check if SMB encryption is forced on the share. 3. First backup is always slow because it’s a full copy. Incrementals are minutes.

LaunchDaemon never fires

Check: sudo launchctl list | grep tm-monthly — middle column is exit code. tail /var/log/tm-monthly.log shows stderr. Mac must be awake at the fire time; if asleep, launchd skips the missed run unless you also set <key>StartOnMount</key> or related keys. Solution: just run tm-backup manually if you missed a month.

Mount works in Finder but not from tmutil

Different credential storage: Finder uses the login Keychain, tmutil uses the System Keychain. After running tmutil setdestination with the URL-embedded password once, the System Keychain has the entry and everything just works thereafter.

Tearing it down

If you want to remove this setup entirely:

Reverse everything
# 1. Stop and remove the LaunchDaemon
sudo launchctl unload /Library/LaunchDaemons/com.prometheus.tm-monthly.plist
sudo rm /Library/LaunchDaemons/com.prometheus.tm-monthly.plist

# 2. (Optional) restore Apple's hourly schedule

sudo tmutil enable

# 3. Remove the destination

sudo tmutil removedestination <UUID-from-destinationinfo>

# 4. Remove exclusions (per path)

sudo tmutil removeexclusion -p ~/Code/somerepo/node_modules

# 5. Unmount the share

diskutil unmount /Volumes/timemachine

# 6. Delete the dataset on TrueNAS (web UI) — destroys all backups
On this page
Edit this page on GitHub Last updated

Built by rafay99-epic · MIT License

Tokyo Night · macOS · SketchyBar · AeroSpace · Ghostty · Zsh · Fish