Expose Home Assistant on CG-NAT networks

There is no doubt that Home Assistant is a great home automation software that lets automate a wide range of home appliances. Open source and in constant evolution, is the best option I found to start with my home automation projects. It’s also great to be able to run it within your home network, on any inexpensive computer like a Raspberry Pi.

But in order to go ahead with some integrations like Alexa or Google Assistant, you need to expose your local instance to Internet.

Far gone are the days that this task was as simple as allowing port forwarding on your router, as your ISP assigned a public IP address to it that was reachable through Internet.

Nowadays, almost all ISPs implement CG-NAT, or Carrier Grade Network Address Translation. This allows the ISP to have a big network of private IP addresses assigned to each customer, and only using a smaller number of public IP addresses to access the Internet.

The downside is that you no longer have a public IP address assigned to your router, so port forwarding is no longer an option to access you local home network.

To overcome this limitation, I implemented an inverse pattern. I have a pivot computer that has public internet access, in this case in the form of a VM on AWS. This is the magic of cloud computing, you can have an inexpensive resource (I purchased a reserved instance for 3 years at only $ 100) in a matter of minutes.

To make my architecture clearer, the idea here is to have a reverse tunnel from my home network to the AWS VM, overcoming the limitations of CG-NAT; I just create the connection from my network to a public computer.

The picture completes with a couple of reverse proxies, Ngnix in this case. One running on the AWS VM taking traffic from the Internet and directing it to the reverse tunnel port, and the second one running on a local machine, the one that creates the reverse tunnel connection, redirecting the received traffic to whatever computer in my LAN I want to.

Creating the reverse tunnel

Using SSH is a common place on every Linux distribution. It allows you to start a remote session to your server and work as if locally connected to it. This normal use case allows for traffic and packets to go from your local terminal to your server.

There is a way to open a connection with traffic running on the opposite direction, that is initiating the connection from your local terminal but allowing traffic from your server to you local computer. This known as a reverse tunnel, more details here.

So moving forward I first created a script that opens the tunnel. Check the port number, make sure that you enable incoming traffic for that tunnel on your public VM, I only allowed traffic from my ISP IP range, to enhance security.

$ nano reversetunnel.sh

#! /usr/bin/env sh

# connect to the tunnel if not already connected
while true
do 
  if [ ! -n "`ps ax | grep [c]ompute-1.amazonaws.com`" ]; then
    ssh -f -N -R 10002:localhost:10002 -i /home/user/AWS/key-pair.pem [email protected]
    sleep 1m
  fi
  sleep 1m
done

This script simply creates a loop that validates the connection is up (process not dead) and sleeps for a minute.

About the specifics of the ssh command, the first set of parameters enable the reverse tunnel functionality, then you map the remote port to connect to and the local one, in this case 10002. The remaining is the location of the key pair file and the remote VM address.

Making this script executable and just running it creates the tunnel. As with usage I found that the connection may eventually drop, I created a system service to make sure that the tunnel is reconnected both if the connection drops or the process dies.

$ sudo nano reversetunnel.service

[Unit]
Description=ReverseTunnelAWS

[Service]
Type=simple
ExecStart=/home/user/AWS/reversetunnel.sh
ExecStop=/bin/kill -s QUIT $MAINPID
Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target

After creating the file run:

$ sudo systemctl start reversetunnel
$ sudo systemctl enable reversetunnel

More details on the specifics of creating a service here.

Creating the reverse proxies

I’m using Ubuntu on all my computers, in order to install Nginx just update your local repositories and install with apt:

$ sudo apt update
$ sudo apt upgrade
$ sudo apt install nginx

Lets first create the reverse proxy configuration on the AWS VM, the one that receives public traffic.

Create the reverse proxy configuration in /etc/nginx/niginx.conf (create a backup copy first):

	upstream gabolan {
		server localhost:10002;
	}

	server {
		server_name yourdomain.goeshere.com;
		access_log logs/reverseproxy.access.log;

		location / {
			proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header   Host $host;
			proxy_pass http://gabolan;
			proxy_http_version 1.1;
		}

		location /api {
			proxy_pass http://gabolan/api;
			proxy_http_version 1.1;
			proxy_set_header Upgrade $http_upgrade;
			proxy_set_header Connection "upgrade";
		}

		listen 8082 ssl;
		ssl_certificate /etc/letsencrypt/live/yourdomain.goeshere.com/fullchain.pem;
		ssl_certificate_key /etc/letsencrypt/live/yourdomain.goeshere.com/privkey.pem;
		include /etc/letsencrypt/options-ssl-nginx.conf;
	}

So in this configuration first you can find the upstream definition, which is heading to the reverse proxy port on the localhost.

Then the server definition, which takes the domain name you have for your Home Assistant implementation (this domain name should be directed to this publicly accessible VM, in my case I have it configured in Route 53). The location blocks configure how the redirection should be done. Please check that I have two blocks, one for the front end and the other one for the API access. Also consider using this headers configuration, else Home Assistant may not work correctly.

Finally, its the certificates configuration for SSL connectivity. I’m using Let’s encrypt certificates, you can configure those by following this guide.

In order to test that the config file is correct, run:

$ nginx -t

If no errors are detected, restart the Nginx service to work with the adjusted configuration:

$ sudo systemctl restart nginx

Then we need to go through the same process on the local computer that creates the reverse tunnel.

Again edit the reverse proxy configuration in /etc/nginx/niginx.conf:

	upstream homeassistant {
		server homeassistant:8123;
	}

	server { # simple reverse-proxy
                listen       10002;
                server_name  yourdomain.goeshere.com;
                access_log   /var/log/nginx/reverseproxy.access.log;

                # pass requests for dynamic content to rails/turbogears/zope, et al
                location / {
			proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header   Host $host;
			proxy_set_header X-Real-IP $remote_addr;
                        proxy_pass      http://homeassistant;
			proxy_http_version 1.1;
                }

		location /api {
			proxy_pass http://homeassistant/api;
			proxy_http_version 1.1;
			proxy_set_header Upgrade $http_upgrade;
			proxy_set_header Connection "upgrade";
		}
        }

As you can see this one is very similar to the previous one, the main difference is that the server listens on the reverse tunnel port (10002), and the upstream points towards your local Home Assistant computer/VM.

Again, check the configuration file is ok and restart Nginx to take the new configuration.

In terms of connection setup, that’s it. Just make sure you enable external access in Home Assistant, in the network configuration section, and configure the external facing domain name, else Home Assistant will reject the traffic.

Finally enable the following configuration in your configuration.yaml:

http:
  use_x_forwarded_for: true
  trusted_proxies: 100.200.300.400

This allows the use of some required headers and traffic from your local pivot computer or VM (100.200.300.400 should be replaced by your local pivot computer IP address).

Let me know if this worked for you and any improvements you may find, cheers!!!

Related Posts

ZeroTier router to access my home network

Never had the real need to access my home network from the outside but recently as working back at the office is becoming a reality, I just…

Deploying WordPress in Kubernetes exposed via Cloudflare Tunnels

So this is the story of how you are reading this blog, technically speaking. When thinking about creating this blog, I realized that already had available compute…

Add shared storage to MicroK8s Kubernetes cluster

A big feature of any Kubernetes cluster is its capability to deploy running workloads among the different compute nodes depending on many factors, but primarily assuring availability…

NFS share with external SSD Raid 1 disks

When planning to provide my Kubernetes cluster with a dynamic storage provisioning option, I decided to use NFS to be able to attach volumes to multiple nodes…

Backup script for KVM virtual machines

KVM is a great virtualization engine, open source and light way. I’m running my Home Assistant VM inside an Intel NUC without any issues and super stable….

0 0 votes
Article Rating
Subscribe
Notify of
guest

2 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Peter Hahnenbach
Peter Hahnenbach
1 year ago

Thanks for this post, really helpful!
I was wondering why you have the reverse proxy on both sides and not only on AWS VM side? My setup is running without proxy on the Home Assistant computer (forwarding directly 10002 (cloud) to 8123 (local).
Will it have any security implications?