A Home Assistant Backup You’ve Never Restored Is a Wish, Not a Backup.
Your Home Assistant setup is humming. Automations are running. The house knows when you’re home. Devices respond. Life is good.
Then your SSD dies at 3 AM. Or you need to move HA to a new mini PC. Or the container corrupts itself. You reach for that backup you’ve been taking every night, hit restore, and… something’s wrong. Your custom integrations are gone. Zigbee2MQTT won’t reconnect. Automations reference sensors that don’t exist anymore.
Welcome to the difference between having a backup and having a backup that works.
Home Assistant’s built-in backup tool is solid—but it’s not a complete disaster recovery plan. It’s one piece. And if you’re running HA in a container, on a NAS, or with external databases and zigbee coordinators, you’re leaving critical stuff unprotected.
Let’s fix that.
The Backup Variants: What You’re Actually Protecting
First, let’s be clear: “Home Assistant” isn’t a monolith. You could be running:
Home Assistant OS (the full greenfield setup): Runs as a hypervisor on Proxmox, a mini PC, or a Raspberry Pi. The built-in backup grabs everything—the OS, all addons, your config, automations, the database. It’s the easiest path and the most self-contained.
Supervised (legacy, running on a host OS like Debian): The backup grabs HA Core, addons, and config. But the underlying OS? That’s your problem. Your supervisor installation itself isn’t backed up; if the OS dies, you’re restoring onto a fresh system.
Home Assistant Container (Docker): You get just the HA Core config, automations, the .storage/ directory. The container image, volumes, the docker-compose.yml that runs it, the host OS—all outside the backup. Common gotcha: you restore config to a different container image version and nothing works.
Home Assistant Core (Python venv, no containerization): You’re getting config and automations. Dependencies, recorder database (if external Postgres or MariaDB), custom integrations that live outside custom_components/—nope.
The built-in backup tool only knows about one of these setups. If you’re in a container and you treat the backup like a full system snapshot, you’re in for a bad time.
What the Built-In Backup Actually Covers (and What It Misses)
Home Assistant’s native backup (accessible in Settings → System → Backups) creates a .tar.gz that includes:
- config/ — automations.yaml, scripts.yaml, scenes.yaml, configuration.yaml
- .storage/ — entity registry, device registry, secrets, all the internal state
- addons/ (HA OS / Supervised only) — addon configurations and data
- Home Assistant database — history, stats, the default SQLite recorder
What it doesn’t grab:
- custom_components/ on Container / Core installs (it gets them on OS, but read on)
- ESPHome flashed configs — the device has the binary; your Home Assistant dashboard only controls it. If the device dies, you’ve lost the build.
- Zigbee2MQTT coordinator data — the zigbee database, the network topology, pairing history. HA’s backup ignores it.
- External Postgres/MariaDB for recorder — if you’ve moved the database outside HA to share it with other services, that DB isn’t in the backup.
- Add-on volumes and persistent data (on Container) — MariaDB add-on, MQTT broker data, ESPHome build outputs.
- Secrets key — HA encrypts your backup with a key stored in the .storage directory. But if you lose that backup, you lose the key, and a brand-new HA instance can’t decrypt old backups.
- Z2M secrets — if you’re using Zigbee2MQTT via addon or container, its
configuration.yamland the coordinator’s NVram backup live somewhere else.
On a Container install, you’re also missing:
- The docker-compose.yml — you need to remember ports, volumes, environment variables.
- The Docker image tag — restore to
image: homeassistant/home-assistant:latestand you might get 2026.10 when the backup was 2025.8. Incompatible changes wreck you. - Bind mounts and volumes — where does
/configpoint? What’s the permissions?
The Off-Site Strategy: Automated Backups to Actually Trusted Places
Home Assistant has a built-in feature for this: Settings → System → Backups → Services. You can send full or partial backups to:
- Google Drive (via Home Assistant Cloud—costs money, but easy)
- SMB/CIFS (a Windows share or Samba server on your network)
- NFS (a NAS share)
- Nextcloud (if you run it)
- WebDAV (some NAS boxes)
Pick one that’s not in your house. A NAS in the basement gets wiped if the house burns down. Google Drive or a Nextcloud instance on a different network? That survives.
Here’s the catch: the built-in service only handles the HA backup .tar.gz. It doesn’t help with:
- Custom components (if running Container/Core)
- Zigbee2MQTT data
- External databases
- ESPHome device binaries
So use the built-in service for HA’s native backup, then supplement with custom automation that handles the rest.
Concrete: The Off-Site + Encryption Setup
Step 1: Enable HA’s Built-In Backup Service
Go to Settings → System → Backups → Services and enable one:
SMB Share example: Host: 192.168.1.50 Username: ha_backup Password: (use a unique password, not your admin) Share: backups
Nextcloud example: URL: https://nextcloud.example.com Username: ha_backup_user Password/app_token: (use an app-specific token) Remote path: /HA_Backups/Test it by running a manual backup first. It’ll show up in the remote share.
Step 2: Automation to Back Up Supporting Files
Create an automation that nightly zips up custom components, Z2M config, etc., and pushes it somewhere. Here’s a template for Container users:
alias: "Backup: Daily Off-Site + Custom Files"description: "HA backup + custom components + Z2M data to NFS"triggers: - trigger: time at: "02:00:00"actions: - action: hassio.backup_full data: {} - service: shell_command.backup_custom_files data: {}mode: singleAdd this to configuration.yaml:
shell_command: backup_custom_files: | #!/bin/bash BACKUP_DIR="/mnt/nfs_backups/ha_support_$(date +%Y%m%d_%H%M%S)" mkdir -p "$BACKUP_DIR"
# Custom components if [ -d /config/custom_components ]; then cp -r /config/custom_components "$BACKUP_DIR/" fi
# Zigbee2MQTT addon/container config if [ -d /config/addons/zigbee2mqtt/data ]; then cp -r /config/addons/zigbee2mqtt/data "$BACKUP_DIR/z2m_data" fi
# ESPHome build outputs (optional, large) if [ -d /config/esphome ]; then cp -r /config/esphome "$BACKUP_DIR/" fi
# Log summary echo "Backup: $(date)" > "$BACKUP_DIR/backup.log" tar -czf "$BACKUP_DIR.tar.gz" -C "$(dirname "$BACKUP_DIR")" "$(basename "$BACKUP_DIR")" rm -rf "$BACKUP_DIR"Mount an NFS share or SMB share in your docker-compose.yml to make /mnt/nfs_backups available.
Step 3: Encrypt the Secrets Key Separately
Home Assistant generates a unique encryption key for backups. It’s stored in .storage/auth. Back this up offline:
# From the HA hostcp /config/.storage/auth /some/offline/location/ha_secrets_key_$(date +%Y%m%d).json# Burn this to USB, email it to yourself, store it in a password manager—somewhere separate.If you lose this key and your backups, you can’t restore them. Write that down.
Step 4: Configure Recorder for External Database (Optional, but Recommended)
If you’re running HA in a container, keep your recorder database external (PostgreSQL or MariaDB). That way, the DB survives HA crashes and is easier to back up separately.
In configuration.yaml:
recorder: purge_keep_days: 30 exclude: entity_globs: - sensor.uptime - sensor.time*Back up the Postgres/MariaDB instance separately (cron + pg_dump / mysqldump, sent to NFS/S3/wherever). Your HA backup is now smaller and faster.
Zigbee2MQTT: The Coordinator That Backup Forgot
If you’re using Zigbee2MQTT (via addon or container), you have a separate device—the coordinator (a ConBee II, RaspBee, or USB stick). Its internal NVram holds:
- The network address (PAN ID)
- Pairing history
- Device trust relationships
- The firmware state
Home Assistant’s backup doesn’t touch it. The MQTT messages and the HA entity registry are backed up, but if the coordinator dies and you restore HA on a new device without the coordinator’s NVram, your devices won’t rejoin.
Solution: Use Zigbee2MQTT’s built-in backup.
If you’re running Z2M as a Home Assistant add-on:
Settings → Add-ons → Zigbee2MQTT → Backups (yes, it has its own backups)If you’re running Z2M as a separate container, SSH into that container and back up manually:
docker exec z2m tar -czf - -C /app/data . | gzip > z2m_backup_$(date +%Y%m%d).tar.gzStore it alongside your HA backups in the same off-site location.
Container-Specific Gotchas: Migration and Rebuild
You’re running HA in Docker. Your docker-compose.yml looks like:
version: '3.8'services: homeassistant: image: homeassistant/home-assistant:2025.8 container_name: homeassistant volumes: - /data/homeassistant:/config - /etc/localtime:/etc/localtime:ro environment: - TZ=America/Chicago ports: - "8123:8123" restart: unless-stoppedWhen you restore:
-
Version mismatch kills you. If the backup was made on
2025.6and you restore to2025.10, you might hit breaking changes. Keep the image pinned to the same version as your backup, restore, then upgrade.Terminal window # Restore to the old version firstdocker pull homeassistant/home-assistant:2025.8# Update compose, test, then upgrade -
The bind mount must exist and have the right permissions. If
/data/homeassistantdoesn’t exist or is owned by root, the container won’t start. -
Secrets are encrypted inside the backup. The
.storage/authkey is included. But if you’re restoring to a different host, make sure that key decrypts.
Pre-restore checklist for a container rebuild:
- HA backup file downloaded to
/data/homeassistant/ - docker-compose.yml matches the original (same ports, volumes, image tag)
- The config directory exists and is writable by the HA container user (UID 1000)
- Any dependent services (MariaDB, Postgres, MQTT) are running and accessible
- Zigbee2MQTT coordinator is plugged in (if you’re using it)
- Custom components are back in
/data/homeassistant/custom_components/(from your separate backup)
Then:
docker-compose down# Extract backup into configcd /data/homeassistant && tar -xzf homeassistant_backup_*.tar.gzdocker-compose up -ddocker logs -f homeassistant# Wait 5 minutes for startupFrom HA OS to Container: A Harder Migration
If you’re moving from Home Assistant OS (or Supervised) to a Container install, the built-in backup helps, but you’re not just restoring—you’re translating.
What changes:
- Addons become docker containers or systemd services you manage yourself.
- Supervisor settings (hardware, network) don’t translate; you’re configuring the host OS from scratch.
- The
.storage/directory moves with you (automations, histories, entity registries all come), but addon data might not.
The safe path:
- Export HA’s native backup (Settings → System → Backups).
- Spin up a fresh HA Container on the new host.
- Restore the backup via Settings → System → Backups → (upload file).
- Add missing addons as separate containers (Zigbee2MQTT, MariaDB, etc.) and reconfigure them.
- Test automations and integrations thoroughly before decommissioning the old system.
It’s not a one-click restore; expect manual reconfiguration.
Secrets: The Thing You’re Probably Forgetting
Home Assistant stores secrets two ways:
In secrets.yaml:
latitude: !secret latlongitude: !secret lonapi_key: !secret openweather_keyThese are backed up. But the actual values are in .storage/secrets, which is encrypted with the backup key.
In the UI (Automations, Helpers, Scenes):
Long API keys are encrypted and stored in .storage/core.config_entries. Backups include them, but if you restore to a new HA instance before restoring the backup, the decryption key won’t match.
Gotcha: If you lose the backup file and the HA instance, you’ve lost your secrets forever. There’s no recovery.
Action: After you set up HA, export your secrets:
# From the HA host (SSH access required)cat /config/secrets.yaml > /offline/backup/secrets_plaintext_backup.txtchmod 600 /offline/backup/secrets_plaintext_backup.txtStore that plaintext file somewhere very offline and very safe (USB in a locked drawer, printed and laminated—whatever paranoia level you’re comfortable with). You need it if HA burns down completely.
The Restore Drill: The Checklist That Catches Everything
A backup you’ve never tested is theater. Run a drill quarterly:
-
Pick a non-critical day. Saturday afternoon, when nobody’s automations matter.
-
Restore to a test instance. Don’t restore over your production HA. Spin up a second container or use a spare Pi.
-
Download the latest backup, put it somewhere accessible.
-
Do a cold restore:
Terminal window docker-compose downrm -rf /test/homeassistant/config/*tar -xzf homeassistant_backup_2026_10_05.tar.gz -C /test/homeassistant/config/docker-compose up -ddocker logs -f homeassistant# Wait 10 minutes -
Walk through the checklist:
- HA is accessible at http://localhost:8123
- Automations exist and show in Settings → Automations
- Custom components appear in Settings → Devices & Services
- Automations run (check the trace in Settings → Automations → Traces)
- Zigbee2MQTT devices are paired (Settings → Devices, look for Zigbee devices)
- Sensors that depend on custom components report values (not “unavailable”)
- Historical data is there (Settings → About, check database size)
- You can view the system log and it’s not full of errors
-
Check for silent failures:
- A template sensor that depends on a missing custom integration won’t crash HA, but it’ll be unavailable forever.
- A Z-Wave device won’t rejoin if the coordinator isn’t running.
- A webhook automation won’t fire if you forgot to recreate the webhook secret.
-
Document what broke, what you had to fix manually, what surprised you. That’s the list you fix on the production backup.
-
Destroy the test instance (or leave it running and test again next quarter).
A Restore Drill That Catches the Holes
Here’s the full checklist automation for your production setup. Add it to automations.yaml:
- alias: "Backup: Monthly Restore Drill Reminder" description: "Nag you to test a restore" triggers: - trigger: time at: "10:00:00" conditions: - condition: time weekday: - fri week: 1 # First Friday of the month actions: - service: notify.notify data: title: "🔥 Restore Drill Due" message: | Time for a quarterly restore drill. 1. Spin up test HA instance 2. Restore latest backup 3. Walk the checklist (automations, devices, DB size) 4. Document issues 5. Destroy test instanceAnd a backup status automation to keep you honest:
- alias: "Backup: Failed Backup Alert" description: "Alert if last backup is >36 hours old" triggers: - trigger: time_pattern hours: "12" actions: - service: notify.notify data: title: "⚠️ Backup Stale" message: | Last backup: {{ state_attr('system_health.backup_status', 'last_backup') }} Check Services and review if automated backup is running.The Takeaway
A Home Assistant backup is like insurance for your house. You can have it, but if you’ve never tested the claim, you don’t actually have it.
Start here:
- Enable Settings → System → Backups → Services and test a manual backup to an off-site location.
- Automate daily backups with an automation that keeps at least 10 days of backups off-site.
- Back up the secrets key separately, offline.
- If you’re using Zigbee2MQTT, back up the coordinator via Z2M’s own backup feature.
- If you’re using a Container, keep a copy of
docker-compose.ymland the pinned image version. - Run a restore drill quarterly on a test instance and document what breaks.
Your 3 AM self—the one staring at a dead SSD—will thank you.