Sanoid 2.3.0
Overview
Sanoid is an open source, policy-driven snapshot manager for ZFS. You define how many hourly, daily and monthly snapshots to keep. Sanoid takes them on schedule and prunes the ones past the policy. Its companion syncoid replicates datasets incrementally to another pool or another machine with zfs send and zfs receive over SSH.
This guide covers the Sanoid 2.3.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud backup and recovery listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
What you get:
- A ZFS pool on its own volume. First boot creates the pool
tank(lz4 compression,autoexpand=on) on a separate 20 GiB gp3 EBS volume that you can grow on its own.tank/datais mounted at/tank/datafor your files. - A snapshot policy that is already running.
sanoid.timerruns Sanoid every 15 minutes. The shipped policy keeps 36 hourly, 30 daily and 3 monthly snapshots oftank/dataand of every dataset you create under it, and prunes older ones automatically. Snapshots you take by hand are never pruned. - Replication with a key of its own. First boot generates an SSH key for syncoid that is unique to the instance. It writes the key's fingerprint and public half to a root-only note, so you can trust the key on your backup host with one line.
- Locked down. Sanoid, the prune job and the first-boot service run with all network access denied, including the instance metadata service, and the AMI requires IMDSv2. Nothing listens on the network except SSH.
- Upstream, unmodified. Sanoid 2.3.0 is installed from the upstream release (pinned by SHA-256) in the upstream Debian package layout, on fully patched Ubuntu 24.04 LTS with unattended security upgrades switched on. OpenZFS comes from Ubuntu's own packages.
What is on the image: Sanoid and syncoid 2.3.0 (/usr/sbin, licence, source and build provenance in /usr/share/doc/sanoid), OpenZFS (zfsutils-linux and the kernel's ZFS module), pv and lzop for syncoid, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet.
- A security group that allows TCP 22 (SSH) from your own networks only.
- For replication, a backup host with OpenZFS that this instance can reach over SSH. Another instance of this image works.
pv,lzopandmbufferon the backup host are optional, and syncoid uses them when they are present.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Sanoid 2.3.0 on Ubuntu 24.04 as the software version, pick your Region, and choose Launch through EC2.
Choose m5.large, your key pair and a security group as described above. The AMI attaches the 20 GiB data volume for the pool automatically. To fit your data, increase its size in the launch wizard's storage section. First boot creates the pool on that volume.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=sanoid}]'Connect to your instance
Version SSH user
Sanoid 2.3.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>Check the timer and the pool
First boot sets the ZFS host id, creates the pool, generates the syncoid key and starts the first Sanoid run. The timer then runs Sanoid every 15 minutes. Check the timer:
systemctl is-active sanoid.timer
sudo test -f /var/lib/sac/data/sanoid-firstboot.done && echo FIRSTBOOT_DONE
systemctl list-timers sanoid.timer --no-legend | awk '{print "next run:", $1, $2, $3, $4, " unit:", $NF}'active FIRSTBOOT_DONE next run: Sun 2026-11-08 12:45:00 UTC unit: sanoid.serviceThe pool and its dataset:
sudo zpool list -o name,size,health tank
sudo zfs list -o name,used,avail,mountpoint -r tankNAME SIZE HEALTH tank 19.5G ONLINE NAME USED AVAIL MOUNTPOINT tank 564K 18.9G /tank tank/data 96K 18.9G /tank/dataOnly SSH listens on the network:
sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/pid=[0-9]+,fd=[0-9]+/.../g' | sort0.0.0.0:22 users:(("sshd",...),("systemd",...)) 127.0.0.53%lo:53 users:(("systemd-resolve",...)) 127.0.0.54:53 users:(("systemd-resolve",...)) [::]:22 users:(("sshd",...),("systemd",...))Read the instance details
First boot writes the pool, the policy file, and the syncoid key's fingerprint and public half to the root-only file /root/sanoid-credentials.txt. The note holds no secret itself, but the private key it points to is a secret:
sudo cat /root/sanoid-credentials.txt# Sanoid 2.3.0 - generated on first boot by sanoid-firstboot.service
# The syncoid SSH key below is unique to this instance; none was baked into the image. Its private half
# (/root/.ssh/syncoid_ed25519, 0600 root) is what lets this instance replicate to a host that trusts the public half. Keep it secret.
# SANOID_POOL=tank SANOID_DATASET=tank/data SANOID_MOUNTPOINT=/tank/data SANOID_DATA_DISK=/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol04116e20e536495a5 SANOID_POLICY=/etc/sanoid/sanoid.conf SYNCOID_SSH_KEY=/root/.ssh/syncoid_ed25519 SYNCOID_KEY_FINGERPRINT=<SYNCOID_KEY_FINGERPRINT> SYNCOID_PUBLIC_KEY=ssh-ed25519 <this instance's public key> syncoid@<instance-id> # # Snapshots: zfs list -t snapshot -r tank/data Policy check:
sudo sanoid --monitor-snapshots
# Self test:
sudo sanoid-selftest Timer: systemctl list-timers sanoid.timer
# Replicate:
sudo syncoid --sshkey /root/.ssh/syncoid_ed25519 -r tank/data root@<backup-host>:<pool>/<dataset>
# (first add the SYNCOID_PUBLIC_KEY line above to /root/.ssh/authorized_keys on <backup-host>)
# User guide: https://secureanycloud.com/stacks/sanoid-2-3-0/Your instance shows its own volume, fingerprint and public key. Each value has a purpose:
Value Used for
SANOID_DATASET, SANOID_MOUNTPOINT Where your data goes; everything under it is snapshotted on the policy
SANOID_DATA_DISK The EBS volume that holds the pool (Step 10)
SANOID_POLICY The snapshot policy Sanoid follows (Step 7)
SYNCOID_SSH_KEY The private key syncoid uses to reach your backup host (Step 8)
SYNCOID_KEY_FINGERPRINT, SYNCOID_PUBLIC_KEY The key's SHA-256 fingerprint, and the line to add to the backup host's authorized_keys
Run the self test
sanoid-selftest checks the whole snapshot, replicate and prune cycle without touching your data or your policy. It works in a private temporary directory and takes about 70 seconds.
The test creates two small throwaway pools and has Sanoid take a snapshot. It has syncoid replicate the snapshot (a full send, then an incremental one a minute later) and checks each copy's ZFS guid. It then has Sanoid prune past its policy and checks that a hand-made snapshot survives. Finally, it removes everything it made:
sudo sanoid-selftest 2>&1 | tail -5sanoid took sacsanoidst/data@autosnap_2026-11-08_12:30:05_frequently syncoid replicated autosnap_2026-11-08_12:30:05_frequently to sacsyncoidst/copy (full; guid 4635368680420080423 matches) syncoid replicated autosnap_2026-11-08_12:31:08_frequently to sacsyncoidst/copy (incremental; guid 8710735610719297007 matches) sanoid pruned autosnap_2026-11-08_12:30:05_frequently (past the policy) and kept autosnap_2026-11-08_12:31:08_frequently and the manual snapshot SANOID_SELFTEST_OKSnapshots of tank/data
Sanoid names its snapshots autosnap_<UTC date>_<time>_<hourly|daily|monthly>. The first run takes one of each:
sudo zfs list -H -t snapshot -o name -r tank/datatank/data@autosnap_2026-11-08_12:26:49_monthly tank/data@autosnap_2026-11-08_12:26:49_daily tank/data@autosnap_2026-11-08_12:26:49_hourlySanoid can check that the snapshots are fresh and the pool is healthy, in a form that Nagios-style monitoring understands. The exit code is 0 when everything is OK:
sudo sanoid --monitor-snapshots sudo sanoid --monitor-healthOK: all monitored datasets (tank/data) have fresh snapshots OK ZPOOL tank : ONLINE {Size:19.5G Free:19.5G Cap:0%}Every snapshot is browsable, read-only, under .zfs/snapshot. The following commands write a file, take a snapshot by hand, delete the file and copy it back from the snapshot:
echo "notes from sac" | sudo tee /tank/data/notes.txt >/dev/null
sudo zfs snapshot tank/data@before-change
sudo rm /tank/data/notes.txt
sudo cat /tank/data/.zfs/snapshot/before-change/notes.txt
sudo cp /tank/data/.zfs/snapshot/before-change/notes.txt /tank/data/ ls /tank/datanotes from sac notes.txtfindoid lists every version of a file that the snapshots hold, newest last:
sudo findoid /tank/data/notes.txtSun Nov 8 12:31:09 2026 20 Bytes /tank/data/.zfs/snapshot/before-change/notes.txt Sun Nov 8 12:31:09 2026 20 Bytes /tank/data/notes.txtA snapshot you take by hand stays until you destroy it:
sudo zfs destroy tank/data@before-changeTo roll a whole dataset back to a snapshot, run sudo zfs rollback -r tank/data@<snapshot>. This discards everything written since that snapshot.
Change the policy
The policy file is /etc/sanoid/sanoid.conf. The image ships this policy:
grep -vE '^[[:space:]]*(#|$)' /etc/sanoid/sanoid.conf[tank/data] use_template = production recursive = yes [template_production] frequently = 0 hourly = 36 daily = 30 monthly = 3 yearly = 0 autosnap = yes autoprune = yes- Keep more or fewer snapshots: change the counts in
template_production. Setfrequentlyabove 0 to take snapshots every 15 minutes. - Protect more data: create datasets under
tank/data(sudo zfs create tank/data/projects).recursive = yesputs every child on the same policy. - Use a different policy for one dataset: add a section for it, for example
[tank/data/scratch]with its ownuse_templateor counts.
After an edit (for example with sudo nano /etc/sanoid/sanoid.conf), check the policy without changing anything. Then run Sanoid once now instead of waiting for the timer:
sudo sanoid --configdir /etc/sanoid --readonly --verbose --take-snapshots sudo systemctl start sanoid.serviceThe built-in defaults are in /etc/sanoid/sanoid.defaults.conf. Do not edit that file. The annotated upstream example is /usr/share/doc/sanoid/sanoid.conf.example.
Replicate to a backup host with syncoid
syncoid sends tank/data and its snapshots to a dataset on another machine, incrementally after the first run. First, trust this instance's syncoid key on the backup host. Print the public key on this instance:
sudo sed -n 's/^SYNCOID_PUBLIC_KEY=//p' /root/sanoid-credentials.txtOn the backup host, as root, append that line to root's authorized_keys. The backup host needs OpenZFS installed, as in Prerequisites:
# on the backup host, as root (not on this instance at <public-ip>)
echo '<the SYNCOID_PUBLIC_KEY line>' >> /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keysThen, on this instance, replicate. The first run is a full send, and later runs send only the changes since the last common snapshot:
# on this instance (<public-ip>), with your own backup host, pool and dataset
sudo syncoid --sshkey /root/.ssh/syncoid_ed25519 -r tank/data root@<backup-host>:<pool>/<dataset>The first connection asks you to accept the backup host's SSH host key. Check it against the host's own fingerprint.
To replicate on a schedule, run the same command from a systemd timer or cron job on this instance.
If you prefer not to allow root logins on the backup host, use a normal user there with ZFS permissions delegated (zfs allow) and add --no-privilege-elevation. The syncoid documentation covers the options, including pulling from this instance instead of pushing.
On the backup host, let Sanoid prune the replicas too. Give their datasets a policy with autosnap = no and autoprune = yes, so the backup host keeps its own retention without taking snapshots of its own.
Restore from the backup host by replicating the other way, from the backup host to this instance or a new one:
# on the restore target (this instance at <public-ip>, or a new one), with your own backup host, pool and dataset
sudo syncoid --sshkey /root/.ssh/syncoid_ed25519 root@<backup-host>:<pool>/<dataset> tank/restoredCheck the security posture
Sanoid, the prune job and the first-boot service run with all network access denied. Each unit's IPAddressDeny covers every IPv4 and IPv6 address, so none of them can reach the instance metadata service or anything else:
systemctl show sanoid.service -p IPAddressDeny --value | tr ' ' '\n' | grep -xE '0\.0\.0\.0/0|::/0' | sort0.0.0.0/0 ::/0They do not need network access: Sanoid only talks to the local ZFS pool. syncoid runs from your own shell or timer, so it can reach your backup host.
The syncoid private key and the note are root-only, and the key's fingerprint matches SYNCOID_KEY_FINGERPRINT in the note:
sudo stat -c '%a %U %n' /root/.ssh/syncoid_ed25519 /root/.ssh/syncoid_ed25519.pub /root/sanoid-credentials.txt
sudo ssh-keygen -lf /root/.ssh/syncoid_ed25519.pub | awk '{print $2}'600 root /root/.ssh/syncoid_ed25519 644 root /root/.ssh/syncoid_ed25519.pub 600 root /root/sanoid-credentials.txt <SYNCOID_KEY_FINGERPRINT>Sanoid runs as root because taking and destroying snapshots requires it.
Grow the pool
Increase the data volume's size in the EC2 console (Volumes, Modify volume) or with aws ec2 modify-volume. Wait until its state is optimizing or completed, then let the pool use the new space:
D=$(sudo sed -n 's/^SANOID_DATA_DISK=//p' /root/sanoid-credentials.txt)
sudo zpool online -e tank "$D"
sudo zpool list -o name,size tankThe pool also has autoexpand=on, so a reboot picks up a larger volume on its own.
Snapshots live in the same pool as the data, so they do not protect against losing the volume. Replicate with syncoid (Step 8), or take EBS snapshots of the data volume as well.
Server components
Component Path
Sanoid, syncoid, findoid /usr/sbin/sanoid, /usr/sbin/syncoid, /usr/sbin/findoid
Licence, source and build provenance /usr/share/doc/sanoid/
Policy /etc/sanoid/sanoid.conf (defaults in /etc/sanoid/sanoid.defaults.conf)
Pool tank on the data volume, tank/data at /tank/data
Service units sanoid.timer, sanoid.service, sanoid-prune.service (upstream), sanoid-firstboot.service, with SecureAnyCloud drop-ins in /etc/systemd/system/<unit>.d/
Helpers /usr/local/sbin/sanoid-selftest, /usr/local/sbin/sanoid-find-data-disk
syncoid key /root/.ssh/syncoid_ed25519 (mode 0600)
Instance details /root/sanoid-credentials.txt (mode 0600)
sanoid --version 2>&1 | head -1 syncoid --version 2>&1 | head -1 grep -E '^(source|LICENSE)' /usr/share/doc/sanoid/BUILD-PROVENANCE.txt | cut -c1-110 zfs version/usr/sbin/sanoid version 2.3.0 /usr/sbin/syncoid version 2.3.0 source: https://github.com/jimsalterjrs/sanoid/archive/refs/tags/v2.3.0.tar.gz (tag commit 8d4abf14b2bf8d6b9 LICENSE: GPL-3.0 (sha256 e1c0ad728983d8a57335e52cf1064f1affd1d454173d8cebd3ed8b4a72b48704) zfs-2.2.2-0ubuntu9.5 zfs-kmod-2.4.1-1ubuntu5.1Logs: sudo journalctl -u sanoid -u sanoid-prune.
Security recommendations
- Allow port 22 in the security group from your own networks only.
- Treat
/root/.ssh/syncoid_ed25519as a credential. On the backup host, restrict the key's line inauthorized_keys(for example withfrom="<this instance's address>"), or use a delegated non-root user. - Keep a copy of your data off the instance. Replicate with syncoid to a backup host, or take EBS snapshots of the data volume.
- Run
sudo sanoid --monitor-snapshotsfrom your monitoring, and runsudo sanoid-selftestafter configuration changes. - Keep the operating system patched. Unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else.
Support and licensing
Sanoid is open source under the GPL-3.0 licence, and OpenZFS is open source under the CDDL. There is no per-user or per-CPU licence fee. The Sanoid licence, the upstream release tarball (the corresponding source) and a written source offer are in /usr/share/doc/sanoid/.
SecureAnyCloud is not affiliated with or endorsed by the Sanoid project or OpenZFS. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24 hour response SLA
Deploy on AWS
Launch Sanoid 2.3.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com