Creating a Secure Tunnel for Home Assistant
In this post, I’m documenting step by step on how I created a secure tunnel for my Home Assistant installation. By creating the tunnel, now I can connect to my Home Assistant website from anywhere via the internet. The benefit of being able to connect my Home Assistant from anywhere in the world is that now I can control all smart devices in my home not just while I’m home, but also while I’m away. So that if I forget turning off a switch or an appliance while away from home, I can turn it off using my smart phone.
My Home Assistant is installed on an Orange Pi 5 board. If you are interested to know how I setup it, you can read my previous post here: Installing & Configuring Home Assistant Stack on Docker Ubuntu Server 22.04. My home server is inside the LAN and it is behind my ISP’s NAT. The IP of my router is not public, so that I cannot connect to it from outside my home. On the other hand, I also have a VM in my Azure account that I use to serve this blog. This VM can be accessed from the internet. So, by establishing a secure tunnel between my home server and my public VM, I can achieve my goal to access my home server from the internet via my VM.
The Basic Concept
There are two basic knowledge or concepts that we have to poses in order to understand the whole process that I will describe in this post:
- SSH Port forwarding
- HTTP Reverse Proxy
SSH Port Forwarding
By using the SSH port forwarding capability, we can forward access of a local port to a designated remote port. If for example we have a local website that is not accessible from the internet, and we want that local website to be accessible, we can forward the access to local website’s port to a designated remote port, so that the website can be accessed via the internet. The basic syntax is simple:
ssh -R [remote port]:localhost:[local port] [remote user]@[remote host]
For example, I can forward port 8123 that is used by Home Assistant from my localhost to my remote server that has domain name pena.id using user agus as follow:
ssh -R 8123:localhost:8123 agus@pena.id
HTTP Reverse Proxy
If the website is accessible from the internet, we have to provide at least SSL security to protect the information exchanged between the browser accessing the website to the remote server. Especially for Home Assistant, where we can change any parameters and do any action to our home automation. HTTP Reverse Proxy provides this capability, including the capability to use our own domain or subdomain name to access the local website via remote port.
For example, without HTTP Reverse Proxy, we can forward HTTP Port for Home Assistant on port 8123 to remote host pena.id at the same port. So we can access the website from the internet using URL http://pena.id:8123
. This is fine if we are ok with the fact that non standard HTTP or HTTPS port is exposed to the internet. But this is dangerous because it uses unencrypted HTTP protocol. Important information such as password can be intercepted along the way between a client browser and the remote server. So the solution is providing a secure HTTPS public website such as https://hass.pena.id
that act as a reverse proxy for the actual destination http://pena.id:8123
. And also we have to prevent direct access to HTTP port 8123 from the internet using the firewall.
Step by Step
The following step by step are built using the fundamental concepts described above. My implementation of local server is Ubuntu Jammy 22.04, and my remote server is also on Ubuntu Jammy 22.04 with nginx web server. So, the following step by step will be specific to Ubuntu Jammy operating system. But, if you understand the basic concepts, you can adapt the step by step for other operating systems as long as they support the implementation of the basic concepts.
1. Setting up a Service for Port Forwarding
The port forwarding tunnel is using SSH protocol that can be typed directly from the command prompt. However, if you want the tunnel to be automatically reestablished after any interruption, you can use another package called autossh. So, the first step is to install autossh, because this package is not installed by default in an Ubuntu server.
sudo apt install autossh -y
What about server restart? The console window will be terminated and you have to login and type the autossh command again. This is not a practical situation. We want the ssh tunnel to be automatically created after restart. In Ubuntu we can do this by creating a service using systemd
. We should create only one service template that can be parameterized by port number that we want to forward. The service parameter will be fulfilled by values from a file argument when we enable the service. For example, we can create a file argument for port 8123 as follows.
Create a new file at /etc/default/secure-tunnel@8123
, and fill it with the following values. Please adjust the REMOTE_HOST and USERNAME values accordingly.
REMOTE_HOST=pena.id
REMOTE_PORT=8123
LOCAL_PORT=8123
USERNAME=agus
Then create a systemd service called secure-tunnel@.service by typing the following command.
sudo vim /etc/systemd/system/secure-tunnel@.service
Fill the service with the following content. Pay attention to the hardcoded argument 22. It is the port number of remote SSH server. If the SSH service is not running on port 22, change it accordingly. Also pay attention to the %i parameter in the value for EnvironmentFile key. This parameter’s argument will be filled with the value from a command line while we are enabling the service.
[Unit]
Description=Setup a secure tunnel to ubuntu-vmweb on port %i
[Service]
EnvironmentFile=/etc/default/secure-tunnel@%i
Environment="AUTOSSH_GATETIME=0"
ExecStart=/usr/bin/autossh -M 0 -o "ExitOnForwardFailure=yes" -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" -NR ${REMOTE_PORT}:localhost:${LOCAL_PORT} -p 22 ${USERNAME}@${REMOTE_HOST}
RestartSec=5
Restart=always
[Install]
WantedBy=multi-user.target
Enable, start, and check the status of the service using the following commands. As you can see that we put the port number as a part of the service name. This port number matches the /etc/default/secure-tunnel@8123
we created previously.
sudo systemctl enable secure-tunnel@8123
sudo systemctl start secure-tunnel@8123
sudo systemctl status secure-tunnel@8123
It is important to to check the status of running service, since it may encounter some problems while running.
2. Setting up HTTP Reverse Proxy and SSL
The second step is performed in the remote server. Login to remote server using a user with admin privilege. Make sure nginx is already installed. Make sure you are also the owner of the domain that we will use for accessing the website. We need to add a subdomain record to the DNS entries. This step varies among registrars, so I left it for you to do it on your own. For my case, I already add hass.pena.id as the subdomain that I will use to access my Home Assistant instance from the internet.
Create a nginx configuration file for the subdomain hass.pena.id by typing the following command.
sudo vim /etc/nginx/conf.d/hass.pena.id.conf
Use the following code snippet as the content of the file.
server {
server_name hass.pena.id;
location / {
proxy_pass http://127.0.0.1:8123;
proxy_set_header X-real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
}
error_page 502 /50x.html;
location = /50x.html {
root /usr/share/nginx.html;
}
}
Pay attention at line 4. This line is the line which “forward” the request to http://hass.pena.id
to http://localhost:8123
. As addition, for the Home Assistant it is mandatory to add line 9 and line 10. At this point we should be able to access the website from the internet using URL http://hass.pena.id
.
The last thing that we have to configure is the SSL. For this purpose we use the free SSL service Lets Encrypt. Just login to the remote server’s console, and type the following command.
sudo certboot --nginx -d hass.pena.id
It will add several lines to the /etc/nginx/conf.d/hass.pena.id.conf
file related to SSL configuration. If you now open the file, you should see something similar like this.
server {
server_name hass.pena.id;
location / {
proxy_pass http://127.0.0.1:8123;
proxy_set_header X-real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
}
error_page 502 /50x.html;
location = /50x.html {
root /usr/share/nginx.html;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/hass.pena.id/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/hass.pena.id/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = hass.pena.id) {
return 301 https://$host$request_uri;
} # managed by Certbot
server_name hass.pena.id;
listen 80;
return 404; # managed by Certbot
}
Restart or reload nginx. Now we should be able to access our Home Assistant website using HTTPS protocol https://hass.pena.id which serves the content from the local Home Assistant http://localhost:8123 via an SSH secure tunnel.
Conclusion
In this post I’ve presented the basic knowledge, concept, and also practical step by step on how to configure our local installation of Home Assistant behind NAT, so that it can be accessed from the internet. In the process, we establish a secure tunnel from local server to a remote server using SSH port forwarding, using nginx reverse proxy, and then securing the website using SSL.