Externalize Home Assistant
This is a third post in the Smart Home series. See the previous post if you haven’t already: https://blog.michal.pawlik.dev/posts/smarthome/zigbee-setup/. Last time I wrote about Zigbee and shown how to control smart home devices. In this post I want to show you one of many ways you can use to expose your HA (Home Assistant) instance to the internet.
Why expose?
All of the examples in previous posts were referring to local instance of Home Assistant. To work with it you had to be with the same network. While it’s not a problem when you are at home, there are cases when you want to access HA from the external network. Those cases include controlling the heat, accessing monitoring, switching light etc. when you are not home.
How to externalize
DMZ
The first and quite common approach is to externalize the instance through your router. It is only possible if your ISP provides you with a public IP address, which means your home router is reachable from the internet. Some ISPs charge extra for that, some don’t offer it at all for individual clients, which renders this approach not suitable for some users.
Externalization using this method also brings some risks. Depending on implementation users either forward specific ports or set up a DMZ to the machine where HA is hosted.
This is a one way to go, but due to it’s limitations and potentially large surface of vulnerabilities introduced. It’s also well described already so there’s no point in me repeating the knowledge.
Instead I want to share a different approach, based on reverse ssh tunnel and an reverse proxy.
Reverse Proxy
Overview
Reverse proxy approach doesn’t require the public IP, instead it uses a VPS. If you have decided to self host there’s a good chance you already have one. Otherwise you can either buy the cheapest one you can find or ask a friend who has one to help you with that.
Before digging down to the implementation, let’s see the solution architecture:
The whole thing works because VPS is externally available, and once it receives traffic for a specific domain, it’s forwarded to the HA instance. VPS can access the HA instance thanks to the tunnel, initialized from the private HA network, but thanks to it’s reverse nature it allows the traffic to go back there from VPS.
Implementation
There are two components to be implemented: reverse proxy and ssh tunnel. Let’s start with proxy.
Reverse Proxy
The reverse proxy can be set up in many ways. My preference here is Caddy server. To forward the traffic to reverse tunnel, you can use a following snippet:
homeassistant.mydomain.com {
reverse_proxy http://127.0.0.1:9090
}
Quite simple ain’t it? This snippet assumes your reverse tunnel is exposed on port 9090
and that your subdomain is homeassistant.mydomain.com
.
If you wish to introduce an additional layer of security, you can add Basic Auth, so before accessing the instance you’ll need to provide extra pair of login and password. This makes brute force attacks against your instance much more difficult.
To implement that, you need to generate password, for that you can use caddy hash-password
and use the generated password like this:
ha.michalp.net {
reverse_proxy http://127.0.0.1:9090
basicauth * {
MyBasicAuthUserName JDJhJDE0JGc0YUYzZGVSZklkYjBHOFM2MGtCSy45Rm9vUkYydkFwbjRZU0tVejl6VU1OVlJ6cXhNZXBx
}
}
Where MyBasicAuthUserName
is your username and JDJhJDE0JGc0YUYzZGVSZklkYjBHOFM2MGtCSy45Rm9vUkYydkFwbjRZU0tVejl6VU1OVlJ6cXhNZXBx
is the generated password hash.
Reverse SSH Tunnel
Having the reverse proxy ready, let’s set up the tunnel. Such tunnel needs to start with your home assistant instance, keep the connection alive and so on. Luckily enough, you don’t need to write any code, since I’ve published this part as a Home Assistant addon.
Here’s how to use it:
- In your user settings enable Advanced Mode
- Go to Settings > Add-ons > Add-on Store (bottom right)
- Open the three-dot menu on the top right
- Add following repository: https://github.com/majk-p/home-assistant-addons
- Refresh the page so the new repository shows up
- Install the
Reverse SSH Tunnel
addon - In the configuration provide the necessary details, you can use the yaml view to set it up like this:
username: tunnel-user-name
private_key: |
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEA7nwnrZzm2PM6OQB0juB3PDd7Z0wsJsTa11gRPtzeYkRGXdmHRTXm
......................................................................
8pBXR0LFQcVTRC6iUtLjabH3GjHh6w/EpX3OoVqlwY5dNWlWJB0yj5dc3evxbRJZWw5c9O
KykTD+AymlAZ1QZoNysME8TjrGBeUiO4yOdlg9ejLM7B090IUzdaJfgcoFIRyvNO54ue+K
2N6RDpQMW0rr9rAAAADm1hamtwQGluc3Bpcm9uAQIDBA==
-----END OPENSSH PRIVATE KEY-----
server:
host: homeassistant.mydomain.com
port: 9090
Note that we use the exact domain name and port as in Caddy file.
There’s one more question - where does the user and key come from?
This is the user that’s going to be used to open ssh connection. Here’s the setup:
- Create a user on vps, in this example username would be
tunnel-user-name
but it can be anything - Generate SSH key pair
- Add the public key to
~/.ssh/authorized_keys
on VPS - Use the private key in the configuration
If you struggle with generating keys, save and run this snippet
#!/bin/bash
echo "SSH keypair generator"
echo "Provide the user you want to generate keys for"
read username
if [[ -z "$username" ]]; then
echo "Username cannot be empty!"
exit 1
fi
key_file="./keys/$username"
public_key_file="$key_file.pub"
echo "Key will be created in $key_file"
ssh-keygen -q -t rsa -N '' -f $key_file <<<y >/dev/null 2>&1
chmod 600 $key_file
echo "Files are ready in $key_file and $key_file.pub"
Once you do it, just start the addon and that’s it! If the setup is correct, in the logs you should see an output similar to the one shown below:
s6-rc: info: service s6rc-oneshot-runner: starting
s6-rc: info: service s6rc-oneshot-runner successfully started
s6-rc: info: service fix-attrs: starting
s6-rc: info: service fix-attrs successfully started
s6-rc: info: service legacy-cont-init: starting
s6-rc: info: service legacy-cont-init successfully started
s6-rc: info: service legacy-services: starting
s6-rc: info: service legacy-services successfully started
[22:30:36] INFO: Reverse tunnel initializing.
[22:30:36] INFO: Variables set, reading configuration.
[22:30:37] INFO: Reverse tunnel configured for tunnel-user-name@homeassistant.mydomain.com
[22:30:37] INFO: Using private key authorization
[22:30:37] INFO: Initializing the ssh tunnel
Warning: Permanently added 'homeassistant.mydomain.com' (ED25519) to the list of known hosts.
The last thing you need is to allow the ingress from reverse proxies. This is done as described in this community discussion. Basically just put this snippet at the end of your config.yaml
.
http:
use_x_forwarded_for: true
trusted_proxies:
- 172.30.33.0/24
- 127.0.0.1
- ::1
Then in Settings > System > Network
insert your external hostname (equivalent of homeassistant.mydomain.com
form previous snippets) and you’re good to go!
Wrap up
This option is not perfect, but if you can’t get a public IP or prefer not to expose your home network to the internet it sounds like a reasonable thing to try.