Sanoid 2.3.0 on AWS User Guide
Overview
This guide covers launching and using the Sanoid 2.3.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg backup and recovery listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Sanoid is an open source policy driven snapshot manager for ZFS. You say 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 to another pool or another machine incrementally with zfs send and zfs receive over SSH.
The cloudimg image is ready to protect your data from the first minute:
- 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, withtank/datamounted 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, and writes its fingerprint and public half to a root only note, so you can trust it on your backup host in 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; syncoid uses them when they are there.
Step 1: 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 and your Region, then Launch through EC2. Choose m5.large, your key pair, and a security group as above. The AMI attaches the 20 GiB data volume for the pool automatically; increase its size in the launch wizard's storage section to fit your data. The pool is created on that volume on first boot.
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}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Sanoid 2.3.0 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the timer and the pool
First boot sets the ZFS host id, creates the pool, generates the syncoid key and then starts the first Sanoid run. The timer runs it every 15 minutes from then on:
systemctl is-active sanoid.timer
sudo test -f /var/lib/cloudimg/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.service
The pool and its dataset:
sudo zpool list -o name,size,health tank
sudo zfs list -o name,used,avail,mountpoint -r tank
NAME SIZE HEALTH
tank 19.5G ONLINE
NAME USED AVAIL MOUNTPOINT
tank 564K 18.9G /tank
tank/data 96K 18.9G /tank/data
Only SSH listens on the network:
sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/pid=[0-9]+,fd=[0-9]+/.../g' | sort
0.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",...))
Step 4: Read the instance details
First boot writes the pool, the policy file and the syncoid key's fingerprint and public half to /root/sanoid-credentials.txt (root only). The note holds no secret itself; the private key it points to does:
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://www.cloudimg.co.uk/guides/sanoid-2-3-0-on-ubuntu-24-04-aws/
Your instance shows its own volume, fingerprint and public key. What each value is for:
| 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 |
Step 5: Run the self test
sanoid-selftest checks the whole snapshot, replicate and prune cycle without touching your data or your policy. In a private temporary directory it creates two small throwaway pools, has Sanoid take a snapshot, has syncoid replicate it (a full send, then an incremental one a minute later) and checks each copy's ZFS guid, then has Sanoid prune past its policy and checks that a hand made snapshot survives. It removes everything it made, and takes about 70 seconds:
sudo sanoid-selftest 2>&1 | tail -5
sanoid took cloudimgsanoidst/data@autosnap_2026-11-08_12:30:05_frequently
syncoid replicated autosnap_2026-11-08_12:30:05_frequently to cloudimgsyncoidst/copy (full; guid 4635368680420080423 matches)
syncoid replicated autosnap_2026-11-08_12:31:08_frequently to cloudimgsyncoidst/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_OK
Step 6: Snapshots of tank/data
Sanoid names its snapshots autosnap_<UTC date>_<time>_<hourly|daily|monthly>. The first run took one of each:
sudo zfs list -H -t snapshot -o name -r tank/data
tank/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_hourly
Sanoid 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-health
OK: 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. This writes a file, takes a snapshot by hand, deletes the file and copies it back from the snapshot:
echo "notes from cloudimg" | 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/data
notes from cloudimg
notes.txt
findoid lists every version of a file that the snapshots hold, newest last:
sudo findoid /tank/data/notes.txt
Sun 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.txt
A snapshot you take by hand stays until you destroy it:
sudo zfs destroy tank/data@before-change
To roll a whole dataset back to a snapshot, sudo zfs rollback -r tank/data@<snapshot> (this discards everything written since that snapshot).
Step 7: Change the policy
The policy is /etc/sanoid/sanoid.conf. This is what the image ships:
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.frequentlysnapshots are taken every 15 minutes when you set it above 0. - Protect more data: create datasets under
tank/data(sudo zfs create tank/data/projects);recursive = yesputs every child on the same policy. - 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 it 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.service
The built-in defaults are in /etc/sanoid/sanoid.defaults.conf (do not edit it) and the annotated upstream example is /usr/share/doc/sanoid/sanoid.conf.example.
Step 8: 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.txt
On the backup host, as root, append that line to root's authorized_keys (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_keys
Then, on this instance, replicate (the first run is a full send, 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 would rather not 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/restored
Step 9: Check 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' | sort
0.0.0.0/0
::/0
They do not need it: 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 needs it.
Step 10: 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 tank
The 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.
Step 11: 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 cloudimg 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.1
Logs: sudo journalctl -u sanoid -u sanoid-prune.
Step 12: 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, andsudo 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 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/. cloudimg is not affiliated with or endorsed by the Sanoid project or OpenZFS. The AMI price covers cloudimg's hardening, first boot setup and support.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA