Backup Strategies for Unraid

Parity is not a backup

Unraid's array uses XOR parity across data disks, tolerating one failed disk (single parity) or two (dual parity). This protects against disk failure, and nothing else. Parity is a live, continuously-derived value — delete a file, and the parity disk is updated to reflect that deletion on the next write. Corrupt a file via a failing cable or bad RAM, and unless the array runs regular data-integrity checks (Unraid does not checksum file contents by default), parity happily protects the corrupted version.

Danger

Parity provides zero protection against accidental deletion, ransomware, filesystem corruption, controller failure, or physical loss (fire, theft, flood). It also does nothing for bitrot on array data disks unless the underlying filesystem does its own checksumming (BTRFS/ZFS do; XFS, the Unraid default per-disk format, does not). Treat parity purely as an uptime feature.

A real backup strategy needs at least one copy of critical data that is physically or logically separate from the array — a second device, a different building, or both — and ideally versioned so a bad write doesn't overwrite the last good copy.

What actually needs backing up

Not everything on an Unraid box carries the same backup priority. Bulk media that can be re-acquired matters less than the handful of small, high-value directories that took real time to configure.

AssetDefault locationWhy it mattersPriority
USB boot device/boot (flash)Holds the OS, license key (.key), network config, disk assignments, plugin configsCritical
Appdata/mnt/user/appdataDocker container configs, databases, application stateCritical
Docker templates / compose/boot/config/plugins/dockerManContainer definitions — rebuildable if lost, but tediousHigh
VM images/mnt/user/domainsvdisks and libvirt.img — often large, expensive to rebuildHigh
User shares (documents, photos)/mnt/user/*Irreplaceable personal dataCritical
Bulk media library/mnt/user/mediaLarge but typically re-acquirableLow

Flash drive backup

The USB flash drive is a single point of failure for the entire system's identity: it stores the OS image, the Pro/Plus license binding, network bond configuration, and every plugin's config directory. Unraid ships a built-in exporter under Main → Flash → Flash Backup, which archives the full config directory to a local download or syncs it to an Unraid Connect account if registered.

Where GUI backup isn't set up, the same result is achieved manually — with the array stopped, or at minimum the flash device unmounted for consistency:

# run from a machine with SSH access to the server
rsync -avz root@tower:/boot/ ./unraid-flash-backup/
tar -czf flash-backup-$(date +%F).tar.gz ./unraid-flash-backup/
Warn

The .key license file is bound to the flash drive's GUID. Replacing a failed flash drive requires either restoring the exact same drive's identity or requesting a replacement license from the vendor tied to the new GUID. Keep the .key file in at least two separate backup locations — losing it and the original drive simultaneously means re-purchasing the license.

Appdata: consistency before archiving

Appdata is the highest-churn, highest-value directory on the box — SQLite and Postgres databases, media server metadata, reverse proxy certs, home automation state. Copying it while containers are actively writing risks capturing a torn, inconsistent file.

The community CA Backup / Restore Appdata plugin (via Community Applications) handles this correctly: on a schedule, it stops the selected containers, archives appdata (and optionally flash and libvirt) to a destination share, then restarts the containers. It supports retention counts, exclusion patterns, and pre/post-run scripts.

The equivalent done manually through the User Scripts plugin, for a smaller or more selective setup:

#!/bin/bash
# stop-consistent appdata backup
DEST="/mnt/user/backups/appdata"
STAMP=$(date +%Y%m%d)

for c in plex sonarr radarr homeassistant postgres; do
  docker stop "$c"
done

tar -czf "$DEST/appdata-$STAMP.tar.gz" --exclude='*/cache/*' --exclude='*/logs/*' /mnt/user/appdata

for c in plex sonarr radarr homeassistant postgres; do
  docker start "$c"
done
Tip

Exclude transient cache and log subdirectories from the archive — Plex's Cache folder alone can dwarf every other container's appdata combined and adds nothing worth restoring.

Docker template layer

Container templates (the XML definitions behind each Docker tab entry) live under /boot/config/plugins/dockerMan/templates-user and are covered by the flash backup above. Losing only appdata but keeping templates means containers can be recreated and will simply re-detect their restored config volumes on first launch.

VM backups

VM disks (vdisk1.img, etc.) and the libvirt configuration database live on the domains share. Unlike appdata, these are large, single files that are actively held open by qemu while the VM runs — copying a live vdisk produces a crash-consistent image at best, and a corrupt one at worst if the guest filesystem is mid-write.

The reliable approach is to shut the VM down before copying, scheduled during a low-usage window via User Scripts and virsh:

#!/bin/bash
VM="Windows11"
SRC="/mnt/user/domains/$VM"
DEST="/mnt/user/backups/vms/$VM-$(date +%Y%m%d)"

virsh shutdown "$VM"
while virsh domstate "$VM" | grep -q running; do sleep 5; done

mkdir -p "$DEST"
rsync -a "$SRC/" "$DEST/"

virsh start "$VM"

For VMs that can't tolerate downtime, an external qcow2 snapshot (virsh snapshot-create-as) allows backing up the base disk while writes redirect to a delta file, followed by a live block-commit to merge the delta back — more moving parts, worth it only where uptime genuinely matters.

ZFS snapshots (Unraid 6.12+)

Since native ZFS pool support landed, appdata and VM storage can live on a ZFS-formatted cache pool, unlocking atomic, near-instantaneous snapshots regardless of whether containers are running — the snapshot captures a consistent point-in-time view via copy-on-write, without the stop/start dance appdata otherwise needs.

# snapshot the appdata dataset
zfs snapshot cache/appdata@$(date +%Y%m%d-%H%M)

# incremental send to a backup pool on the same box
zfs send -i cache/appdata@20260701-0300 cache/appdata@20260708-0300 | \
  zfs receive backup/appdata

# or streamed to a remote host over SSH
zfs send -i cache/appdata@20260701-0300 cache/appdata@20260708-0300 | \
  ssh backup-host "zfs receive tank/unraid-appdata"

Retention is managed by pruning old snapshots on a rolling schedule (hourly for a day, daily for a week, weekly for a month is a common pattern) — tools like sanoid/syncoid automate the snapshot-and-prune policy and the incremental send/receive in one step.

Note

A ZFS snapshot is not, by itself, an offsite backup — it's a local, filesystem-level recovery point. It still needs to be sent (via zfs send, or by backing up the receiving pool in turn) to a location separate from the source array to count toward 3-2-1.

Offsite replication

The standard reference point is the 3-2-1 rule: three copies of data, on two different media types, with one stored offsite. A parity-protected array plus a local USB drive backup satisfies neither the media diversity nor the offsite leg on its own.

ToolScopeEncryptionDedupBest for
rcloneAny folder → cloud object storageOptional (crypt remote)NoSimple mirror/sync to S3-compatible or B2 storage
resticAny folder → repositoryAlways (AES-256)YesVersioned, deduplicated, encrypted offsite archives
borgbackupAny folder → repositoryAlwaysYes (strong)Same niche as restic; slightly better compression ratios
SyncthingContinuous folder syncTransport onlyNoNear-real-time mirror to a second physical site
zfs send/receiveZFS datasets onlyTransport via SSHBlock-levelIncremental snapshot replication between ZFS pools

A minimal rclone push of a completed appdata archive to Backblaze B2:

rclone copy /mnt/user/backups/appdata b2:unraid-offsite/appdata --transfers 4 --log-file /var/log/rclone-appdata.log

restic gives versioning and deduplication on top, so only changed blocks consume additional remote storage on each run:

restic -r b2:unraid-offsite:appdata-restic backup /mnt/user/appdata
restic -r b2:unraid-offsite:appdata-restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
Warn

Losing an encryption password for a restic/borg repository or an rclone crypt remote is unrecoverable by design — there is no vendor to call. Store the password in a separate password manager entry, not only on the Unraid box being backed up.

Danger

Ransomware that reaches an Unraid box over SMB can encrypt every writable share it can mount, array and backups included, if the backup destination is a plain writable network share. Keep at least one copy in a repository format that isn't directly mounted as an SMB share (restic/borg repositories, or a cloud object store reached only via rclone/restic credentials) so a compromised client can't simply overwrite it.

Automating and scheduling

The User Scripts plugin (Community Applications) runs arbitrary bash on a cron schedule and is the standard glue for chaining snapshot, archive, and push steps without maintaining a separate job scheduler. A combined nightly job:

#!/bin/bash
# Schedule: 0 3 * * *  (nightly at 03:00)

/mnt/user/scripts/appdata-backup.sh
zfs snapshot cache/appdata@$(date +%Y%m%d)
rclone copy /mnt/user/backups/appdata b2:unraid-offsite/appdata --max-age 24h

if [ $? -ne 0 ]; then
  curl -H "Content-Type: application/json" -d '{"content":"Unraid backup job failed"}' \
    "https://discord.com/api/webhooks/..."
fi
Tip

Wire failure notifications into whatever's already monitoring the box (Discord webhook, ntfy, healthchecks.io ping). A backup job that silently stops running for three months is worse than no backup job — it creates false confidence.

Validating restores

An untested backup is a hypothesis, not a backup. Each layer above has a cheap way to verify it actually works:

Quick reference

LayerToolFrequencyDestination
USB flash / licenseBuilt-in Flash BackupOn changeLocal + offsite copy
AppdataCA Backup/Restore or scripted tarDailySecond local disk
Appdata (ZFS)zfs snapshot + sendHourly/dailyLocal backup pool + remote host
VM imagesvirsh shutdown + rsyncWeeklySecond local disk
Offsite copyrestic / rcloneDailyS3-compatible / B2
User dataSyncthing or resticContinuous / dailySecond site