how do I make a crontab entry for root's crontab on freebsd to make a zfs snapshot of zroot/datashare:
zfs snapshot zroot/datashare@[ISO8601 Date-Time]
I also want to zfs send the latest snapshot every so often to server.com
Use root’s crontab plus a small helper script and you’re set.
As root:
crontab -u root -eAdd (example: snapshot every 15 minutes):
*/15 * * * * /sbin/zfs snapshot zroot/datashare@$(date -u +\%Y-\%m-\%dT\%H:\%M:\%SZ)Key points:
date -u +...→ ISO8601-like timestamp in UTC, e.g.2025-11-10T13:45:00Z.- In crontab, all
%must be escaped as\%, or cron treats them specially. - Cron’s
/bin/shis fine with$(...). zfs snapshot pool/fs@nameruns as root because it’s in root’s crontab.
If you prefer local time instead of UTC, drop -u.
Best pattern: script that:
- Creates a new snapshot with your timestamp.
- Detects the previous snapshot with the same prefix.
- Sends incrementally to the remote host via SSH.
Create /root/bin/zfs-snap-and-send-datashare.sh:
#!/bin/sh
set -eu
DATASET="zroot/datashare"
REMOTE_HOST="[email protected]" # or just server.com if root there
REMOTE_DATASET="backup/zroot/datashare" # adjust to your remote pool/fs
PREFIX="autosnap"
# New ISO8601-style snapshot name
STAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
SNAP="${DATASET}@${PREFIX}-${STAMP}"
# 1. Create snapshot
zfs snapshot "$SNAP"
# 2. Find previous snapshot with same prefix (excluding the one we just made)
PREV=$(zfs list -H -t snapshot -o name -s creation -d 1 "$DATASET" \
| grep "${DATASET}@${PREFIX}-" \
| grep -v "$SNAP\$" \
| tail -1 || true)
# 3. Send (incremental if possible, otherwise full)
if [ -n "$PREV" ]; then
# Incremental from PREV to SNAP
zfs send -w -I "$PREV" "$SNAP" \
| ssh "$REMOTE_HOST" zfs receive -uF "$REMOTE_DATASET"
else
# First run: full stream
zfs send -w "$SNAP" \
| ssh "$REMOTE_HOST" zfs receive -uF "$REMOTE_DATASET"
fiThen:
chmod 700 /root/bin/zfs-snap-and-send-datashare.shAdd a cron job for root (example: every hour at minute 5):
5 * * * * /root/bin/zfs-snap-and-send-datashare.sh >/var/log/zfs_send_datashare.log 2>&1- Passwordless SSH from the backup source host (root) to
REMOTE_HOST(e.g.[email protected]). - Remote pool/dataset (
backup/zroot/datasharein the example) created or creatable byzfs receive. - Enough retention on both ends; you can prune old snapshots with another cron if you’d like.
If you tell me your desired snapshot + replication frequency and remote dataset layout, I can adjust these lines exactly to your setup.
Can you write a cronjob to prune old snapshots? The data doesn't continuously change - sometimes we get busy and add/change/update a lot of files though, and then don't do anything else for a couple of months. There should always be a few snapshots, but my understanding is that the snapshots don't take up a lot of space if there is no change.
You’re right: if nothing changes, ZFS snapshots are basically just lightweight metadata. Let’s set up pruning that:
- only touches your auto snapshots,
- always leaves a healthy number,
- and is burst-friendly (keeps recent ones more aggressively).
Below is a ready-to-drop-in script + cronjob.
I’ll match the previous pattern I used:
- Dataset:
zroot/datashare - Auto snapshots look like:
zroot/datashare@autosnap-2025-11-10T13:45:00Z - We do not touch any other snapshot names (manual ones, different prefixes).
If your current autos are just @YYYY-... without autosnap-, we can tweak, but I’ll keep it safe and prefix-scoped.
For zroot/datashare@autosnap-*:
- Keep at least 10 newest snapshots, no matter how old.
- For anything older than that, delete if older than 30 days.
This works well for “bursty then quiet” usage:
- Busy period → many recent snapshots kept.
- Quiet period → only a small tail of old snapshots sticks around.
You can change KEEP_MIN / KEEP_DAYS if you like.
Create /root/bin/zfs-prune-autosnap-datashare.sh:
#!/bin/sh
set -eu
DATASET="zroot/datashare"
PREFIX="autosnap-"
KEEP_MIN=10 # minimum number of snapshots to always keep
KEEP_DAYS=30 # delete autosnap snapshots older than this (days), beyond KEEP_MIN
# Current time (UTC, epoch seconds)
NOW=$(date -u +%s)
MAX_AGE_SECONDS=$((KEEP_DAYS * 86400))
# List autosnap snapshots for this dataset, oldest first
SNAPS=$(zfs list -H -t snapshot -o name -s creation -d 1 "$DATASET" \
| grep "^${DATASET}@${PREFIX}" || true)
# If nothing to do, exit quietly
[ -z "$SNAPS" ] && exit 0
# Count snapshots
COUNT=0
for S in $SNAPS; do
COUNT=$((COUNT + 1))
done
# If we have <= KEEP_MIN, don't delete anything
if [ "$COUNT" -le "$KEEP_MIN" ]; then
exit 0
fi
# How many from the start (oldest) are eligible for deletion checks
TO_CHECK=$((COUNT - KEEP_MIN))
N=0
for SNAP in $SNAPS; do
N=$((N + 1))
# Only consider the oldest TO_CHECK snapshots for deletion;
# newer ones are always kept.
if [ "$N" -gt "$TO_CHECK" ]; then
break
fi
# Extract timestamp from name: dataset@autosnap-<TS>
NAME="${SNAP##*@${PREFIX}}"
# Expect ISO8601 UTC like 2025-11-10T13:45:00Z
# Convert to epoch; if this fails, skip deletion for safety.
SNAP_EPOCH=$(date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$NAME" "+%s" 2>/dev/null || echo "")
if [ -z "$SNAP_EPOCH" ]; then
# Unknown format; don't touch it
continue
fi
AGE=$((NOW - SNAP_EPOCH))
if [ "$AGE" -gt "$MAX_AGE_SECONDS" ]; then
# Safe to remove
zfs destroy "$SNAP"
fi
doneThen:
chmod 700 /root/bin/zfs-prune-autosnap-datashare.shRun prune daily (example: 03:20):
20 3 * * * /root/bin/zfs-prune-autosnap-datashare.sh >/var/log/zfs_prune_datashare.log 2>&1That’s it: you’ll always have recent restore points plus a handful of older ones, without snapshots piling up forever.
If you paste your current snapshot naming pattern, I can align this script exactly without changing your existing setup.