Skip to content

HA Backup & Restore

A backup you've never tested is not a backup — it's a hope. Home Assistant is stateful in three ways:

  1. The database (/config/.core/*.db, or homeassistant.db in newer versions) — automations, scripts, device registry, all your state
  2. Configuration files (configuration.yaml, automations.yaml, addon configs)
  3. Addon state (HACS repositories, MQTT broker data, Zigbee2MQTT network, etc.)

Every one of those can die independently. A "backup" that only handles one is a trap. This guide covers the deployment-specific paths to a real backup + restore — including the ones that break when you think they won't.

The Three Layers of a Real Backup

Layer 1 (local, fast):      HA Supervisor built-in backup. 7-30 days retention.
Layer 2 (off-site, cold):   Scheduled tar of /config to a NAS or cloud. 30-90 days.
Layer 3 (proven):           Test-restore to a staging VM, once a year.

Missing any layer is a specific class of failure: - No Layer 1 — no fast recovery after a bad config edit - No Layer 2 — same-machine disk failure = total loss - No Layer 3 — a "working" backup that doesn't actually restore

The rest of this guide walks each layer, then the deployment-specific matrix, then the failure scenarios and playbooks.

Layer 1: HA Supervisor (Local Backup)

Available on HA OS and Supervised only. HA Container has no Supervisor, skip to the Container cron script below.

Configuration

In the HA UI: Settings → System → Backups → Manage Backups → Backup Settings

  • ✅ Backup on system shutdown — automatic on reboot
  • ✅ Automatic backups:
  • Daily: 1 snapshot, 7-day retention
  • Weekly: 1 snapshot, 4-week retention
  • Monthly: 1 snapshot, 12-month retention

That gives you ~7 daily + ~4 weekly + ~1 monthly = ~12 backups at any time. Each snapshot is 100-500 MB depending on how much state you've accumulated.

What the Supervisor snapshot actually contains

Supervisor backups include:

  • ✅ /config — all your config files
  • ✅ The database — homeassistant.db (SQLite)
  • ✅ Addon configs and state (except the addon binaries themselves)
  • ✅ Snapshots of addon databases (Mosquitto, Zigbee2MQTT, Matter Server, etc.)
  • ❌ Addons themselves — you must re-install any addon after restore
  • ❌ HA Core version — you restore into whatever HA version the target has running; if that version differs significantly, expect migration issues

The last two are the two most common "restore failed" complaints.

Manual backup via CLI (headless servers)

If you're running HA headless, or want to script Layer 1:

# HA CLI — ships with HA OS and Supervised
ha backup create --name "manual-$(date +%F)"
ha backup list
ha backup restore --id <backup_id>

The CLI is the reliable way to run Layer 1 in a cron job — the UI API is unstable across versions.

Layer 2: Scheduled tar to Off-Box Storage

The most important layer. Layer 1 dies with the host; Layer 2 lives elsewhere.

HA Container (the most common setup for home labs)

HA Container has no Supervisor, so this script is your backup:

#!/bin/bash
# /usr/local/bin/ha-backup.sh
set -euo pipefail

CONTAINER="homeassistant"
BACKUP_DIR="/mnt/nas/ha-backups"
DATE=$(date +%Y%m%d-%H%M)
KEEP=30
mkdir -p "$BACKUP_DIR"

# Stop HA for a consistent snapshot. Use docker stop, not stop.
docker stop "$CONTAINER"

# Tar from inside the container so paths are absolute
docker exec -t "$CONTAINER" tar czf /tmp/ha-config.tar.gz \
    --exclude='core/backup' \
    --exclude='core/.cache' \
    --exclude='.storage/ui_lovelace*' \
    /config

docker cp "$CONTAINER":/tmp/ha-config.tar.gz \
    "$BACKUP_DIR/ha-$DATE.tar.gz"

docker rm "$CONTAINER" >/dev/null 2>&1 || true
docker start "$CONTAINER"

# Keep latest N
ls -t "$BACKUP_DIR"/*.tar.gz | tail -n +"$((KEEP + 1))" | xargs -r rm -f

# Success ping (replace with your webhook / Telegram)
echo "HA backup OK: $BACKUP_DIR/ha-$DATE.tar.gz ($(du -h "$BACKUP_DIR/ha-$DATE.tar.gz" | cut -f1))"

Schedule with cron:

0 3 * * * /usr/local/bin/ha-backup.sh >> /var/log/ha-backup.log 2>&1

Two traps in this script:

  1. --exclude matters. Without it, you're backing up core/backup (which contains the Supervisor backups — Layer 1 duplicating itself) and browser cache. Exclude them.
  2. docker stop before tar. Live tar of SQLite can produce a corrupt database if HA writes during the snapshot. Stop, tar, start. The window is seconds; acceptable for a nightly job.

If you don't want to stop HA, use SQLite's transaction export mode:

docker exec "$CONTAINER" python3 -c "
import sqlite3
conn = sqlite3.connect('/config/homeassistant.db')
conn.backup('/tmp/homeassistant.db')
conn.close()
print('DB exported')
"
docker cp "$CONTAINER":/tmp/homeassistant.db /tmp/ha-db-$(date +%Y%m%d).db

Then tar the config directory separately. Two backups, one is the DB, one is the config.

HA OS / Supervised

Same idea, but pull the archive from within the VM:

#!/bin/bash
# On HA OS, run inside a SSH addon or via a supervisor addon like "SSH & Webserver"
DATE=$(date +%Y%m%d)
tar czf /mnt/nas/ha-backups/ha-$DATE.tar.gz -C /config .

For HA OS specifically, the built-in Supervisor backup (Layer 1) is enough for on-host, and this tar script is Layer 2.

HA Supervised

Supervised gives you Layer 1 natively, but the config is on the host, not in a VM. Same tar script applies:

tar czf /mnt/nas/ha-backups/ha-$DATE.tar.gz -C /etc/homeassistant .

Where to Store

Not the same disk as HA. Always.

  • NAS over SMB/CIFS — most common; add --backup-compression gzip if space matters
  • WebDAV (Nextcloud, Seafile, self-hosted) — cleaner, easier to sync
  • Cloud storage (Backblaze B2, S3-compatible, Google Drive) — off-prem disaster recovery

For a home NAS, SMB is the pragmatic choice:

# Mount on the HA host (or a docker host)
sudo mkdir -p /mnt/nas
sudo mount -t cifs -o vers=3.0,username=hauser,password=xxx //nas/ha-backups /mnt/nas

Layer 3: The Restore Test

Layer 3 is the whole point. If Layer 1 or 2 has a bug, Layer 3 catches it. If the whole backup strategy is broken, Layer 3 catches that too.

#!/bin/bash
# Restore the latest backup to a staging VM
STAGING_VM=ha-staging
BACKUP=$(ls -t /mnt/nas/ha-backups/*.tar.gz | head -1)
echo "Restoring $BACKUP to $STAGING_VM"

# On the staging VM
ssh staging@10.0.0.50 << 'EOF'
  docker stop homeassistant || true
  docker rm homeassistant || true
  docker run -d --name homeassistant \
    -v /ha/config:/config \
    -p 8123:8123 \
    homeassistant/homeassistant:stable

  # Extract backup
  tar xzf /mnt/nas/ha-backups/$(basename "$BACKUP") -C /ha/config
  docker start homeassistant
  # Let it come up
  sleep 30
  curl -f http://localhost:8123/ && echo "STAGING OK"
EOF

What you're actually testing: 1. Backup extraction works — tarball isn't corrupt 2. DB is restorable — SQLite not corrupted 3. Addons work — configs load, devices reconnect 4. Automations run — pick one and check it triggers 5. Version compatibility — target HA version can read the backup

If any of these fails, you now know before a real disaster.

Yearly Full Restore to Production (Optional)

Some people do a full restore into a fresh VM every year. This is a bigger project — you lose a day — but it catches problems that staging misses (e.g., addon version drift).

Restore: Deployment Matrix

Deployment Backup source Restore steps
HA OS Supervisor UI or ha backup restore UI → Backups → Restore, or ha backup restore --id <id>
HA Container External tar Stop, extract tar, start
HA Supervised Supervisor UI or ha backup restore Same as HA OS
Fresh install from a tar Tarball Install HA, stop, extract, start

Fresh Install from Tar (any deployment)

Useful if your whole host is dead:

  1. Install a fresh HA on a new VM / Raspberry Pi / Docker
  2. Stop HA: docker stop homeassistant
  3. Extract: tar xzf ha-backup.tar.gz -C /config
  4. Ensure permissions: chown -R 1000:1000 /config (HA runs as uid 1000)
  5. Start HA: docker start homeassistant
  6. Wait ~30 seconds for migration

Trap: if HA version on the target differs significantly from the version in the backup, the auto-migration can fail. In that case: 1. Install the older HA version from the backup first 2. Let it upgrade in-place to current 3. Then continue

Failure Scenarios & Playbooks

Scenario 1: Wrote Bad YAML, Broke Everything

Symptom: HA UI shows "Configuration error" banner. Or automation no longer triggers.

Diagnosis:

# On HA
docker exec -it homeassistant python3 -c "
import yaml
with open('/config/configuration.yaml') as f:
    yaml.safe_load(f)
print('valid')
"

Fix — restore just that file:

docker stop homeassistant
cp /mnt/nas/ha-backups/configs/$(date -d yesterday +%F).yaml /config/
docker start homeassistant

If you keep a daily /config archive (not the whole tarball, just the config directory), this is a 30-second fix. If you don't, you're doing a full restore.

Scenario 2: Bad Addon Install Wiped HA

Symptom: HA won't start. docker logs homeassistant shows Python errors on startup.

Fix:

# Identify the bad addon
ls -lt /config/addons/
# Delete it, or move it out
mv /config/addons/<bad-addon> /tmp/bad-addon-backup/

# If HA won't start, restore from tarball
docker stop homeassistant
rm -rf /config
tar xzf /mnt/nas/ha-backups/ha-latest.tar.gz -C /
docker start homeassistant

Scenario 3: Disk Failure (Whole /config Gone)

The worst case. Total loss of /config.

If you have a Supervisor backup (HA OS / Supervised):

Settings → System → Backups → select backup → Restore

Then re-install addons (HACS, Zigbee2MQTT, etc.) one by one. 2-3 hours of work.

If you only have Layer 2 tar (HA Container):

docker stop homeassistant
rm -rf /config
tar xzf /mnt/nas/ha-backups/ha-latest.tar.gz -C /
docker start homeassistant

10 minutes of work, but you've still lost any unbacked state from the last 24 hours (state between daily backup and disk death).

Scenario 4: Upgrade Failed, Need to Roll Back

Symptom: HA upgraded, something broke, you want the previous version.

Two cases:

A. Backup taken just before upgrade:

# HA UI: Settings → System → Backups → Restore (pre-upgrade backup)

15 minutes. Clean rollback.

B. No pre-upgrade backup — you have to downgrade manually:

# On HA OS — downgrade via Supervisor UI
Settings → System → Core → ⋮ → Downgrade

# On HA Container — change image tag
docker stop homeassistant
docker rm homeassistant
docker pull homeassistant/homeassistant:2024.10
docker run -d --name homeassistant ... # same flags as before

Data is safe (the DB is in /config), but HA will warn about running an older version. Some automations may not work — test them.

Always take a backup before upgrading. This is the single most common "oops" in HA.

Scenario 5: Corrupt Backup

Symptom: tar -tvf backup.tar.gz fails, or extraction produces warnings.

Diagnosis:

# Test extraction without writing
mkdir /tmp/test-restore && cd /tmp/test-restore
tar tzf /mnt/nas/ha-backups/ha-latest.tar.gz 2>&1 | tail
# Look for "Read error" or "Unexpected EOF"

Fix: restore from the previous backup:

ls -t /mnt/nas/ha-backups/*.tar.gz | head -3
# Use the second-newest
tar xzf /mnt/nas/ha-backups/ha-latest-1.tar.gz -C /

If all backups are corrupt, check the source:

  • NAS is corrupt → restore from cloud sync
  • Sync is lagging → wait and retry
  • All backups fail the same way → the backup script has a bug (common: --exclude pattern broke)

Encrypting Backups

If your backups go to a NAS that's not fully trusted, or to cloud storage, encrypt them. age is a modern choice:

# Generate key once
age-keygen -o ~/.age/ha.key

# Encrypt
age -r "$(age -r < ~/.age/ha.key)" backup.tar.gz > backup.tar.gz.age

# Decrypt
age -d -i ~/.age/ha.key backup.tar.gz.age > backup.tar.gz

Or GPG:

gpg --default-new-key-algo ed25519
gpg --full-generate-key -a
gpg -c backup.tar.gz  # Encrypt with passphrase
gpg -d backup.tar.gz.gpg

Store the key separately from the backup. If they die together, you've made the problem worse.

HA's Native Backup Tools

HA has a few built-in mechanisms worth knowing:

HA → HA (native migration): on newer HA versions, the "Migrate to new server" wizard handles this for HA OS. Not yet for Container or Supervised.

Third-party backup tools: Backups addon (community), HA Backups Uploader (syncs Supervisor backups to cloud). If you use these, still do the manual tar layer — third-party tools can silently fail.

Notification integration: hook up a Telegram / Slack notification on backup success and failure:

# Add to backup script
curl -X POST "https://api.telegram.org/bot<token>/sendMessage" \
  -d "chat_id=<id>" \
  -d "text=HA backup OK: $(date)"

If backups stop notifying you, that's itself a red flag.

What Goes Wrong (5 Traps)

Trap 1: ~/.ssh permissions on a fresh restore. After extracting, .ssh might be 755 instead of 700. SSH logins silently fail.

Fix: chown -R 1000:1000 /config && chmod -R go-rwx /config/.ssh

Trap 2: SQLite database corruption after kill -9 during backup. Live tar can grab a partially-written DB.

Fix: docker stop before tar, or use SQLite's backup API as shown above.

Trap 3: Addon config restored, addon binary not restored. HACS, Zigbee2MQTT, etc. are not included in tarballs. Reinstall them after restore.

Fix: keep a list of your addons and re-install them: cat /config/.storage/hacs shows what was installed.

Trap 4: Version skew. Backup was taken on HA 2023.11, restored into HA 2024.6. Migration can silently fail.

Fix: check docker inspect homeassistant | grep -i version, match it to the backup date.

Trap 5: Restore path mismatch. Backup has /config/configuration.yaml, your HA installs to /etc/homeassistant/configuration.yaml.

Fix: check the container's -v mount. HA Container defaults to /config. Old-style setups used /etc/homeassistant.

Cheat Sheet

10 Backup / Restore Commands

# List recent backups
ls -lt /mnt/nas/ha-backups/*.tar.gz | head

# Test a backup (dry-run list)
tar tzf /mnt/nas/ha-backups/ha-latest.tar.gz | head

# Test extraction
mkdir /tmp/restore-test && tar xzf /mnt/nas/ha-backups/ha-latest.tar.gz -C /tmp/restore-test

# Extract to running HA (stop first!)
docker stop homeassistant && tar xzf backup.tar.gz -C /config && docker start homeassistant

# Extract to fresh install
mkdir -p /config && tar xzf backup.tar.gz -C /config

# HA OS built-in
ha backup list
ha backup restore --id <id>

# Fresh install of an older HA (post-restore downgrade path)
docker run -d --name ha --network host \
  -v /config:/config \
  homeassistant/homeassistant:2023.11.0

# Sync backup to cloud
rclone copy /mnt/nas/ha-backups b2:ha-backups --transfers 1

# Encrypt
age -r <key> backup.tar.gz > backup.tar.gz.age

# Decrypt
age -d -i ~/.age/ha.key backup.tar.gz.age > backup.tar.gz

5 Backup Config Snippets

Full HA Container cron backup (see Layer 2 script above)

HA OS Supervisor manual backup:

ha backup create --name "manual-$(date +%F)" --type daily

HA Container manual tar:

docker stop homeassistant
docker exec -t homeassistant tar czf - -C /config . > ha-$(date +%F).tar.gz
docker start homeassistant

Age encryption for cloud upload:

age -r <key> backup.tar.gz > backup.tar.gz.age
rclone copy backup.tar.gz.age b2:ha/

Restore from encrypted backup:

rclone copy b2:ha/latest.tar.gz.age ./latest.tar.gz.age
age -d -i ~/.age/ha.key latest.tar.gz.age > latest.tar.gz
docker stop homeassistant && tar xzf latest.tar.gz -C /config && docker start homeassistant

Backup Checklist — 20 Questions

# Question Should be
1 Do I have an off-box backup? Yes
2 Are backups encrypted at rest? Yes if not on trusted storage
3 Is the backup script logged? Yes, with failure notification
4 Do I know where backups live? Yes
5 Have I restored from a backup in the last 6 months? Yes, at least once
6 Do I have HA's Supervisor backup enabled? Yes (HA OS / Supervised)
7 Do I exclude core/backup in tar? Yes (avoid duplicates)
8 Do I docker stop before tar? Yes
9 Do I keep addon lists separately? Yes (for reinstall)
10 Do I back up /config/.ssh? Yes (with permission preservation)
11 Do I back up addon data or just config? Data (Mosquitto DB, Zigbee network)
12 Is my backup retention ≥ 30 days? Yes
13 Is my backup retention ≥ 1 year (monthly)? Yes
14 Have I tested restoration into a fresh install? Yes, at least once
15 Do I have a pre-upgrade backup ritual? Yes
16 Do I get notified when a backup fails? Yes
17 Do I back up HA's secrets.yaml? Yes (via /config tar)
18 Do I back up my .env files? Yes (in /config/.env*)
19 Do I have a restore runbook for a fresh machine? Yes
20 Do I practice restores, not just take them? Yes, quarterly

If you answer "No" to any of the top 5, fix those first. Everything else is secondary.

Authoritative References


Written for ops.jiunotes.top. Original work based on production HA disaster-recovery experience. Not copied from any third-party blog. CC BY-NC-SA 4.0.