Expose self-hosted Home Assistant to the internet (and companion app) with Cloudflare Tunnels and Docker Compose
Prerequisites
This guide assumes you have the following:
- A Cloudflare account (free)
- A domain name (bought from Cloudflare or elsewhere; $10/yr), set up in Cloudflare
- A linux server with docker, which can be the same one where you run Home Assistant, but doesn’t need to be
The general idea
Web users connect to Cloudflare, and instead of Cloudflare reaching into our network, we run a service inside the network that reaches out to Cloudflare:
We are not opening any ports, e.g. http/https from our server to the local network or internet. We are not messing with dynamic dns stuff. We don’t need to set up TLS.
An example with Docker Compose
Create the tunnel config at Cloudflare
First create your tunnel in the Cloudflare dashboard > Zero Trust > Networks > Tunnels:
- Choose
cloudflared
- Name it whatever you want, e.g.
homelab
- Click the “docker” installation instructions just to grab the token
- Don’t actually install anything
Copy that token for the next section.
On your homelab server
Create docker-compose.yaml
like this, with the token you copied above:
version: "3.3"
services:
tunnel:
image: cloudflare/cloudflared
restart: unless-stopped
command: tunnel run
environment:
- TUNNEL_TOKEN=***insert CF Tunnel token here***
Start up the containers:
docker compose up -d
Tail the logs:
docker compose logs -f
Back in the Cloudflare dashboard
At this point, the Cloudflare dashboard should show your tunnel as healthy. If not, read those logs carefully for errors and fix it before continuing.
Add a public hostname to your tunnel:
- subdomain: anything you want (this DNS record will be added for you)
- domain: pick the domain you already set up in Cloudflare
- service: choose
http
, and seturl
to the internal IP and port of your Home Assistant inac name of your docker service. E.g.http://192.168.1.100:8123
- additional application settings: none
With that done, you should be able to browse to the subdomain you set up, and the response will be tunneled over from
your homelab server. You’ll probably get a 400
error. Don’t fret—you’re almost done.
Now you need to tell Home Assistant that the Cloudflare tunnel is allowed to proxy it. You can see the exact IP you need in the Home Assistant error log, e.g.:
“A request from a reverse proxy was received from 172.21.0.8, but your HTTP integration is not set-up for reverse proxies”
That exact IP could change when your cftunnel container restarts, but the first two parts of the network probably won’t,
so you can just trust the whole thing like this, in HA configuration.yaml
:
http:
use_x_forwarded_for: true
trusted_proxies:
- 172.21.0.0/16 # add the actual IP of your cftunnels container, or the network it's on
(And restart your HA instance.)
You can also find the anticipated proxy IP by checking the network details, too. First, list your docker networks:
docker network ls
NETWORK ID NAME DRIVER SCOPE
37c19c0df841 bridge bridge local
da9f58733dc9 home-assistant_default bridge local
587ff440738c host host local
30a44b8290f3 none null local
0e1df72bc30c sites_default bridge local
Then pick the one where you’re running your cftunnels container (not the one running your HA instance!). In my case, it’s in a folder called sites
, so I need the last one. Inspect it:
// docker network inspect sites_default
[
{
"Name": "sites_default",
"IPAM": {
"Config": [
{
"Subnet": "172.21.0.0/16", // <-- this is what you want
"Gateway": "172.21.0.1"
}
]
},
"Containers": {
"6626c238...": {
"Name": "sites-tunnel-1",
"IPv4Address": "172.21.0.8/16"
}
}
}
]
Do I need to do TLS, HTTP compression, etc.?
No. Cloudflare handles TLS and HTTP compression for you. The tunnel is encrypted so no additional TLS is necessary for that leg.
I’m still getting a 400
error, or something else isn’t working
Check the logs!