Updated September 2024: Added step installing Cloudflare CA cert to avoid client cert installation issues
TL;DR: Skip to "Setting up mTLS with Cloudflare and Android" for the high level instructions.
There are many different ways to set up Home Assistant so that it is accessible when you're away from home, which have various pros and cons, but I had some extra requirements beyond just securing it with HTTPS/TLS which I haven't found any articles about (in bold):
- Does not expose my home IP address
- Does not publicly expose my Home Assistant instance (i.e. no publicly-accessible login form)
- Secured using TLS/SSL
- Accessible from any device
- Works with the Home Assistant Android native app
- Does not require running a network client "tool" on my phone (e.g. VPN client, Cloudflare WARP client)
- Ideally doesn't require a paid subscription to something
- Can support Google Assistant integration in future
This blog post, as with most of my posts, serves as a combination of a reminder to my future self on how I got it working, and hopefully also a useful resource for others. As such, this post will be rough-around-the-edges in places.
Things this blog post doesn't cover:
- Setting up Cloudflare with your domain
- Setting up Cloudflare Tunnel
- Configuring Home Assistant to accept reverse-proxied traffic through Cloudflare Tunnel (see Home Assistant docs)
Ways of exposing Home Assistant
Some different ways of setting up Home Assistant so that it's accessible publicly:
- Paying for Home Assistant Cloud
- Pro: easiest, supports the maintainers
- Con: subscription service, leaves your HA instance exposed to the internet
- Satisfies requirements 1-6, 8 (I think)
- Forwarding a port on your home router
- Pro: easy to set up
- Con: exposing your home IP, and has potential risks from security holes in Home Assistant
- Satisfies requirements 3-8
- Using a VPN (incl. Tailscale) to connect back into your home network
- Pro: secure, no attack surface for your home server
- Con: you have to install the VPN client wherever you want to use it, and connect/disconnect when needed
- Satisfies requirements 1-5, 7
- Hiding your Home Assistant instance behind Cloudflare, using a Cloudflare Tunnel to connect outbound from your home network to Cloudflare.
- Pro: secure, no direct route of attack to your home server
- Con: has potential risks from security holes in Home Assistant
- Satisfies requirements 1, 3-8
Requirement 2 (does not publicly expose my Home Assistant instance to the internet) can be mitigated when using Cloudflare by using Cloudflare Access to require visitors to log in (e.g. via Google, Okta, one-time PIN via email) before they even get to your Home Assistant login form. This is great from a security standpoint, but it does cause issues with the Android app.
Cloudflare Access and the Android app
If you set up Cloudflare Access so that it prompts the user to log in via either an OTP sent via email, or via another identity provider before exposing your Home Assistant login form, this breaks compatibility with the Android app. This is because when you access your Home Assistant domain, e.g. ha.mydomain.co.uk
, you will be redirected to your identity provider domain before you get to see your Home Assistant login form (e.g. myname.cloudflareaccess.com
). This redirect takes you out of the Home Assistant app and into your browser, and so once you've logged into your identity provider, the Android app never receives the auth token generated as part of the final login step, which it needs to be able to communicate with Home Assistant. As such, we need to find another way of authenticating the Android app which does not require redirecting to another domain.
There have been issues raised on GitHub about this exact issue, but there is no solution for the redirect problem at present.
Just get to the point
What does seem to work is having two subdomains, which both back off to the same Home Assistant instance, one which is for use in a browser, and one which is for use from the Android app.
ha.mydomain.co.uk
- for use from a browser- Uses Cloudflare Access to enforce login via an identity provider
- Accessible from anywhere, as long as you can log into your identity provider
ha-android.mydomain.co.uk
- for use from the Android app- Uses mTLS (mutual TLS) to require the Android app to present a client TLS certificate to Cloudflare
- Requires manual setup on each Android device, but only once
You will need two separate Cloudflare Tunnels, one for each domain, both of which should back off to the same origin URL.
I've seen a number of other guides/tutorials online describing how to set up Cloudflare Access, but I haven't seen much explaining how to set up mTLS with Cloudflare, so that's what I'll do next.
Setting up mTLS with Cloudflare and Android
Tl;dr this is how I did it:
- Remove Cloudflare Access rules for your
ha-android
domain
- Cloudflare Access rules will prompt the user to log in, which we don't want
- Generate a client certificate in Cloudflare
- Use the "SSL/TLS" pane of Cloudflare to generate and download a client certificate and secret (or generate it yourself offline). Store the certificate as
cf.pem
and the private key ascf.key
- Convert these two files into a
pfx
file, which is importable in Android, using the following command (source):openssl pkcs12 -export -out cf.pfx -inkey cf.key -in cf.pem
- Securely transfer this file to your Android phone
- Use the "SSL/TLS" pane of Cloudflare to generate and download a client certificate and secret (or generate it yourself offline). Store the certificate as
- Download and install Cloudflare CA cert on Android device
- See guide on Cloudflare's site. This seems to be required to install the client cert.
- Install client certificate into Android cert store
- Go to Settings > Security > Credential Storage, tap Install from storage, then locate the file you transferred from step 2.
- Give the certificate a sensible name so that future-you remembers what it is
- Create an mTLS rule in Cloudflare WAF to block non-certificate-holders
- From the "SSL/TLS" pane in Cloudflare, click "Client certificates", then click the "Create mTLS rule" button. If this isn't visible in Cloudflare, it's because they've moved it to the API Shield section which they are warning about right now ('Nov 2022)
- Configure Home Assistant app to use cert
- Change the "External URL" in your app settings to use your
ha-android
domain, then restart the app. You should be prompted to select a client certificate on next start.
- Change the "External URL" in your app settings to use your
That's it. You now have a domain for browser use, and a domain which works in the Android app, without having to start any other VPN apps first.
References
Some posts I found useful while setting this up:
- https://empty.coffee/home-assistant-cloudflare-zero-trust-setup/ - looking back, this blog post is a much more nicely written version of a significant portion of what I wrote!