Home Assistant Remote Access - How To Make It More Secure

Updated on 29th Oct 2021 15:59 in DIY, Home Assistant, Tutorial

Home Assistant has transformed the world of do-it-yourself (DIY) home automation. Its compatibility with smart products is virtually unparalleled, as it offers a community-supported module for almost everything. Furthermore, in the rare situation where something doesn't already exist, creating the functionality yourself is surprisingly easy. If you haven't seen it, be sure to check out my article on setting up remote access with Home Assistant here before reading this one! This guide aims to demonstrate how you can increase the security of your remotely accessible control panel.

Securing remote Home Assistant control panel

Why Home Assistant Doesn't Work Remotely by Default

The reason it's almost impossible for remote functionality to work out of the box is quite technical but can still be understood if we accept certain facts about home networking. First, your average router (which is often a box your Internet Service Provider (ISP) gives you) will be hard at work protecting your network. In fact, it ensures no external connection from outside your home can ever reach any device on your network. This helps keep potentially dangerous devices that use the default password, have a security vulnerability or other issues from getting hacked. Examples include robotic lawnmowers, security cameras, lamps, and really anything that is connected to the network.

While that's a good side effect, the primary reason this system exists is that we don't run out of IP addresses. You might have noticed that the IP of your computer or phone is not accessible from the internet, and that's because they are using private addresses. These usually look something like "192.168.0.1". Your network then has a public address which is seen from the internet as being a single device. Your router is in charge of sending the right incoming data to the right place based on a process called "Network Address Translation" (NAT). The details of this system are not relevant for this task, but it explains why we can't just put in the IP of our Home Assistant instance to reach our control panel.

Why Using Regular Remote Access Could be Insecure

The reality is that most people trust a product's instructions to provide the best way of achieving their goal. Unfortunately, when it comes to Home Assistant and remote access, the instructions are focused more on ease of use rather than security. The biggest problem with the standard method is that it exposes the web server built into Home Assistant directly to the entire internet. This might not sound like a big deal, but the reality is that exploits are constantly found in software, and anything that is accessible over the internet will be attacked by automated programs called "Bots." These bots are programmed to try and attack every address on the internet, so they will find yours even if it seems unlikely.

As a result, all it takes is an exploit to be found in Home Assistant's web server for anyone to start attacking your smart home. One of the most important concepts in security is that of attack surfaces, which is the number of ways a bad actor could get into your system. Making your attack surface small is key - but what does that actually mean? Imagine a large school that has a fancy WiFi network for everyone to use. A big attack surface would be a situation where every computer in the school would be connected directly to the internet. A small attack surface would conversely be where only a router is exposed to the internet, with the rest behind its protection.

The idea is that worrying about 1 device's security is much easier than worrying about 300 or more. This applies directly to Home Assistant and any other web panels you want to see remotely. Exposing each thing is worse for security than having a single point of entry. So we can see it's bad to have everything open, but how can we use all our control panels remotely if we can't expose them to the internet? This is where the reverse proxy shines!

How a Reverse Proxy Fixes All Our Problems

A proxy is a service that hides your identity to internet services by collecting all their customers' connections and routing them through a few IP addresses which belong to the proxy company. As such, a third-party website can no longer tell who exactly is sending this information or where they live. A reverse proxy works almost identically, with the big difference being it goes in the other direction. While a proxy makes multiple private networks look like one big network, a reverse proxy makes a single private network look like many public networks. This means functionally that multiple domains (such as control.wltd.org or security.wltd.org) can point to the same IP address and end up in different places. The picture below demonstrates this visually by comparing a regular proxy service with the reverse proxy we want to use.

A visual illustration of the proxy vs reverse proxy
The differences between a proxy and a reverse proxy.

We can see that each flow is color-coded to show where data is going. The regular proxy looks like a single node to the websites at the top while there are actually multiple customers using it simultaneously. The reverse proxy directs multiple domains towards a single address, then distributes the traffic to the correct place locally.

You might now think that this will be great at reducing the attack surface on your local network, but you are probably wondering how this improves security if you only have one node (such as Home Assistant). The answer is that some software is older, more widely adopted, and heavily scrutinized while other projects go without too many problems until one day there is a serious problem. The short answer is that any software could have a severe security vulnerability in it, but it's less likely to be the proxy if it's been around for a long time and has been studied by many experts. The two most popular programs used for this purpose are Apache HTTP Server, which was released in 1995, and Nginx, which was released in 2004. In contrast, Home Assistant was only released in 2013!

Let's be clear: the age of any given software does not directly imply that it is secure, but these two represent, by some estimates, as much as 50% of all websites on the internet. So you can safely say that if an exploit is found in either of those two, your Home Assistant instance won't be at the top of the target list. The other advantage is that they are both extremely powerful on their own - doing anything from acting as a proxy to being a file server to managing permissions. They also support all of the features in the HTTP protocol as they are considered to be the standard for what a webserver should do (because they are so widely used).  As such, we can see that using either Apache or Nginx is a far better option than exposing Home Assistant's internal HTTP server.

Prerequisites For Running a Reverse Proxy With Home Assistant

While the proxy helps tremendously, unfortunately, it adds some complexity to the standard process seen here. What exactly you need to do will depend on your setup, but we will assume that you are using the same computer for this as you are for Home Assistant. This can be done with an extra machine, but since it's less common, we won't go into too much detail about this setup. To follow this guide,  you will need the following.

  • Have already followed the instructions for setting up remote access here.
  • Have root access to a Linux machine (this can be the one Home Assistant is installed on).
  • Have a domain name or be signed up to a dynamic DNS service, like DuckDNS.
  • Have done port forwarding in the router
  • Have installed an integration to keep the domain pointed to your network's public IP (like the DuckDNS integration)

Once you have all of these things, you are ready to start.

Setting up a Reverse Proxy For Home Assistant

We will be using Apache, but you could also use Nginx if you wanted to. The configuration will be done differently, but they are both equally as good for this purpose.

Installing and configuring Apache

Log in to a root shell on the machine you will use as the reverse proxy. Then, either connect a keyboard to the computer you are using for Home Assistant (like a Raspberry Pi) or use a program like Putty to SSH into it using its IP. Provide your username and password.

Now you should see a command prompt. To install Apache, issue the following command.

$ apt-get -y install apache2

Once it finishes, the text should stop moving, and you should not see any error texts. Now, if you visit the IP address of this computer in a browser, you should see a page that says, "It works!". This is the default site that is always present when Apache is first installed. We will be removing this default page and setting up our configuration next.

$ cd /etc/apache2
$ ls sites-enabled
000-default.conf
$ a2dissite 000-default

The default configuration has now been disabled. Now go into the sites-available directory and create the new configuration.

$ cd /etc/apache2/sites-available
$ nano homeassistant.conf

Paste the following into the file and change the values to be valid for your setup. Don't worry about the SSL section for now.

<VirtualHost *:80>
    ServerAdmin [email protected] # replace with your domain
    ServerName hass.duckdns.org        # replace with your domain
    ServerAlias www.hass.duckdns.org

    #SSLEngine On
    #SSLProxyEngine On
    #SSLCertificateFile    /etc/letsencrypt/live/hass.duckdns.org/fullchain.pem
    #SSLCertificateKeyFile /etc/letsencrypt/live/hass.duckdns.org/privkey.pem
    #SSLProxyCheckPeerCN Off
    #SSLProxyCheckPeerName Off

    # This is the default anyway, but no harm having it explicitly set
    ProxyPreserveHost On
    ProxyRequests Off
    ProxyPass /api/websocket ws://127.0.0.1:8123/api/websocket
    ProxyPassReverse /api/websocket wss://127.0.0.1:8123/api/websocket
    ProxyPass / http://127.0.0.1:8123/
    ProxyPassReverse / http://127.0.0.1:8123/

    #fix websockets for addons and apis
    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteRule ^/?(.*) "ws://127.0.0.1:8123/$1" [P,L] #MODIFY to your HA IP address

</VirtualHost>  

For the moment, the important settings are ServerName, ServerAlias, and everywhere you see the value "127.0.0.1". In this case, we are specifying your domain (from a registrar or just a service like DuckDNS) for Home Assistant. Every instance of the IP "127.0.0.1" corresponds to the computer's IP that is running Home Assistant. Leave it was is unless you are installing Apache on a different machine.

These directives tell Apache that it should forward any data it receives from the URL to Home Assistant. The details of what everything does are outside the scope of this article, but each one is well documented on the Apache website.

Save the file by pressing Control + O. Exit the editor by pressing Control + X. You should see the file appear under /etc/apache2/sites-available

$ ls /etc/apache2/sites-enabled
homeassistant.conf

Now we need to activate the new configuration file and restart Apache before it works.

$ a2ensite homeassistant
Enabling site homeassistant.
To activate the new configuration, you need to run:
  systemctl reload apache2
$ systemctl restart apache2

With any luck, it will run without any messages being returned. However, if you get an error, it will say that starting the Apache server failed. Check the error by running the following command.

$ journalctl -xe

You will see the reason or the failure, though you might need to scroll up or down a bit depending on how busy your server is. Control + C to exit. The most common reason for an error at this stage is syntax-related, so double-check all your configuration files are correct.

Updating Your Port Forwarding

If you use a different machine than the one running Home Assistant for your proxy, you will need to change the router's configuration. The port forwarding should map port 80 to port 80 and port 443 to port 443. The target IP should be the IP address of the computer that is running the Apache proxy. Check out my simple guide for a refresher on port forwarding here. These settings should result in all incoming web traffic being redirected to the reverse proxy server. You might want to come back to this step later and remove port 80 once SSL is working. Leaving port 80 presents a minor security risk that is better to close once it's no longer needed.

Giving it a try

At this point, you should be able to type your URL into a browser and see the Home Assistant login screen. Assuming everything works, you are already almost done! All the traffic which is coming from the internet is being redirected to the Apache server. The server is then sending the requests that match your chosen URL to Home Assistant. While this is really cool, we still haven't really taken full advantage of the power Apache brings to the table. For that, we will let Apache handle the SSL connections. This is called "SSL Termination," as our proxy will be responsible for decrypting the data and sending it in plain text to Home Assistant over the local network (where it's normally safe). Such a strategy is great for reducing the load on an application, but it also helps security by keeping all the SSL aspects in one place.

Personally, I recommend only accepting SSL connections with Apache. While it might not seem necessary, the reality is that when you log in to Home Assistant remotely, you are sending your username and password over the internet. When the connection is made over SSL (port 443), everything is encrypted, which prevents someone from seeing your password. The danger of using regular HTTP is that you need to ensure the network you are using at a remote destination is completely free of any bad actors. You might think that this is impossible, especially if using free WiFi at a mall or something. I completely agree - don't use HTTP! When connected with SSL, no one on the network can view your information since it's encrypted. This is why I recommend only using SSL for remote connections.

Setting up SSL on the Apache Proxy

The Apache SSL Configuration itself is rather simple. You just need to specify the certificate file's location in the configuration, and everything will work. The problem is obtaining a certificate. There was a time when you needed to pay to get these certificates from a given authority. Lucky for us, there is an organization known as "Letsencrypt," which provides free certificates for everyone! The only downside is that Letsencrypt requires you to perform a challenge to prove you control the domain you are requesting a certificate for. These challenges usually entail hosting a key file on your web server or putting it as a TXT DNS record.

The easiest is the DNS challenge, but it requires control of the domain you are using, which isn't always possible if you use a service like DuckDNS. Usually, each DNS provider has an API that can automatically perform the challenge when the certificates need renewing. The program we are going to use to manage our SSL certificates is called "Certbot." Start by visiting the Certbot website to find specific instructions for your system here. Put "Apache" for software and put the operating system you are using, which is usually Debian or Ubuntu for Home Assistant. Then, follow the instructions to install Certbot.

Once installed, you will need to install a plugin to automatically perform the DNS challenge. Since I have my own domain, which uses Cloudflare as a DNS provider, I would use the "certbot-dns-cloudflare" plugin. Anyone following along will want to use the plugin built for your DNS provider. The instructions in this guide will assume you are using DuckDNS, but any provider with a Certbot plugin can be used. Install the appropriate plugin by running the following command.

$ pip install certbot_dns_duckdns

You shouldn't see any errors if everything was successful. Now check that the plugin is getting picked up by Certbot.

$ certbot plugins
* dns-duckdns
Description: Obtain certificates using a DNS TXT record for DuckDNS domains
Interfaces: Authenticator, Plugin
Entry point: dns-duckdns = certbot_dns_duckdns.cert.client:Authenticator

You should see a list of plugins. Check that the DuckDNS (or the one you installed) is listed. If you do get errors, double-check your Python installation - any problems there will prevent this from working. You need to have at least Python version 3.6 for the plugin to work. You might need to use the "update-alternatives" command if your Python version is too old.

Once that works, we can begin requesting the certificate for our domain. Run the following command.

certbot certonly 
--non-interactive 
--agree-tos 
--email <your email>
--preferred-challenges dns 
--authenticator dns-duckdns 
--dns-duckdns-token <your duckDNS token>
--dns-duckdns-propagation-seconds 60 
-d "example.duckdns.org"

The DuckDNS token can be found on your DuckDNS dashboard. Keep in mind that other DNS providers will have a similar token that must be provided. Once the command has finished, you should now have your shiny new certificate! Certbot saves the certificate file at "/etc/letsencrypt/live/<yourdomain>/fullchain.pem" and ".../privkey.pem". These are the files we will specify in our Apache configuration. Open the homeassistant.conf file again and modify it to look like the one below.

<IfModule mod_ssl.c>
        <VirtualHost *:443>
                ServerAdmin [email protected] # replace with your domain
                ServerName hass.duckdns.org     # replace with your domain
                ServerAlias www.hass.duckdns.org

                SSLEngine On
                SSLProxyEngine On
                SSLCertificateFile    /etc/letsencrypt/live/hass.duckdns.org/fullchain.pem
                SSLCertificateKeyFile /etc/letsencrypt/live/hass.duckdns.org/privkey.pem
                SSLProxyCheckPeerCN Off
                SSLProxyCheckPeerName Off

                # This is the default anyway, but no harm having it explicitly set
	        ProxyPreserveHost On
	        ProxyRequests Off
	        ProxyPass /api/websocket ws://127.0.0.1:8123/api/websocket
	        ProxyPassReverse /api/websocket wss://127.0.0.1:8123/api/websocket
	        ProxyPass / http://127.0.0.1:8123/
	        ProxyPassReverse / http://127.0.0.1:8123/

	        #fix websockets for addons and apis
	        RewriteEngine On
	        RewriteCond %{HTTP:Upgrade} websocket [NC]
	        RewriteRule ^/?(.*) "ws://127.0.0.1:8123/$1" [P,L] #MODIFY to your HA IP address

         </VirtualHost>
</IfModule>

Note the difference at the start of the file. It might be better to erase the entire contents of the old configuration and replace it with this. If you want to leave both unencrypted and SSL connections (not recommended), you can add this block under the other one in the config file. Just make sure the <IfModule> directive of the SSL config is under the </VirtualHost> directive of the port 80 block.

Apache will now use the certificate file, which is managed by Certbot. Every so often, Certbot will use the information you gave it to register the certificate to renew all of them for you. As a result, everything should keep working by itself without requiring manual input. I really recommend only keeping the SSL version of the configuration file for reasons we went into earlier. You don't want to accidentally connect over an unencrypted connection on a public network. Otherwise, that's it! You are now ready to access Home Assistant remotely and securely!

Other Posts