niklas‑stephan.de

ZFS / syncoid CLI gotchas (trixie, zfs 2.4.4, sanoid v2.3.0)

Last updated 05/11/2026 · migrated-from-outline, tools, zfs

.

Reference for the non-obvious behaviours of zfs and syncoid as shipped in our Docker images (zfsutils-linux 2.4.4 from trixie backports + sanoid/syncoid v2.3.0 Perl scripts). Found while building weboid’s ZFS backup path (see that session log). These bite anyone scripting against these tools.

syncoid (sanoid v2.3.0)

  • No **-s** short flag. It is ambiguous (sendoptions | skip-parent | source-bwlimit | sshcipher | ...) and the command aborts with “Option s is ambiguous”. Always use long options.
  • Remote-target form mis-parses a snapshotted SOURCE. syncoid ds@snap [user@]host:target treats ds@snap as user=ds, host=snap → “Could not resolve hostname ”. To push, target the dataset (syncoid ds user@host:target) and let syncoid manage its own snapshot. It snapshots locally, transfers the delta since the last run, and (by default) destroys its working snapshot after.
  • Refuses to receive into a pre-existing empty dataset. If the target dataset exists but has no matching snapshots: “Target … exists but has no snapshots matching … Cowardly refusing to destroy your existing target.” → create only the parent of the destination, let syncoid create the final dataset.
  • Needs its own SSH credentials to push. It dials the server itself, so pass --sshkey=<priv> and an ssh_config (via --sshconfig=) with host-key trust (StrictHostKeyChecking no or a pinned key), else “Host key verification failed”.

zfs CLI (2.4.x)

  • **zfs get <prop> <ds>** parses **<ds>** as a property, not a dataset. It fails for both filesystems and volumes with “bad property list: invalid property ''”. Don’t use zfs get to test dataset existence.
  • Prefer **zfs list -H -o name <ds>** + exit code for an existence check that works for both filesystems and volumes: rc 0 = exists, rc 1 with “dataset does not exist” = missing. (zfs get and the combined -Hno flag both fail.)
  • The combined **-Hno** flag is invalid. Write -H -o name, not -Hno. (Affects any script that shortcuts the flags.)
  • Listing snapshots: zfs list -t snapshot -r <ds> works; a literal @* glob as an argument does not (“invalid character ’*’ in name”) — rely on -t snapshot instead of shell-globbing names.

sshd + Docker (receiving syncoid pushes)

  • StrictModes rejects a bind-mounted **authorized_keys** unless owned by root (UID 0) and mode 600. A Docker bind mount preserves the host file’s ownership, so chown root:root + chmod 600 the host copy before mounting. Symptom in sshd -ddd: “Authentication refused: bad ownership or modes for file /root/.ssh/authorized_keys”.
  • A server that receives syncoid must run as root to zfs create receive datasets and bind :22; an unprivileged user gets “permission denied” on zfs admin.

Quick existence-check idiom (Go)

cmd := exec.CommandContext(ctx, "zfs", "list", "-H", "-o", "name", ds)
out, err := cmd.CombinedOutput()
if err == nil { return nil } // exists
var ee *exec.ExitError
if !errors.As(err, &ee) || ee.ExitCode() != 1 || !strings.Contains(string(out), "does not exist") {
    return fmt.Errorf("check dataset %s: %w", ds, err) // real error
}
// ... create parent with: zfs create -p -o canmount=off <parent>

Source: Wiki.js