Install Home Assistant on Ubuntu

Why Ubuntu Specifically?

Ubuntu is the most common server distribution for self-hosting, and Home Assistant runs well on it with Docker. But Ubuntu has a few platform-specific requirements that trip people up: host networking configuration for device discovery, udev rules for stable USB dongle paths, D-Bus access for Bluetooth integrations, and NetworkManager for Home Assistant’s network management features.

This guide covers the Ubuntu-specific setup. For the general Docker Compose walkthrough, see the main Home Assistant guide.

Prerequisites

  • Ubuntu 22.04 LTS or 24.04 LTS (server or desktop)
  • Docker and Docker Compose installed (guide)
  • 2 GB of RAM minimum (4 GB recommended)
  • 32 GB of free disk space
  • A Zigbee or Z-Wave USB dongle (optional, for local device control)
  • Root or sudo access

Install Docker on Ubuntu

If Docker is not installed yet:

sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER

Log out and back in for the group change to take effect.

Platform Setup

Install Required System Packages

Home Assistant’s Bluetooth and network integrations depend on system services. Install them:

sudo apt install -y dbus network-manager bluez
  • dbus — Inter-process communication. Home Assistant uses D-Bus to communicate with BlueZ for Bluetooth device discovery.
  • network-manager — Home Assistant’s network integration can manage your server’s network connections through NetworkManager. Not strictly required, but enables the Network panel in HA’s settings.
  • bluez — Linux Bluetooth stack. Required for Bluetooth integrations (ESPHome Bluetooth Proxy, iBeacons, BLE sensors).

Configure NetworkManager

By default, Ubuntu Server uses Netplan with networkd as the renderer. Home Assistant’s network integration works best with NetworkManager. To switch:

# Check current Netplan renderer
cat /etc/netplan/*.yaml

Edit your Netplan config (e.g., /etc/netplan/01-netcfg.yaml) to use NetworkManager:

network:
  version: 2
  renderer: NetworkManager

Apply the change:

sudo netplan apply

If you prefer to keep networkd (which is fine for servers), Home Assistant will still work — you just will not see network management options in the HA UI.

Set Up USB Device Passthrough with udev Rules

If you use Zigbee or Z-Wave USB dongles, Linux assigns device paths like /dev/ttyUSB0 dynamically. The path can change after a reboot if you have multiple USB serial devices. Udev rules create stable symlinks so your device is always at a predictable path.

Find your dongle’s vendor and product ID:

lsusb

Look for your device. Example output:

Bus 001 Device 004: ID 10c4:ea60 Silicon Labs CP210x UART Bridge

The 10c4:ea60 is the vendor:product ID pair. Get the serial number for a more specific rule:

udevadm info -a -n /dev/ttyUSB0 | grep '{serial}'

Create a udev rule at /etc/udev/rules.d/99-usb-serial.rules:

# Zigbee dongle — Sonoff Zigbee 3.0 USB Dongle Plus (Silicon Labs CP2102N)
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ATTRS{serial}=="YOUR_SERIAL_HERE", SYMLINK+="zigbee", MODE="0666"

# Z-Wave dongle — example Aeotec Z-Stick Gen5
SUBSYSTEM=="tty", ATTRS{idVendor}=="0658", ATTRS{idProduct}=="0200", SYMLINK+="zwave", MODE="0666"

Replace YOUR_SERIAL_HERE with your dongle’s actual serial number. Reload udev rules:

sudo udevadm control --reload-rules
sudo udevadm trigger

Verify the symlink was created:

ls -la /dev/zigbee /dev/zwave

Now your Zigbee dongle is always at /dev/zigbee regardless of USB enumeration order.

Docker Compose Configuration

Create the directory structure:

sudo mkdir -p /opt/homeassistant/config

Create /opt/homeassistant/docker-compose.yml:

services:
  homeassistant:
    container_name: homeassistant
    image: ghcr.io/home-assistant/home-assistant:2026.3.1
    volumes:
      - /opt/homeassistant/config:/config
      - /etc/localtime:/etc/localtime:ro
      - /run/dbus:/run/dbus:ro
    devices:
      - /dev/zigbee:/dev/zigbee     # Zigbee USB dongle (via udev symlink)
      # - /dev/zwave:/dev/zwave     # Z-Wave USB dongle (uncomment if used)
    environment:
      - TZ=America/New_York
    restart: unless-stopped
    network_mode: host

Why Host Networking on Ubuntu

Home Assistant uses mDNS (port 5353), SSDP (port 1900), and UPnP for automatic device discovery. These protocols rely on broadcast/multicast traffic that does not cross Docker’s bridge network boundary. With network_mode: host, the container shares the host’s network stack directly. Home Assistant binds to port 8123 on all host interfaces.

The trade-off: you cannot use Docker’s port mapping (ports: directive is ignored with host networking). Port 8123 is exposed directly on the host.

Why Not Privileged Mode

The main guide uses privileged: true for simplicity. On Ubuntu, you can avoid privileged mode by being explicit:

  • Mount /run/dbus:/run/dbus:ro for Bluetooth (instead of granting full host access)
  • Map specific USB devices via devices: using udev symlinks (instead of accessing all host devices)
  • Keep network_mode: host for device discovery

This follows the principle of least privilege. Only use privileged: true if you encounter permission issues you cannot resolve with explicit device mappings.

D-Bus for Bluetooth

The /run/dbus:/run/dbus:ro volume mount gives Home Assistant read-only access to the host’s D-Bus socket. This is required for:

  • Bluetooth device discovery — scanning for BLE devices
  • ESPHome Bluetooth Proxy — using ESP32 devices as remote Bluetooth scanners
  • iBeacon tracking — presence detection with Bluetooth beacons

Without this mount, Bluetooth integrations fail silently or show “Bluetooth not available” in the HA UI.

Make sure the D-Bus service is running:

sudo systemctl status dbus

It should be active. On Ubuntu, it always is by default.

Start the Stack

cd /opt/homeassistant
docker compose up -d

Verify it is running:

docker compose logs -f homeassistant

Access the web UI at http://<your-ubuntu-server-ip>:8123.

systemd Auto-Restart

Docker’s restart: unless-stopped handles container restarts, but you may want a systemd service for tighter integration with Ubuntu’s init system. This gives you systemctl commands, boot ordering, and journal logging.

Create /etc/systemd/system/homeassistant.service:

[Unit]
Description=Home Assistant
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/homeassistant
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
ExecReload=/usr/bin/docker compose restart
TimeoutStartSec=300

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable homeassistant
sudo systemctl start homeassistant

Now Home Assistant starts automatically on boot and can be managed with:

sudo systemctl status homeassistant
sudo systemctl restart homeassistant
sudo journalctl -u homeassistant -f

Configuration

Ubuntu Firewall (UFW)

If UFW is active, allow the ports Home Assistant needs:

# Home Assistant web UI
sudo ufw allow 8123/tcp

# mDNS for device discovery
sudo ufw allow 5353/udp

# SSDP for device discovery
sudo ufw allow 1900/udp

# Zigbee2MQTT web UI (if using)
sudo ufw allow 8080/tcp

# MQTT broker (if using)
sudo ufw allow 1883/tcp

Verify:

sudo ufw status

Automatic Updates with Watchtower (Optional)

Home Assistant releases monthly updates. You can automate updates with Watchtower, but be aware that HA updates occasionally introduce breaking changes. The safer approach is manual updates by changing the image tag in docker-compose.yml.

If you want automatic updates anyway:

  watchtower:
    container_name: watchtower
    image: containrrr/watchtower:1.7.1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_INCLUDE_STOPPED=true
      - WATCHTOWER_SCHEDULE=0 0 4 * * *  # 4 AM daily
    restart: unless-stopped

Monitor the update log: docker logs watchtower.

Platform-Specific Tips

  • AppArmor and Docker. Ubuntu enables AppArmor by default. If Home Assistant fails to access USB devices or D-Bus despite correct configuration, check if AppArmor is restricting the container. Run dmesg | grep apparmor to look for denials. You can add security_opt: - apparmor:unconfined to the container definition as a last resort.
  • Snap Docker vs APT Docker. Do not use the docker snap package. It runs in a confined namespace that breaks volume mounts and device access. Use the official Docker APT repository as shown in the prerequisites.
  • Ubuntu Pro / Livepatch. These do not interfere with Home Assistant. Kernel live patches apply without rebooting, so your HA container stays running.
  • Multicast DNS resolution. Ubuntu includes systemd-resolved which handles .local mDNS resolution. This complements Home Assistant’s discovery rather than conflicting with it. Do not disable systemd-resolved for Home Assistant (unlike Pi-hole, which needs port 53).

Troubleshooting

USB Device Not Found After Reboot

Symptom: Home Assistant loses connection to the Zigbee/Z-Wave dongle after a reboot. The device path changed from /dev/ttyUSB0 to /dev/ttyUSB1.

Fix: Set up udev rules as described above. Use the stable symlink (/dev/zigbee) in your Docker Compose instead of /dev/ttyUSB0. After creating the udev rule, unplug and replug the dongle, then restart the container.

Bluetooth “Not Available” in HA

Symptom: Bluetooth integration shows “Bluetooth adapter not found” even though the host has Bluetooth hardware.

Fix: Verify D-Bus is mounted:

docker inspect homeassistant | grep -A2 dbus

Check that BlueZ is installed on the host:

sudo systemctl status bluetooth
bluetoothctl list

If BlueZ is not running, start it:

sudo systemctl enable bluetooth
sudo systemctl start bluetooth

Restart the Home Assistant container after fixing.

Device Discovery Fails on Ubuntu with Multiple Network Interfaces

Symptom: Devices on your LAN are not auto-discovered, even with host networking enabled.

Fix: If your Ubuntu server has multiple network interfaces (common with VMs or servers with both Ethernet and Wi-Fi), mDNS traffic may be going out the wrong interface. Check your default route:

ip route show default

Make sure the default route uses the interface connected to your IoT network. If needed, set a static route or disable unused interfaces.

Permission Denied on /dev/ttyUSB0

Symptom: PermissionError: [Errno 13] Permission denied: '/dev/ttyUSB0' in Home Assistant logs.

Fix: Add your user to the dialout group:

sudo usermod -aG dialout $USER

Or set MODE="0666" in the udev rule (as shown above) to make the device world-readable/writable. Restart udev and the container.

Container Fails to Start After Ubuntu Upgrade

Symptom: After upgrading Ubuntu (e.g., 22.04 to 24.04), Home Assistant container fails to start with various errors.

Fix: Ubuntu major upgrades can change kernel versions, D-Bus socket paths, or AppArmor policies. Steps:

  1. Verify Docker is running: sudo systemctl status docker
  2. Pull a fresh image: docker compose pull
  3. Check D-Bus socket path: ls -la /run/dbus/system_bus_socket
  4. Recreate the container: docker compose up -d --force-recreate

Comments