Zigbee2MQTT sin dispositivos tras reiniciar Proxmox: causa y solución definitiva
El fin de semana pasado cambié la batería de mi SAI Riello. Nada del otro mundo: apagar los dispositivos conectados, sustituir la batería, volver a encender. Lo que no esperaba es que al reiniciar Proxmox, todos mis dispositivos Zigbee desaparecieran de Home Assistant sin dejar rastro aparente.
Tres días después de investigar, el diagnóstico es simple: el dongle Sonoff Zigbee 3.0 USB Dongle Plus cambió de /dev/ttyUSB0 a /dev/ttyUSB2 tras el reinicio, rompiendo el passthrough USB al LXC donde corre Zigbee2MQTT. En este post documento el proceso de diagnóstico completo y la solución definitiva para que no vuelva a pasar.
El contexto: Zigbee2MQTT en un LXC de Proxmox
Mi stack domótico corre así:
- Proxmox como hipervisor en un servidor dedicado
- LXC 109 con Zigbee2MQTT (Debian)
- LXC con Mosquitto (broker MQTT)
- VM con Home Assistant OS
- Sonoff Zigbee 3.0 USB Dongle Plus pasado al LXC 109 mediante passthrough USB
El passthrough USB en LXC de Proxmox funciona montando el dispositivo del host dentro del contenedor mediante entradas en el fichero /etc/pve/lxc/<ID>.conf. Llevaba funcionando sin problemas durante años.
Los síntomas
Al arrancar Home Assistant tras el reinicio de Proxmox, las notificaciones empezaron a llegar: dispositivos Zigbee no disponibles. Sensores, enchufes inteligentes, controladores LED... todos offline.
Zigbee2MQTT, en cambio, no mostraba ningún error obvio en la interfaz — simplemente no arrancaba correctamente y se reiniciaba en bucle.
Diagnóstico paso a paso
1. Revisar los logs de Zigbee2MQTT
Lo primero es siempre mirar los logs. Dentro del LXC:
journalctl -u zigbee2mqtt -n 100 --no-pagerEl error era claro y repetitivo:
error: z2m: Error: No such file or directory, cannot open
/dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_bc58aeb0acdbed11b349f12d62c613ac-if00-port0Z2M no encontraba el dongle. El dispositivo configurado en configuration.yaml no existía dentro del LXC.
2. Verificar el dispositivo dentro del LXC
ls -la /dev/serial/by-id/El symlink existía dentro del LXC:
lrwxrwxrwx 1 root root 13 Mar 15 09:48
usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_[...]-if00-port0 -> ../../ttyUSB2Pero apuntaba a ttyUSB2, que no existía en el LXC. Y los permisos del ttyUSB0 que sí existía eran ---------- — cero permisos para todos.
3. Verificar en el host Proxmox
En la shell de PVE:
journalctl -k --since "2026-03-01" | grep -i "sonoff\|cp210x\|10c4" | tail -10Resultado revelador:
Mar 15 09:48:08 pve kernel: cp210x converter now attached to ttyUSB2El Sonoff se había asignado a ttyUSB2 al arrancar, probablemente porque otro dispositivo USB (el RFXtrx433E, que usa el driver ftdi_sio) se inicializó antes y ocupó ttyUSB0.
4. La causa raíz
El número ttyUSBx lo asigna el kernel dinámicamente según el orden en que detecta los dispositivos USB al arrancar. No es fijo. Antes del reinicio el Sonoff era ttyUSB0; después pasó a ser ttyUSB2.
La configuración del LXC montaba ttyUSB0 y ttyUSB1 pero no ttyUSB2, así que el dongle era visible en /dev/serial/by-id/ (porque ese directorio se montaba completo), pero el symlink apuntaba a un ttyUSB2 que no existía dentro del contenedor.
La respuesta estaba en casa
Lo más frustrante del diagnóstico es que ya había resuelto exactamente este problema años atrás. Cuando monté Zigbee con OpenHAB en una Raspberry Pi, el coordinador CC2531 y el RFXtrx433E tenían el mismo problema de nombres dinámicos. La solución fue idéntica: una regla udev en /etc/udev/rules.d/10-local.rules con SYMLINK+="ttyUSB-ZIGBEE" para fijar el nombre del dispositivo.
Lo documenté en detalle en el post Zigbee con OpenHAB y CC2531. La técnica es exactamente la misma, solo cambia el contexto: ahí era una Raspberry con OpenHAB, aquí es un host Proxmox con un LXC.
El fallo fue mío: al migrar de la Raspberry a Proxmox apliqué el passthrough USB pero no trasladé la regla udev. Confiaba en que el mount de /dev/serial/by-id era suficiente, y durante años lo fue — hasta que un reinicio en frío cambió el orden de detección del kernel.
La solución: reglas udev con nombre fijo
La solución correcta es asignar un nombre permanente al dongle mediante una regla udev en el host Proxmox, independientemente del número ttyUSBx que le asigne el kernel en cada arranque.
Paso 1: Crear la regla udev en PVE
nano /etc/udev/rules.d/99-usb-serial.rules# Sonoff Zigbee 3.0 USB Dongle Plus
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ATTRS{serial}=="bc58aeb0acdbed11b349f12d62c613ac", SYMLINK+="ttyUSB-Sonoff"Los valores idVendor, idProduct y serial se obtienen con:
udevadm info -a -n /dev/ttyUSB2 | grep -E 'idVendor|idProduct|"{serial}"' | head -5O directamente desde el bus USB si el dispositivo no está disponible como tty:
udevadm info /sys/bus/usb/devices/1-3.3 | grep -E 'SERIAL|VENDOR|PRODUCT'Aplicar las reglas:
udevadm control --reload-rules && udevadm triggerVerificar que el symlink se ha creado:
ls -la /dev/ttyUSB-*
# lrwxrwxrwx 1 root root 7 Mar 17 07:42 /dev/ttyUSB-Sonoff -> ttyUSB2Paso 2: Actualizar la configuración del LXC
Editar /etc/pve/lxc/109.conf y reemplazar los mounts de ttyUSBx por el nombre fijo:
Antes:
lxc.mount.entry: /dev/serial/by-id dev/serial/by-id none bind,optional,create=dir
lxc.mount.entry: /dev/ttyUSB0 dev/ttyUSB0 none bind,optional,create=file
lxc.mount.entry: /dev/ttyUSB1 dev/ttyUSB1 none bind,optional,create=file
lxc.mount.entry: /dev/ttyACM0 dev/ttyACM0 none bind,optional,create=file
lxc.mount.entry: /dev/ttyACM1 dev/ttyACM1 none bind,optional,create=fileDespués:
lxc.mount.entry: /dev/ttyUSB-Sonoff dev/ttyUSB-Sonoff none bind,optional,create=fileReiniciar el LXC:
pct reboot 109Paso 3: Actualizar configuration.yaml de Zigbee2MQTT
Dentro del LXC, editar /opt/zigbee2mqtt/data/configuration.yaml:
Antes:
serial:
port: >-
/dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_bc58aeb0acdbed11b349f12d62c613ac-if00-port0Después:
serial:
port: /dev/ttyUSB-SonoffReiniciar Zigbee2MQTT:
systemctl restart zigbee2mqtt
journalctl -u zigbee2mqtt -fSi todo ha ido bien, verás los dispositivos cargando en los logs y el Zigbee2MQTT started! final.
¿Por qué no había pasado antes?
Llevaba años con esta configuración sin problemas porque el servidor raramente se reinicia. Los reinicios controlados que había hecho antes no cambiaban el orden de detección USB porque los dispositivos siempre estaban conectados y el kernel los detectaba en el mismo orden.
El cambio de batería del SAI implicó apagar completamente el servidor, y en ese arranque en frío el kernel detectó los dispositivos en un orden ligeramente diferente, asignando ttyUSB2 al Sonoff en lugar de ttyUSB0.
Conclusión
El passthrough USB en LXC de Proxmox es frágil si se basa en nombres dinámicos como ttyUSBx. La solución correcta es siempre usar nombres fijos mediante reglas udev en el host, montando esos nombres fijos en el LXC. Así el setup es resiliente a reinicios, reconexiones y cambios en el orden de detección USB del kernel.
Si tienes más de un dispositivo USB serial en Proxmox (en mi caso también tengo un RFXtrx433E pasado por USB a la VM de Home Assistant), te recomiendo crear una regla udev para cada uno y documentar los idVendor, idProduct y serial de cada dispositivo. Ese fichero /etc/udev/rules.d/99-usb-serial.rules es el seguro de vida de tu infraestructura domótica.
¿Te ha sido útil?
Ayúdame a mejorar con tu puntuación y comentarios.
Comentarios
Los comentarios están gestionados por GitHub Discussions. Necesitas una cuenta de GitHub para participar. ¡Es gratis y rápido!