Home Assistant in Docker Host Mode with Nginx Proxy and Let’s Encrypt the 2026 way

Home Assistant in Docker Host Mode with Nginx Proxy and Let’s Encrypt the 2026 way

23.04.2026 Networking Raspberri Pi 0

Or: how we turned “almost working” into “clean HTTPS and zero drama”

There are projects you start with dangerous confidence.

You think: Home Assistant in Docker, host networking, reverse proxy in front, Let’s Encrypt on top. Easy win.

And then three hours later you are staring at a hanging curl, your coffee is cold, and Nginx is silently judging your life choices.

Good news: this setup works beautifully once it is wired the right way.

In this post I’ll walk through the final, working solution for running Home Assistant in Docker host mode behind nginx-proxy with acme-companion handling the certificates for:

lin-ha.vanudengroup.com

This is the cleaned-up version. No half-working experiments, no “should work in theory,” and no mystery meat networking.


The environment

This build runs on an Ubuntu host with this IP:

192.168.1.166

The requirements were simple:

  • Home Assistant in Docker
  • Home Assistant in host network mode
  • Nginx as a reverse proxy
  • Automatic Let’s Encrypt certificates
  • Public URL: https://xxx.xxx.com

Nice and tidy.

The one big takeaway from the whole adventure was this:

If Home Assistant runs in host mode, it is often much cleaner to run the reverse proxy in host mode too.

That ended up being the magic ingredient.


Why host mode for Home Assistant?

Home Assistant is one of those applications that tends to be happiest in network_mode: host.

That gives you a few practical benefits:

  • easier local network discovery
  • less pain with mDNS, SSDP, and multicast-based integrations
  • fewer awkward “why can’t this thing find my device?” moments

So that part stayed exactly as requested.


Where things went sideways

The first design looked like this:

  • Home Assistant in host mode
  • nginx-proxy in bridge mode
  • acme-companion alongside it
  • Nginx forwarding traffic to Home Assistant

On paper, perfectly reasonable.

In practice, we hit the classic issue:

  • Home Assistant was listening correctly on port 8123
  • Nginx was receiving requests
  • Let’s Encrypt certificates were issued successfully
  • but traffic over HTTPS would hang when Nginx tried to pass it upstream

That meant the outside looked polished, but internally the proxy path was broken.

Testing quickly showed the real story:

  • curl http://127.0.0.1:8123 worked on the host
  • curl from inside the Nginx container to the host-side Home Assistant service did not
  • so the failing path was container → host-mode Home Assistant

The clean fix was gloriously boring:

Run nginx-proxy in host mode too, and proxy directly to 127.0.0.1:8123.

No weird bridge routing.
No host-gateway guesswork.
No Docker networking séance.

Just straight traffic, like the networking gods intended.


The final working architecture

We are using:

  • nginxproxy/nginx-proxy
  • nginxproxy/acme-companion
  • ghcr.io/home-assistant/home-assistant:stable

And the final design is:

  • Home Assistant runs in host mode
  • nginx-proxy runs in host mode
  • acme-companion handles Let’s Encrypt
  • Nginx proxies to 127.0.0.1:8123
  • Home Assistant trusts the proxy via trusted_proxies

Simple. Stable. Pleasantly boring.


Step 1 – Create the folder structure

Create the working directory and the required subfolders:

mkdir -p /docker/ha-proxy/nginx/vhost.d
mkdir -p /docker/ha-proxy/homeassistant/config/themes
cd /docker/ha-proxy

Now create the empty include files Home Assistant expects if you use the standard includes:

touch /docker/ha-proxy/homeassistant/config/automations.yaml
touch /docker/ha-proxy/homeassistant/config/scripts.yaml
touch /docker/ha-proxy/homeassistant/config/scenes.yaml

If you skip those while referencing them in configuration.yaml, Home Assistant will go into recovery mode faster than a switch stack during a surprise firmware mismatch.


Step 2 – Create the .env file

File path:

/docker/ha-proxy/.env

Contents:

TZ=Europe/Amsterdam
HA_DOMAIN=xxx.xxx.com
LETSENCRYPT_EMAIL=admin@xxx.xxx.com

Small file, big energy.


Step 3 – Create the final docker-compose.yml

File path:

/docker/ha-proxy/docker-compose.yml

services:
  nginx-proxy:
    image: nginxproxy/nginx-proxy:latest
    container_name: nginx-proxy
    restart: unless-stopped
    network_mode: host
    volumes:
      - certs:/etc/nginx/certs:ro
      - html:/usr/share/nginx/html
      - ./nginx/vhost.d:/etc/nginx/vhost.d:rw
      - /var/run/docker.sock:/tmp/docker.sock:ro

  acme-companion:
    image: nginxproxy/acme-companion:latest
    container_name: nginx-proxy-acme
    restart: unless-stopped
    depends_on:
      - nginx-proxy
    environment:
      DEFAULT_EMAIL: ${LETSENCRYPT_EMAIL}
      NGINX_PROXY_CONTAINER: nginx-proxy
    volumes:
      - certs:/etc/nginx/certs:rw
      - html:/usr/share/nginx/html
      - ./nginx/vhost.d:/etc/nginx/vhost.d:rw
      - acme:/etc/acme.sh
      - /var/run/docker.sock:/var/run/docker.sock:ro

  homeassistant:
    image: ghcr.io/home-assistant/home-assistant:stable
    container_name: homeassistant
    restart: unless-stopped
    privileged: true
    network_mode: host
    environment:
      TZ: ${TZ}
      VIRTUAL_HOST: ${HA_DOMAIN}
      LETSENCRYPT_HOST: ${HA_DOMAIN}
      LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}
      VIRTUAL_PORT: 8123
    volumes:
      - ./homeassistant/config:/config
      - /etc/localtime:/etc/localtime:ro
      - /run/dbus:/run/dbus:ro

volumes:
  certs:
  html:
  acme:

A few important notes here:

  • nginx-proxy runs in network_mode: host
  • Home Assistant also runs in network_mode: host
  • acme-companion is explicitly told which Nginx container to use
  • there are no ports: lines for host-mode services because they bind directly to the host stack

This arrangement turned out to be the key to a stable build.


Step 4 – Create the Nginx location override

File path:

/docker/ha-proxy/nginx/vhost.d/lin-ha.vanudengroup.com_location_override

Contents:

location / {
    proxy_pass http://127.0.0.1:8123;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_buffering off;
    proxy_read_timeout 3600;
    proxy_send_timeout 3600;
}

And yes, the filename matters a lot.

It must end in:

_location_override

Not just the hostname.
Not _location.
Not “close enough.”

This is one of those tiny details that can eat an entire evening if you get it wrong.


Step 5 – Configure Home Assistant for reverse proxy support

File path:

/docker/ha-proxy/homeassistant/config/configuration.yaml

Minimal working version:

default_config:

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 127.0.0.1
    - ::1
  ip_ban_enabled: true
  login_attempts_threshold: 5

If you are using the normal include-based structure, this is also perfectly fine:

default_config:

frontend:
  themes: !include_dir_merge_named themes

automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 127.0.0.1
    - ::1
  ip_ban_enabled: true
  login_attempts_threshold: 5

Important:

  • only use one http: block
  • do not duplicate sections
  • because Nginx is now in host mode, 127.0.0.1 and ::1 are the correct trusted proxy sources

That is what allows Home Assistant to accept forwarded headers correctly.


Step 6 – Start the stack

From the project directory:

cd /docker/ha-proxy
sudo docker compose up -d

If you want to rebuild from scratch:

cd /docker/ha-proxy
sudo docker compose down
sudo docker compose up -d

Nice, predictable, and satisfying.


Step 7 – Check that everything is alive

See the running containers:

sudo docker ps

Follow the Home Assistant logs:

sudo docker logs -f homeassistant

Follow the Nginx logs:

sudo docker logs -f nginx-proxy

Follow the Let’s Encrypt logs:

sudo docker logs -f nginx-proxy-acme

At this point, the stack should come up cleanly and the certificate should be issued automatically.


Step 8 – DNS and router setup

This part still matters, because no amount of Docker enthusiasm can fix bad DNS.

You need:

  • an A record for lin-ha.vanudengroup.com pointing to your public IP
  • port forwarding on the router:
    • TCP 80 → 192.168.1.166:80
    • TCP 443 → 192.168.1.166:443

Do not expose port 8123 directly to the internet.
That is the reverse proxy’s job.


Step 9 – Allow the traffic through the firewall

If ufw is active on the Ubuntu host:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

Otherwise you will have a beautifully configured service that nobody can actually reach, which is a very traditional IT move.


Step 10 – Useful validation tests

Test Home Assistant locally

curl -I http://127.0.0.1:8123

Expected result:

HTTP/1.1 405 Method Not Allowed
Allow: GET

That is fine. curl -I sends a HEAD request, and Home Assistant is basically replying with: “I’m here, but use GET like a civilized person.”

Test HTTP via the public hostname

curl -I --resolve xxx.xxx.com:80:127.0.0.1 http://xxx.xxx.com

Expected result:

HTTP/1.1 301 Moved Permanently
Location: https://xxx.xxx.com/

Exactly what we want.

Test HTTPS via the public hostname

curl -kI --resolve xxx.xxx.com:443:127.0.0.1 https://xxx.xxx.com

Expected result:

HTTP/2 405

Also fine. That proves:

  • TLS is working
  • Nginx is working
  • proxying is working
  • Home Assistant is answering behind HTTPS

In other words: the whole chain is alive.


Problems we ran into so you do not have to

1. acme-companion could not find nginx-proxy

The fix was this:

environment:
  DEFAULT_EMAIL: ${LETSENCRYPT_EMAIL}
  NGINX_PROXY_CONTAINER: nginx-proxy

Without that, acme-companion complained loudly and usefully, which is nice once and less nice the seventh time.

2. Duplicate mount points on vhost.d

Do not mount the same path twice using both:

  • a named Docker volume
  • and a bind mount

Docker is absolutely correct to reject that, and it will do so with all the warmth of a failed config commit.

3. Wrong override filename

This one was sneaky.

The correct file is:

xxx.xxx.com_location_override

Not the hostname alone.
Not _location.
The exact suffix matters.

4. Home Assistant recovery mode

If you reference:

  • automations.yaml
  • scripts.yaml
  • scenes.yaml

and those files do not exist, Home Assistant will refuse to load the config normally.

Easy fix: create them.

5. Reverse proxy trust

If trusted_proxies is wrong, Home Assistant will not correctly accept the forwarded request path.

In this final design, the correct trusted proxies are:

trusted_proxies:
  - 127.0.0.1
  - ::1

That part matters more than it looks.


Why this final solution works so well

The earlier attempts failed because bridge-mode Nginx had trouble cleanly reaching a host-mode Home Assistant service in this specific setup.

Once both services were placed in host networking:

  • the path became direct
  • the proxy target became simple
  • the config became easier to understand
  • troubleshooting became dramatically less annoying

Nginx now just proxies to:

127.0.0.1:8123

That is the kind of solution that makes a network engineer smile softly and mutter, “yes, that’s better.”


Final test

Open this in your browser: xxx.xxx.com

Then test it from outside your LAN as well, for example from your phone on 4G or 5G.

If that works, congratulations: you now have Home Assistant running in Docker host mode, neatly published behind Nginx, with valid Let’s Encrypt certificates and no ugly exposed port 8123.

That is a good day in the server room.


Handy admin commands

Restart only Home Assistant

cd /docker/ha-proxy
sudo docker compose restart homeassistant

Restart the full stack

cd /docker/ha-proxy
sudo docker compose restart

Rebuild everything

cd /docker/ha-proxy
sudo docker compose down
sudo docker compose up -d

Clear Home Assistant IP bans

sudo rm -f /docker/ha-proxy/homeassistant/config/ip_bans.yaml
cd /docker/ha-proxy
sudo docker compose restart homeassistant

Final thoughts

So yes, Home Assistant in Docker host mode behind nginx-proxy with Let’s Encrypt works perfectly well.

The trick is not to overcomplicate the network path.

Once we stopped trying to make a bridge-mode proxy talk politely to a host-mode service through side doors and half-open windows, the whole thing settled down nicely.

Which, frankly, is the kind of result every cheerful network engineer wants:

less swearing, more uptime, and one more service humming along happily behind a green padlock.