ZFS System Migration: A Complete Working Guide
This document is a comprehensive guide to your working ZFS backup solution. It includes the full script, an explanation of its components, and detailed instructions for using the backup to migrate your system to new hardware without having to look up additional information.
Your Setup
- FreeBSD Server:
joe@fbsd
with ZFS root poolzroot
- Backup Server:
joe@raspberrypi
at IP192.168.1.12
- Backup Pool: A ZFS pool named
tank
on the Raspberry Pi - Backup Script: Located at
/usr/local/bin/zfsbackup_to_pi.sh
Part 1: The Automated Backup Process
The script you've created is designed for automated, incremental backups. It runs as the root
user, which is necessary to manage the system's ZFS pools and file systems.
The Backup Script: /usr/local/bin/zfsbackup_to_pi.sh
#!/bin/sh
# --- ZFS Backup Configuration ---
# Pool on your FreeBSD machine to be backed up
SOURCE_POOL="zroot"
# Destination dataset on your Raspberry Pi (parent pool is 'tank')
REMOTE_DESTINATION="tank/fbsdserver-backup"
# Raspberry Pi user and IP address
REMOTE_USER="joe"
REMOTE_HOST="192.168.1.12"
# A tag to identify snapshots created by this script
SNAPSHOT_TAG="auto-backup"
# --- Script Logic ---
# Get the full name of the last snapshot created by this script
LATEST_SNAPSHOT=$(zfs list -H -t snapshot -o name -S creation "${SOURCE_POOL}" | grep "${SNAPSHOT_TAG}" | head -n 1)
# Create a new snapshot with today's date and the tag
DATE=$(date +%Y-%m-%d)
NEW_SNAPSHOT_NAME="${SOURCE_POOL}@${SNAPSHOT_TAG}_${DATE}"
# Check if a snapshot with today's date already exists to prevent an error
if zfs list -H -t snapshot | grep -q "${NEW_SNAPSHOT_NAME}"; then
echo "Snapshot ${NEW_SNAPSHOT_NAME} already exists. Exiting."
exit 0
fi
echo "Creating new snapshot: ${NEW_SNAPSHOT_NAME}"
zfs snapshot -r "${NEW_SNAPSHOT_NAME}"
# Send the new snapshot incrementally or as a full send if no previous snapshots exist
if [ -z "${LATEST_SNAPSHOT}" ]; then
echo "No previous snapshot found. Performing a full send."
zfs send -R "${NEW_SNAPSHOT_NAME}" | ssh "${REMOTE_USER}"@"${REMOTE_HOST}" "sudo zfs receive -F '${REMOTE_DESTINATION}'"
else
echo "Sending incremental stream from ${LATEST_SNAPSHOT}..."
zfs send -R -i "${LATEST_SNAPSHOT}" "${NEW_SNAPSHOT_NAME}" | ssh "${REMOTE_USER}"@"${REMOTE_HOST}" "sudo zfs receive -F '${REMOTE_DESTINATION}'"
fi
echo "Backup complete."
How the Script Works
- Configuration Variables: The script uses variables at the top to make it easy to configure. You can change
REMOTE_HOST
,REMOTE_DESTINATION
, etc., here if needed. - Snapshot Naming: It automatically generates a snapshot name (e.g.,
zroot@auto-backup_2025-07-26
) to ensure a unique name each time it runs. - Incremental Logic: The script first checks for a previous snapshot. If one exists, it uses
zfs send -R -i
to send only the new, changed data blocks. If no previous snapshot is found (the first run), it performs a full send. - Recursive Send: The
-R
flag is critical as it ensures the entire system's dataset hierarchy (e.g.,zroot
,zroot/ROOT
,zroot/home
) is backed up in a single operation.
The Cron Job
The crontab
command schedules jobs to run automatically. It's important to understand the difference between editing your personal crontab and the root
user's.
crontab -e
: This command edits your personal cron jobs as thejoe
user. These jobs run with your user permissions and would fail when trying to create snapshots onzroot
.sudo crontab -e
: This command edits theroot
user's cron jobs. Jobs run here have full system privileges, which is what's needed to manage ZFS pools.
Your working cron job runs as root
and is set to execute your script every Sunday at 3:00 AM.
# crontab entry for the root user
0 3 * * 0 /usr/local/bin/zfsbackup_to_pi.sh > /dev/null 2>&1
Part 2: The System Migration Process
When you get your new server, here are the steps to use this backup to restore a complete, working system.
What Your Backup Contains
Your ZFS backup is not just a copy of files. It's a block-level replication of your entire system's state, including:
- All user data and configurations (e.g., in
/home/joe
,/etc
) - All installed packages and their files
- All custom services, scripts, and cron jobs
- The exact filesystem structure and permissions
Step 1: Prepare the New Machine
On your new server, install a minimal version of FreeBSD 14-RELEASE. This ensures you have a functioning bootloader and a ZFS pool to receive the data. Once complete, you will destroy the new zroot
pool to prepare for the transfer.
Step 2: Transfer the Final Data from the Raspberry Pi
On your new server, run the following commands to pull the latest backup from the Raspberry Pi.
# Destroy the temporary zroot pool from the new install
zfs destroy -r zroot
# Pull the final, latest backup from your Raspberry Pi
# The snapshot name is the most recent one on your Pi's 'tank' pool
ssh joe@192.168.1.12 "sudo zfs send -R tank/fbsdserver-backup@auto-backup_2025-07-26" | zfs receive -F zroot
Step 3: Adjust System Configuration for New Hardware
This is the most critical step for making your machine bootable and functional. Your new hardware is different, so a few settings must be adjusted.
- Set the Boot Dataset: Tell the bootloader which dataset to use.
- Configure Networking: Edit
/etc/rc.conf
usingnano
to set the new network interface name (e.g.,em0
,igb0
). - Update other configurations: Review other configuration files (e.g.,
/etc/fstab
for non-ZFS mounts) for any hardware-specific settings and adjust as needed.
zpool set bootfs=zroot/ROOT/default zroot
# /etc/rc.conf
hostname="new-server"
ifconfig_em0="DHCP" # Change 'em0' to your new machine's NIC name
Step 4: Reboot and Verify
Once you've made these changes, reboot your new machine. It will boot into a fully functional clone of your original FreeBSD server, with all your services and custom configurations intact.