We are going to learn how to enable external access to our Home Assistant instance using nginx reverse proxy and securing it with Let’s Encrypt ssl certificates.
Although I wrote this procedure for Home Assistant, you can use it for any generic deployment where you need to implement automatic renew of your certificates using the certbot webroot plugin.
I have tested this tutorial in Debian 10, Ubuntu 20.04 and Raspberry PI OS, but it should also work on other versions of Linux with minimal modifications. It also assumes that some previous prerequisites are met:
- Our system has nginx installed, you can install it with ‘apt install nginx’.
- You have shell access as root or as a user with sudo privileges.
- A domain name pointing to our public ip address. You can purchase your own custom domain or get a free one for this in sites like afraid.org or noip.com.
- Ports 80 and 443 properly forwarded in your router to the host where nginx is installed.
- Your Home Assistant instance is listening in http://<ip>/:8123
Note: If you have just installed nginx it is advisable to add ‘deny all;’ to the default host at ‘/etc/nginx/sites-enabled/default’ to prevent the default virtualhost to be accessed.
Security advise: be sure that Home Assistant users have a strong password set, this is very important (ie. long password using numbers, letters and punctuation marks).
Certificate creation
Firstly we will install certbot with our favourite package manager.
apt install certbot
Next generate a proper DHE (Diffie Hellman Ephemeral) parameters file. This will take a long time but it will provide you with much more security than using shorter keys. Change 4096 to 2048 if you want to use a shorter key.
openssl dhparam -out /etc/nginx/dhparam.pem 4096
We will be using webroot plugin to create our certificates. It works by creating a temporary file for validating the requested domain in a predefined path. As a result Let’s Encrypt will make an http request to this file to validate that the requested domain belongs to the server where certbot runs.
Therefore create the directory were certbot will generate the temporary files to verify our domain. We will be using the same folder for all verifications we may need.
mkdir -p /var/www/certbot
Also we will create a code snippet that we can use including it in any virtualhost for which we need to create certificates. This is the code snippet , so save it as ‘certbot.conf’ inside the snippets directory within nginx configuration (ie. /etc/nginx/snippets/). Create the snippets directory if it does not exists.
# /etc/nginx/snippets/certbot.conf location ^~ /.well-known/acme-challenge/ { default_type "text/plain"; root /var/www/certbot; }
Now we will create a new virtualhost for our domain. ‘certbot.conf’ file will be included into this virtualhost. Create the file ‘/etc/nginx/sites-available/example.com.conf’. We will use example.com as the domain, do not forget to change it to your own domain. We will expand this virtualhost later to serve our Home Assistant instance.
# /etc/nginx/sites-available/example.com.conf server { server_name example.com; listen *:80; include snippets/certbot.conf; access_log /var/log/nginx/example.com-access.log; error_log /var/log/nginx/example.com-error.log; }
Make a symbolic link from sites-enabled folder to our virtualhost file and reload nginx configuration.
cd /etc/nginx/sites-enabled/ ln -s ../sites-available/example.com.conf example.com.conf service nginx force-reload
Then we can issue the certbot command that will generate our certificates. Change the email address and domain name by yours.
certbot certonly --agree-tos --email youremail@example.com --webroot -w /var/www/certbot/ --deploy-hook "service nginx reload" -d example.com
If everything went fine, your certificate and key should be located at ‘/etc/letsencrypt/live/example.com/’ so we will use them to reconfigure our virtualhost in next steps of this procedure.
Configure access for Home Assistant
Finally, modify our virtualhost to securely serve Home Assistant. Edit file ‘/etc/nginx/sites-enabled/example.com.conf’ to make it looks as follow.
# /etc/nginx/sites-enabled/example.com.conf map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { server_name example.com; listen *:80; include snippets/certbot.conf; location / { return 301 https://$host$request_uri; } access_log /var/log/nginx/example.com-access.log; error_log /var/log/nginx/example.com-error.log; } server { server_name example.com; root /var/www/example.com; access_log /var/log/nginx/example.com-access.log combined; error_log /var/log/nginx/example.com-error.log; listen *:443 ssl http2; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; include snippets/sslandheaders.conf; proxy_buffering off; location / { proxy_pass http://<ip>/:8123; proxy_set_header Host $host; proxy_http_version 1.1; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } location /api/websocket { proxy_pass http://<ip>:8123/api/websocket; proxy_set_header Host $host; proxy_http_version 1.1; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } }
A few things are going on with this new vhost file:
- We have mapped some variables that will be used later in order to ensure that connections that use websockets work properly.
- A new location added to our server block on port 80. This location will redirect all non certbot verification requests to the same uri but using https schema (port 443).
- We have added a new server block to handle ssl requests with the certificates we created properly configured.
- Two locations to forward all the requests to our Home Assistant instance.
- Also we include a new snippet that holds the ssl configuration and some security headers. It is called ‘sslandheaders.conf’, save it in nginx snippets folder. You can find the content of this file below.
# /etc/nginx/snippets/sslandheaders.conf ssl_dhparam /etc/nginx/dhparam.pem; ssl_ecdh_curve secp384r1; ssl_protocols TLSv1.2 TLSv1.1; ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; add_header X-Content-Type-Options nosniff; add_header X-Frame-Options "SAMEORIGIN"; add_header Strict-Transport-Security "max-age=31536000"; add_header X-XSS-Protection "1; mode=block";
Do not forget to reload nginx configuration for the changes to take effect.
service nginx reload
After this, you will be able to access Home Assistant externally using your domain url (ie. https://example.com).
Automate certificate renewals
If you followed this guide, automated renewal is provided by the certbot package you installed. If you want to check this, check crontab file content ‘/etc/cron.d/certbot’ and systemd timers with the command ‘systemctl list-timers’. Everything is already configured, so no action needs to be taken.
Final thoughts
We have seen how to enable external access to our Home Assistant instance using nginx reverse proxy and securing it with Let’s Encrypt ssl certificates. I hope this has been helpful for you. If you have any doubt or comment, do it below.
Hi and thank you for that guide.
In my case, I had to create the folder “/.well-known/acme-challenge/” within “/var/www/certbot” for it to work. I thought it would but the challenge files directly into “/var/www/certbot” and that configuring this as the root for my domain as described in your guide would search for the files there.
When it didn’t work, I looked into the error log and saw that it actually looks for the file in the subfolder. When I created that subfolder, certbot was able to put the files there and everything worked.
Maybe you can add the note to the guide, that you not only have to create “/var/www/certbot” bot probably also the “./.well-known/acme-challenge” subfolder.
Thanks again for the guide 🙂
Hi, thanks for your comment.
It sounds strange to me that the certbot script do not create the ‘.well-known/acme-challenge/’ directory by itself. I have several different installations and never faced that issue. The certbot script creates the directories and token to perform the challenge and after that it deletes them and leaves the webroot directory empty.
Certbot web calls seeks for the token in that directory ({webroot}/.well-known/acme-challenge/) as configured in the nginx snippet ‘certbot.conf’.
Also I can see in the certbot source code the function that creates those folders. I have checked only the sources for the latest certbot release (1.13.0) and the one included in Debian 10 (0.31.0-1) and both have almost the same function to create the directories.
I do not know what exactly can cause the behaviour that you describe, it could be for several reasons.
Thanks again for your comment, it can help other users that may face the same issue. If you have some comment or need any help, please let me know. Regards.
Jere, thank you for an excellent writeup. The time you took to document all this is much appreciated.
Thanks for your comment, I truly appreciate it.
Great post!! Thanks Jere for your time and knowledge.
HINT: For homeassistant v2021.7.0 and above you will have to add the following code to your configuration.yaml.
http:
use_x_forwarded_for: true
trusted_proxies:
– XXX.XXX.XXX.XXX # Add the IP address of the proxy server
Thanks for the hint freddd 😀 I have updated the post with this info. Very good catch. Happy cacharring!