Sanoid and Syncoid: Automated ZFS Snapshots and Replication

By LK Wood IV · 2026-05-03 · ~10 min read · St. Louis County, MO

Architecture diagram: a sanoid.timer systemd unit fires every 15 minutes to snapshot ZFS datasets (datapool/documents, vm-disks, media) on a Proxmox VE 8 source host using the production, vm_disks and media retention templates, while sanoid --monitor-snapshots pushes health to Uptime Kuma; syncoid then sends incremental zfs snapshots over SSH each day at 03:00 to a remote backuppool, where a restore test clones and mounts a snapshot to verify it.

ZFS snapshots are instant, zero-cost at creation time (they only consume space as data changes), and a critical part of a storage strategy. But ZFS doesn’t automate them — you need a policy engine. Sanoid is that engine. Syncoid adds replication to a second ZFS pool, either locally or over SSH.

This guide covers both: Sanoid for snapshot automation with retention policies, Syncoid for encrypted replication.

Prerequisites

  • A ZFS pool already set up — see the ZFS on Proxmox guide if you’re starting from scratch
  • Debian 12 / Proxmox VE 8 host (the commands apply to any Debian-based system with ZFS)
  • For Syncoid replication: SSH access to a destination host with a ZFS pool

Step 1: Install Sanoid

Sanoid is in the Debian 12 repositories:

apt update && apt install -y sanoid

On Proxmox VE (which is Debian-based), Sanoid installs cleanly. The package installs:

  • /usr/sbin/sanoid — the snapshot management daemon
  • /usr/sbin/syncoid — the replication tool (separate binary, same package)
  • /etc/sanoid/sanoid.conf — the policy config file
  • A systemd timer that runs Sanoid every 15 minutes

Step 2: Configure snapshot policies

Edit the config file:

nano /etc/sanoid/sanoid.conf

Template-based configuration — Sanoid uses templates to define retention policies, then you apply templates to datasets:

# /etc/sanoid/sanoid.conf

# --- Templates ---
# Define retention policies once, reuse across datasets

[template_production]
  # How often to take each type of snapshot
  hourly = 24        # Keep 24 hourly snapshots (last 24 hours)
  daily = 30         # Keep 30 daily snapshots (last month)
  monthly = 12       # Keep 12 monthly snapshots (last year)
  yearly = 0         # No yearly retention

  # When should Sanoid take each type?
  # (Sanoid runs every 15min; it takes a snapshot when the time has passed
  #  since the last one of that type)
  autosnap = yes
  autoprune = yes

[template_media]
  # Media changes rarely — weekly + monthly is enough
  hourly = 0
  daily = 7
  monthly = 3
  yearly = 0
  autosnap = yes
  autoprune = yes

[template_vm_disks]
  # Frequently-changing VM data — more hourly retention
  hourly = 48        # Last 48 hours at hourly granularity
  daily = 14
  monthly = 3
  yearly = 0
  autosnap = yes
  autoprune = yes

# --- Datasets ---
# Apply templates to your ZFS datasets

[datapool/vm-disks]
  use_template = vm_disks

[datapool/media]
  use_template = media

[datapool/backups]
  # Don't snapshot the PBS backup datastore — PBS handles its own versioning
  autosnap = no
  autoprune = no

[datapool/documents]
  use_template = production

Replace datapool with your pool name. List your pools and datasets with:

zfs list -r

Step 3: Enable and test the systemd timer

Sanoid ships with a systemd service and timer. On Proxmox VE 8 (Debian 12), the timer is installed but not enabled by default:

# Enable and start the timer
systemctl enable --now sanoid.timer

# Verify the timer is active
systemctl status sanoid.timer

# Manually trigger a run to test
systemctl start sanoid.service

# Check output
journalctl -u sanoid.service -n 50

After a successful run, verify snapshots were created:

zfs list -t snapshot -r datapool/documents

You should see snapshots named like datapool/documents@sanoid_2026-05-03_14:00:00_hourly.

Step 4: Monitor snapshot health

Sanoid includes a monitoring command that outputs Nagios-compatible status:

sanoid --monitor-snapshots

This checks whether recent snapshots exist and whether the latest snapshot is within the expected age. The output is:

OK: all monitored datasets have recent snapshots

Or, if a dataset has missed snapshots:

CRITICAL: datapool/documents: no daily snapshot in the last 25 hours

Integrate with Uptime Kuma push monitoring:

Add to a cron job or systemd override:

#!/bin/bash
# /usr/local/bin/sanoid-health-check.sh
STATUS=$(sanoid --monitor-snapshots 2>&1)
if echo "$STATUS" | grep -q "^OK"; then
  curl -s "https://uptime.yourdomain.com/api/push/YOUR_PUSH_KEY?status=up&msg=Sanoid+OK"
else
  curl -s "https://uptime.yourdomain.com/api/push/YOUR_PUSH_KEY?status=down&msg=$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.stdin.read().strip()))' <<< "$STATUS")"
fi
chmod +x /usr/local/bin/sanoid-health-check.sh
echo "*/30 * * * * root /usr/local/bin/sanoid-health-check.sh" >> /etc/crontab

Step 5: Syncoid replication

Syncoid replicates datasets from a source ZFS pool to a destination ZFS pool. The destination can be:

  • A second pool on the same host (local backup copy)
  • A remote host over SSH
  • A Proxmox Backup Server node with a ZFS datastore

One-time setup — SSH key auth to the destination:

# On the source host, generate a key for the root user (or a dedicated sanoid user)
ssh-keygen -t ed25519 -f /root/.ssh/sanoid_id -N ""

# Copy to the destination host
ssh-copy-id -i /root/.ssh/sanoid_id.pub root@backup-host

Test a manual replication:

# Replicate datapool/documents to backuppool/documents on a remote host
syncoid --recursive \
  datapool/documents \
  root@backup-host:backuppool/documents

The --recursive flag replicates all child datasets. Syncoid automatically finds the most recent common snapshot between source and destination and sends only the incremental delta.

First replication note: The first run sends the entire dataset — this can take significant time for large pools. Run it manually first to verify the connection and transfer rate before scheduling.

Schedule Syncoid with systemd:

Create a systemd service:

# /etc/systemd/system/syncoid-replicate.service
[Unit]
Description=Syncoid ZFS Replication
Requires=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/sbin/syncoid --recursive datapool/documents root@backup-host:backuppool/documents
ExecStartPost=/usr/sbin/syncoid --recursive datapool/vm-disks root@backup-host:backuppool/vm-disks

Create the timer:

# /etc/systemd/system/syncoid-replicate.timer
[Unit]
Description=Run Syncoid replication daily

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

Enable:

systemctl daemon-reload
systemctl enable --now syncoid-replicate.timer

Step 6: Verify replicated snapshots

On the destination host, confirm snapshots arrived:

# On backup-host
zfs list -t snapshot -r backuppool/documents | tail -10

You should see snapshots with Sanoid’s naming convention, matching what’s on the source.

Test a restore from a replicated snapshot:

# On backup-host: clone a snapshot to a test dataset
zfs clone backuppool/documents@sanoid_2026-05-03_00:00:00_daily backuppool/restore-test

# Mount it and verify content
zfs set mountpoint=/mnt/restore-test backuppool/restore-test

# Check files
ls /mnt/restore-test

# Clean up
zfs destroy backuppool/restore-test

This step is not optional. Snapshots that can’t be mounted and read are not useful backups. Run restore tests when you first set this up and periodically afterward.

Retention math

For a dataset that changes 1GB/day, with the production template (24 hourly, 30 daily, 12 monthly):

Snapshot typeCountTotal additional space
Hourly (24h window)24~24 GB
Daily (30d window)30~30 GB
Monthly (12m window)12~12 GB
Total overhead66~66 GB

Space is additive only for the data that changed between snapshots — unchanged blocks are shared. A dataset that changes 1 GB/day but has 100 GB of static content costs ~66 GB for a year of snapshots, not 66× 100 GB.

ZFS compression applies to snapshot data as well as live data. If your data compresses at 1.5:1, snapshot overhead compresses proportionally.


Sanoid snapshots protect against accidental deletion and filesystem corruption within a single pool. For off-machine protection (fire, hardware failure), combine with Syncoid replication to a second physical host or restic to Backblaze B2. The ZFS on Proxmox setup guide covers the pool creation that Sanoid manages.

Frequently asked questions

What is Sanoid and why do I need it for ZFS?
ZFS can take snapshots natively with ‘zfs snapshot’, but it provides no built-in automation or retention policy enforcement. Sanoid is a policy daemon that runs on a schedule, creates snapshots according to a retention config, and prunes old snapshots automatically. Without Sanoid (or an equivalent tool), you either take no automated snapshots or write and maintain your own cron scripts.
How is Sanoid different from Proxmox Backup Server for ZFS?
Proxmox Backup Server backs up VM and LXC content — it copies the data out of the VM/LXC into PBS’s chunk-based format. Sanoid snapshots operate at the ZFS dataset level and capture the entire filesystem state including the OS inside a VM, not just the backup data. Sanoid is especially useful for datasets that PBS doesn’t see directly — ZFS datasets used for file storage, Docker volumes, or datasets on a TrueNAS host outside Proxmox. The two tools are complementary, not competing.
What does Syncoid do that 'zfs send | zfs recv' doesn't?
Syncoid is a wrapper around zfs send/recv that handles incremental sends (it finds the common snapshot between source and destination and only sends the delta), SSH transport with compression, and mbuffer buffering to prevent I/O stalls. Raw ‘zfs send | zfs recv’ requires you to track the incremental send token manually. Syncoid does this automatically, making replication a single command.
Can I replicate to a non-ZFS destination?
ZFS replication (via Syncoid or raw zfs send) requires a ZFS pool on the destination. For non-ZFS destinations (Backblaze B2, an ext4 NAS, a cloud VM), use restic instead — restic deduplicates and encrypts at the block level and writes to any storage backend. For replicating ZFS to another ZFS machine (a second NAS, a PBS server with a ZFS pool), Syncoid is the right tool.
How often should ZFS snapshots run?
A typical retention policy: hourly snapshots for 24 hours, daily for 30 days, monthly for 12 months. This gives you 15-minute granularity within the last day, day-level recovery for the last month, and month-level going back a year. The Sanoid config in this guide implements exactly this policy. Adjust based on your data’s change rate — a database with many small writes benefits from more frequent snapshots; a media archive that rarely changes needs fewer.