Skip to content
Go back

Home Assistant Backups That Actually Restore

By SumGuy 13 min read
Home Assistant Backups That Actually Restore

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:

What it doesn’t grab:

On a Container install, you’re also missing:


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:

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:

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: single

Add 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:

Terminal window
# From the HA host
cp /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.

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:
db_url: postgresql://ha_user:[email protected]:5432/homeassistant
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:

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:

Terminal window
docker exec z2m tar -czf - -C /app/data . | gzip > z2m_backup_$(date +%Y%m%d).tar.gz

Store 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-stopped

When you restore:

  1. Version mismatch kills you. If the backup was made on 2025.6 and you restore to 2025.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 first
    docker pull homeassistant/home-assistant:2025.8
    # Update compose, test, then upgrade
  2. The bind mount must exist and have the right permissions. If /data/homeassistant doesn’t exist or is owned by root, the container won’t start.

  3. Secrets are encrypted inside the backup. The .storage/auth key is included. But if you’re restoring to a different host, make sure that key decrypts.

Pre-restore checklist for a container rebuild:

Then:

Terminal window
docker-compose down
# Extract backup into config
cd /data/homeassistant && tar -xzf homeassistant_backup_*.tar.gz
docker-compose up -d
docker logs -f homeassistant
# Wait 5 minutes for startup

From 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:

The safe path:

  1. Export HA’s native backup (Settings → System → Backups).
  2. Spin up a fresh HA Container on the new host.
  3. Restore the backup via Settings → System → Backups → (upload file).
  4. Add missing addons as separate containers (Zigbee2MQTT, MariaDB, etc.) and reconfigure them.
  5. 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 lat
longitude: !secret lon
api_key: !secret openweather_key

These 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:

Terminal window
# From the HA host (SSH access required)
cat /config/secrets.yaml > /offline/backup/secrets_plaintext_backup.txt
chmod 600 /offline/backup/secrets_plaintext_backup.txt

Store 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:

  1. Pick a non-critical day. Saturday afternoon, when nobody’s automations matter.

  2. Restore to a test instance. Don’t restore over your production HA. Spin up a second container or use a spare Pi.

  3. Download the latest backup, put it somewhere accessible.

  4. Do a cold restore:

    Terminal window
    docker-compose down
    rm -rf /test/homeassistant/config/*
    tar -xzf homeassistant_backup_2026_10_05.tar.gz -C /test/homeassistant/config/
    docker-compose up -d
    docker logs -f homeassistant
    # Wait 10 minutes
  5. 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
  6. 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.
  7. Document what broke, what you had to fix manually, what surprised you. That’s the list you fix on the production backup.

  8. 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 instance

And 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:

  1. Enable Settings → System → Backups → Services and test a manual backup to an off-site location.
  2. Automate daily backups with an automation that keeps at least 10 days of backups off-site.
  3. Back up the secrets key separately, offline.
  4. If you’re using Zigbee2MQTT, back up the coordinator via Z2M’s own backup feature.
  5. If you’re using a Container, keep a copy of docker-compose.yml and the pinned image version.
  6. 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.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
Boundary vs Teleport

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts