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.
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.
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.
| Asset | Default location | Why it matters | Priority |
|---|---|---|---|
| USB boot device | /boot (flash) | Holds the OS, license key (.key), network config, disk assignments, plugin configs | Critical |
| Appdata | /mnt/user/appdata | Docker container configs, databases, application state | Critical |
| Docker templates / compose | /boot/config/plugins/dockerMan | Container definitions — rebuildable if lost, but tedious | High |
| VM images | /mnt/user/domains | vdisks and libvirt.img — often large, expensive to rebuild | High |
| User shares (documents, photos) | /mnt/user/* | Irreplaceable personal data | Critical |
| Bulk media library | /mnt/user/media | Large but typically re-acquirable | Low |
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/
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 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
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.
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 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.
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.
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.
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.
| Tool | Scope | Encryption | Dedup | Best for |
|---|---|---|---|---|
| rclone | Any folder → cloud object storage | Optional (crypt remote) | No | Simple mirror/sync to S3-compatible or B2 storage |
| restic | Any folder → repository | Always (AES-256) | Yes | Versioned, deduplicated, encrypted offsite archives |
| borgbackup | Any folder → repository | Always | Yes (strong) | Same niche as restic; slightly better compression ratios |
| Syncthing | Continuous folder sync | Transport only | No | Near-real-time mirror to a second physical site |
| zfs send/receive | ZFS datasets only | Transport via SSH | Block-level | Incremental 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
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.
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.
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
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.
An untested backup is a hypothesis, not a backup. Each layer above has a cheap way to verify it actually works:
restic check --read-data (or borg's equivalent) on a schedule; this reads every stored block back, not just the index.rclone check against the source to confirm the remote actually matches, rather than trusting a copy job's exit code alone.| Layer | Tool | Frequency | Destination |
|---|---|---|---|
| USB flash / license | Built-in Flash Backup | On change | Local + offsite copy |
| Appdata | CA Backup/Restore or scripted tar | Daily | Second local disk |
| Appdata (ZFS) | zfs snapshot + send | Hourly/daily | Local backup pool + remote host |
| VM images | virsh shutdown + rsync | Weekly | Second local disk |
| Offsite copy | restic / rclone | Daily | S3-compatible / B2 |
| User data | Syncthing or restic | Continuous / daily | Second site |