Skip to content
Infrastructure

Migrating Home Assistant from Raspberry Pi to Proxmox

By Victor Da Luz
home-assistant proxmox raspberry-pi zigbee usb-passthrough homelab ansible

Home Assistant had been running on a dedicated Raspberry Pi 4 for years. It worked fine. But it was the last major service living outside Proxmox VE - no cluster backups, no ZFS replication, no Ansible management. Just a Pi doing its thing on a shelf.

I’d had a standby Proxmox VM sitting idle for a while, prepared for exactly this move. The main thing blocking me was time and a RAM upgrade for the nodes. Once both were sorted, the migration came together over a couple of sessions.

The setup before

  • Primary: pi1.internal - Raspberry Pi 4, Home Assistant OS, 38 Zigbee devices paired, 192.168.x.x
  • Standby: ha-standby.internal - Proxmox VM (VMID 204, node01), powered off, 192.168.x.x
  • Traefik was already routing homeassistant.example.net and could balance between primary and standby

The standby VM had been set up during an earlier prep pass: 1 vCPU, 2GB RAM, 32GB root disk, 8GB swap, QEMU guest agent enabled, on the same VLAN as the Pi.

Waiting for RAM

I started testing in November - powered on the standby VM, confirmed it was reachable at ha-standby.internal, and verified that Traefik was returning 200 from homeassistant.example.net. Home Assistant came up on the VM without issues.

Then I stopped. I wanted to validate the Zigbee USB passthrough before cutting over, and the Proxmox nodes didn’t have enough memory to test it comfortably. Powered the VM back off and waited for the RAM upgrade to land.

After the nodes went to 32GB each, I picked this up again.

Backup restore

With the VM running and verified, I took a full backup from the Pi: Settings → System → Backups → Create backup. Transferred the .tar to the VM via the “Upload backup” option in the Home Assistant UI, then restored it.

The restore preserved automations, integrations, scripts, dashboards, and the Zigbee device database. All 38 devices came back without re-pairing - the Zigbee integration reads device state and mesh topology from the backup. A few cloud integrations needed re-authentication (token reset on restore), but nothing else.

USB passthrough for the Zigbee coordinator

The Home Assistant ZBT-1 (formerly the SkyConnect) was plugged into node01. This is the Zigbee USB coordinator. Without it, the ZHA integration has no radio.

My first instinct was port-based passthrough: find the device in lsusb, note the bus/port, set usb0: host=1-6.1 in the VM config. That works until the host power-cycles. Linux USB enumeration order isn’t guaranteed after a full restart, so the device shows up on a different port and the passthrough config points at nothing. Zigbee stops working.

The correct approach is vendor:product IDs:

# In the Proxmox VM config
usb0: host=10c4:ea60

10c4:ea60 is the Silicon Labs chipset ID used in the ZBT-1. It’s stable across reboots regardless of which physical port the dongle occupies. Set this once and the passthrough survives power outages.

After adding the passthrough and starting the VM, the coordinator appeared inside Home Assistant OS at /dev/ttyUSB0. I pointed ZHA at that path and the devices came online.

Network cutover

Once automations were running and all devices were responding, I did the cutover:

  1. Stopped Home Assistant on pi1.internal
  2. Reassigned the primary IP static lease from pi1.internal to the VM
  3. Updated DNS to point ha.internal at the VM
  4. Updated the Traefik config to remove pi1.internal from the backend
  5. Renamed the VM hostname from ha-standby to ha in Proxmox and updated the state files

The whole cutover took about five minutes. Traefik started routing to the VM immediately; DNS propagated to the rest of the stack within the lease interval.

A DHCP gotcha

After the migration looked stable, I started seeing “Connection lost” banners in the Home Assistant UI every 15 minutes. The connection would recover in a few seconds, but it was consistent and repeating.

The cause was the DHCP lease time on the HA VLAN: 30 minutes. Home Assistant OS uses NetworkManager, which renews at T1 - 50% of lease time, so every 15 minutes. Each renewal triggers a brief network stack reconfiguration that drops all active TCP connections, including WebSockets. Hence the periodic disconnects.

The fix was raising the lease time to 24 hours:

/ip dhcp-server network set [find where address="192.168.x.x/24"] lease-time=24h

With 24h leases, renewals happen at 12h intervals instead. The disconnects stopped immediately. Any service that relies on long-lived WebSocket connections will run into this if your DHCP lease time is short - worth checking if you see periodic “connection lost” events you can’t otherwise explain.

Storage migration to ZFS

The VM was running on local-lvm (LVM-thin storage) on node01. That works but doesn’t support ZFS replication across nodes. I migrated all three disks to pve-containers, the ZFS pool:

  • Root disk: 32GB
  • Swap disk: 8GB
  • EFI disk: 4MB

Once on ZFS storage, I configured replication from node01 to node02 at a 15-minute interval. The initial sync completed without issues. If node01 fails, I have a recent snapshot on node02 and a known restore path.

The trade-off: USB passthrough and failover

Worth being explicit about this: the ZBT-1 is physically attached to node01. If Proxmox HA ever automatically fails the VM over to node02, the USB passthrough config becomes invalid. The radio doesn’t travel with the VM. ZHA reports the device path as not found.

The VM is effectively node01-pinned as long as the coordinator is plugged in there. Transparent automatic failover doesn’t work for VMs with physical USB dependencies.

For my setup, that’s acceptable. If node01 goes down, I can restore from the node02 replication snapshot, physically move the dongle, update the passthrough config, and restart. It’s a manual step, but it’s a rare event and the recovery path is clear.

Decommissioning pi1.internal

With the VM stable and replication running for a few days, I retired the Pi:

  • Stopped Home Assistant on pi1.internal
  • Removed the DHCP static lease
  • Removed the DNS records
  • Updated Ansible inventory to remove pi1.internal
  • Marked it as decommissioned in the state files

The Raspberry Pi 4 hardware went into the spare parts bin.

What I learned

  • Zigbee device migration is clean with a full backup. No re-pairing, no reconfiguration. Restore, let the devices reconnect, done.
  • Use vendor:product IDs for USB passthrough, not port numbers. Port-based IDs break after power cycles. 10c4:ea60 for the ZBT-1 survives any reboot.
  • Short DHCP lease times cause WebSocket disconnects. 30 minutes is too short for services with persistent connections. 24 hours (or longer) for static-reserved VLANs.
  • USB passthrough pins a VM to a node. Know this going in. Replication gives you a fast restore path, but transparent failover requires USB-over-IP or moving the hardware.
  • ZFS replication requires ZFS storage. You can’t replicate LVM-thin across nodes. Moving the VM to ZFS storage first is a prerequisite.

Disclosure: This article contains affiliate links. If you purchase through these links, I may earn a commission at no extra cost to you.

Related reading

Infrastructure

BirdNET-Go, a doorbell cam, and a dynamic mic from the drawer

Deploying a self-hosted bird-sound ID service on the homelab for $0: the BirdNET-Pi to BirdNET-Go switch, USB audio passthrough into Docker-in-LXC, pulling doorbell audio through Frigate go2rtc, and the Ansible YAML bug that silently stopped clips from saving.

Read
Infrastructure

Consolidating audiobooks and ebooks into a single Audiobookshelf

I was running two media servers, Audiobookshelf for audiobooks and Kavita for ebooks, when one could do both. Rebuilding the homelab in v3 was the excuse to merge them: one Ansible-deployed Audiobookshelf, local-disk storage, and a USB-drive ZFS scare in the middle of the migration.

Read
Infrastructure

Researching BirdNET-Pi for backyard bird detection

Before buying any hardware, I researched what it would take to run a self-hosted bird-sound ID service on the homelab: which BirdNET-Pi to use, the hardware it needs, and how it fits a segmented network. Here is the plan I landed on, and why I shelved it.

Read

Ready to Transform Your Career?

Let's work together to unlock your potential and achieve your professional goals.