Skip to content

Instantly share code, notes, and snippets.

@rma92
Last active November 10, 2025 14:03
Show Gist options
  • Select an option

  • Save rma92/80a23985661c0bd0847cc9be9b65d347 to your computer and use it in GitHub Desktop.

Select an option

Save rma92/80a23985661c0bd0847cc9be9b65d347 to your computer and use it in GitHub Desktop.

user

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

assistant

Use root’s crontab plus a small helper script and you’re set.

1. Root crontab entry for ISO8601 snapshots

As root:

crontab -u root -e

Add (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/sh is fine with $(...).
  • zfs snapshot pool/fs@name runs as root because it’s in root’s crontab.

If you prefer local time instead of UTC, drop -u.


2. Regularly zfs send the latest snapshot to server.com

Best pattern: script that:

  1. Creates a new snapshot with your timestamp.
  2. Detects the previous snapshot with the same prefix.
  3. 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"
fi

Then:

chmod 700 /root/bin/zfs-snap-and-send-datashare.sh

Add 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

3. Things to have in place

  • Passwordless SSH from the backup source host (root) to REMOTE_HOST (e.g. [email protected]).
  • Remote pool/dataset (backup/zroot/datashare in the example) created or creatable by zfs 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.

user

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.

assistant

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.


1. Assumptions (what this script targets)

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.


2. Retention policy

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.


3. Prune script

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
done

Then:

chmod 700 /root/bin/zfs-prune-autosnap-datashare.sh

4. Cronjob

Run prune daily (example: 03:20):

20 3 * * * /root/bin/zfs-prune-autosnap-datashare.sh >/var/log/zfs_prune_datashare.log 2>&1

That’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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment