HomeAssistant NGINX SSL proxy setup

I have seen many people ask about settings up SSL support for HomeAssistant. It was mentioned that someone should write up a guide on how this should be done. I am by no means an expert with NGINX or SSL, but I have managed to get a working config with grade A SSL from https://www.ssllabs.com/ssltest/. Below are the steps I took to get setup with an NGINX SSL proxy using a Let’s Encrypt cert on Ubuntu 14.04, your results may very.

Step 1 - Install NGINX

Make it run at boot

At this point NGINX should be running and you can check by visiting YOUR_IP

Step 2 - Obtain your SSL cert

I will show the steps I took to use Let’s Encrypt certs, but if you want to use a self-signed cert look here https://www.digitalocean.com/community/tutorials/how-to-create-a-ssl-certificate-on-apache-for-ubuntu-14-04 (Start at step 2)

git clone https://github.com/letsencrypt/letsencrypt cd letsencrypt/ ./letsencrypt-auto --server \ https://acme-v01.api.letsencrypt.org/directory --help

Once downloaded, you will need to get your cert, run the below and follow the promts

After running, letsencrypt-auto will display the location of the created .pem files. /etc/letsencrypt/live/YOUR_DOMAIN/

Step 3 - Configure NGINX

*Not sure if this is required
Change the permissions on the YOUR_DOMAIN folder and it’s content by default they can’t only be access by root

Generate stronger Diffie Hellman Ephemeral parameter

cd /etc/ssl/certs sudo openssl dhparam -out dhparam.pem 4096

NGINX config

I am using the default sites-enabled file under /etc/nginx/sites-enabled/ not sure if this is the proper way, but it works.
Replace test.com with your host.domain

[code]server {
listen 80;
server_name test.com;
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl;
server_name test.com;

    ssl on;
    
    # For self signed certs
    # ssl_certificate /etc/nginx/cert.crt;
    # ssl_certificate_key /etc/nginx/cert.key;
    
    # For Let's Encrypt certs
    ssl_certificate /etc/letsencrypt/live/test.com/fullchain.pem; # /etc/nginx/cert.crt;
    ssl_certificate_key /etc/letsencrypt/live/test.com/privkey.pem; # /etc/nginx/cert.key;
    
    # Things for better security
    ssl_session_cache shared:SSL:10m;
    # I am using all modern devices and browsers and don't need TLSv1 if you need it, add to the list below
    ssl_protocols TLSv1.1 TLSv1.2;
    ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    # For quicker streaming updates in HA. Thanks @stephenmg12
    proxy_buffering off;

    location / {
            proxy_pass http://localhost:8123;
    }

} [/code]

Step 4 - When it comes time to renew your cert

Update letsencrypt

cd letsencrypt/
git fetch

Stop the NGINX service

sudo service nginx stop

Test/dry run for the renewal

 ./letsencrypt-auto renew --dry-run --agree-tos --email [YOUR_EMAIL]

You should get the below message
"Congratulations, all renewals succeeded. The following certs have been renewed"

Actually do the renew this time

 ./letsencrypt-auto renew --agree-tos --email [YOUR_EMAIL]

You should get the below message
"Congratulations, all renewals succeeded. The following certs have been renewed"

Start NGINX. For some reason I had to restart NGINX a few times before the update cert was displayed in my browser.

Make sure it all works

At this point you should be able to restart NGINX and access it from test.com which is really redirecting to your ha host at port 8123

If you are able to access it externally, try running the ssllabs tool above on your site and you should receive a letter ranking of A unless you use a self-signed cert in which case you will receive a T but it will state “If cert was trusted, your grade would have been X”

@nkgilley noticed an issue when using SSL and entity_pictures from an http site where Chrome (maybe all browsers) would state “Your connection to this site is private, but someone on the network might be able to change the look of the page.” This is because the image is getting pulled from an non-secure site. To fix this, either access the site from https or use local images.

Again, I am no expert when it comes to NGINX or SSL so if anyone has any suggestions for improvement please let me know.

6 Likes

Awesome guide! I’ll have to try this myself soon too.

As added security, you can add the following to your home assistant config to only serve to requests from the same machine (=nginx requests):

http:
  server_host: 127.0.0.1
  # Optional, change port
  server_port: 8800
3 Likes

Regarding external images causing the padlock to go away, I noticed that viewing the Map page will cause the padlock to go away.

I get this error in Chrome:

Mixed Content: The page at 'https://xxxxxx.com/map' was loaded over HTTPS, but requested an insecure image 'http://otile1.mqcdn.com/tiles/1.0.0/osm/9/143/194.png'. This content should also be served over HTTPS. map:1 Mixed Content: The page at 'https://xxxxx.com/map' was loaded over HTTPS, but requested an insecure image 'http://otile1.mqcdn.com/tiles/1.0.0/osm/9/148/193.png'. This content should also be served over HTTPS.

I’ll note that as a bug Nolan. Let’s see if we can find a tile provider for the maps that serve over SSL.

As an experiment to see if we can achieve the same result without NGINX, I’ve sneaked in two extra configuration options for the http component in release 0.9.1: ssl_certificate and ssl_key. See the docs here.

TooAngel tested it for me and he got it working with Let’s Encrypt.

The goal is to make it super easy for people to get started with Let’s Encrypt and start securing their connection to Home Assistant.

I haven’t put this up big on the blog yet as I want to make sure our getting started is updated to emphasize the importance for getting SSL setup.

Please test this out on your local setup and let me know of any culprits that you encounter.

Thanks @w1ll1am23, I couldn’t figure out why streaming updates were broken on mine, your config gave me the last bit of info I needed:

proxy_buffering off;

Hi
Thx for this guide. I have tried it my self on my rPi, but something goes wrong when try the Test/dry run.
The following is what it tells me.
I’m kind of newbie to all this, but some advice would be much appriciated.

Processing /etc/letsencrypt/renewal/mysite.duckdns.org.conf

2016-06-24 15:06:55,427:WARNING:certbot.renewal:Attempting to renew cert from /etc/letsencrypt/renewal/mysite.duckdns.org.conf produced an unexpected error: Failed authorization procedure. mysite.duckdns.org (tls-sni-01): urn:acme:error:connection :: The server could not connect to the client to verify the domain :: Failed to connect to 80.162.xxx.xxx:443 for TLS-SNI-01 challenge. Skipping.
** DRY RUN: simulating ‘certbot renew’ close to cert expiry
** (The test certificates below have not been saved.)

All renewal attempts failed. The following certs could not be renewed:
/etc/letsencrypt/live/mysite.duckdns.org/fullchain.pem (failure)
** DRY RUN: simulating ‘certbot renew’ close to cert expiry
** (The test certificates above have not been saved.)
1 renew failure(s), 0 parse failure(s)

IMPORTANT NOTES:

  • The following errors were reported by the server:

    Domain: mysite.duckdns.org
    Type: connection
    Detail: Failed to connect to 80.162.xxx.xxx:443 for TLS-SNI-01
    challenge

    To fix these errors, please make sure that your domain name was
    entered correctly and the DNS A record(s) for that domain
    contain(s) the right IP address. Additionally, please check that
    your computer has a publicly routable IP address and that no
    firewalls are preventing the server from communicating with the
    client. If you’re using the webroot plugin, you should also verify
    that you are serving files from the webroot path you provided.

Did you stop Nginx before you attempted to run the dry run?

Yes, followed the guide and used the sudo service nginx stop

Not sure that will work, can you run the following command

ps -p 1 -o comm=

If it outputs systemd then try running

sudo systemctl stop nginx.service

thx for the help, but i did not help, i get the same error when trying

I know you are performing the dry run but I was getting similar errors when I was performing Step 2. I ended up opening up port 80 on my router and ran ./letsencrypt-auto certonly --standalone-supported-challenges http-01. Since you will have to open up 443 in the end, you can try; ./letsencrypt-auto certonly --standalone-supported-challenges tls-sni-01. I got the information from https://certbot.eff.org/docs/using.html#standalone. I have not tried the renew yet. I hope this helps.

For anyone else that get these errors when they attempted to start NGINX after Step 3. You will get this error if you domain name is too long. :slight_smile:

Jun 27 04:13:43 nginx[3487]: nginx: [emerg] could not build server_names_hash, you should increase server_names_hash_
Jun 27 04:13:43 nginx[3487]: nginx: configuration file /etc/nginx/nginx.conf test failed
Jun 27 04:13:43 systemd[1]: nginx.service: Control process exited, code=exited status=1
Jun 27 04:13:43 systemd[1]: Failed to start A high performance web server and a reverse proxy server.

Uncomment or add the following line to the http section in /etc/nginx/nginx.conf
http {
server_names_hash_bucket_size 64;
}

@w1ll1am23 thanks for the guide,can i also use the same for a subdomain?
eg- https://mydomain/hass to point to http://192.168.1.2:8123

I cant seem to get this to work.

I would think so, I am doing it, but it redirects to /. This is my config.

    location / {
            proxy_pass http://192.168.X.X:8123;
    }

    location /home {
            rewrite ^ http://domain.com;
            proxy_pass http://192.168.X.X:8123;
    }

Do you want to have something else at / and only have HA at /hass?

Like @vswraith I want HA at a subdomain /hass but like you said, I also want something else at /

Only problem is, no matter what subdomains I setup, everything tries to serve from the proxy assigned to root. All subdomain proxy_pass commands are ignored and everything tries to serve from the root “/” proxy_pass

Virtual and subdomains are easy in Apache, but Nginx always screws up for me. Too many rules and one mistake and everything falls apart. I just want it to work.

Sorry for the outburst, but I’ve been trying to get Nginx to do a simple task for months now. They made what should have been a lightweight alternative to Apache into a confusing pain in the you-know-what. Nginx configurations are overly complicated for a simple task.

Think I’ll go back to Apache for Hass… Probably get it up and running in short order.

So this works fine for other applications, for example my deluge config is below.

    location /deluge {
            proxy_pass http://localhost:8112/;
            proxy_set_header X-Deluge-Base "/deluge/";
    }

deluge has the option for changing the base URL though.

–base /deluge/ it sounds like hass might need an option to handle changing the base URL.

Looks like this has been discussed. https://github.com/home-assistant/home-assistant/issues/805

Is that your only location? If so, add a / location as well, but proxy passing to a different site and tell me that /deluge doesn’t start routing there as well and ignoring the proxy pass to http://localhost:8112/ that you are currently using. I bet it does. Everything for me forwards to the / location proxy pass no matter what I do.

If not, could you post all of your locations so I can see how you have working subdomains that are routing correctly along with the “/” location? And I don’t mean subdomains that you have purposely forwarding back to the “/” like your /hass example, but subdomains pointing to different proxy pass address than /

Would be very appreciated!

This is my complete config, I did just start having issues after upgrading my server from ubuntu 14.04 to 16.04 where every now an then things start redirecting to / for no reason. If I clear my browsers cache it is good to go no need to restart nginx.

server {
        listen 80;
        server_name domain.com;
        return 301 https://$server_name$request_uri;
        client_max_body_size 25M;
}


server {
        listen 443 ssl;
        server_name domain.com;

        ssl on;
        ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;
        ssl_session_cache shared:SSL:10m;
        ssl_protocols TLSv1.1 TLSv1.2;
        ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
        ssl_prefer_server_ciphers on;
        ssl_dhparam /etc/ssl/certs/dhparam.pem;
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

        proxy_buffering off;

        location / {
                proxy_pass http://192.168.x.x:8123;
        }

        location /home {
                rewrite ^ http://domain.com;
                proxy_pass http://192.168.x.x:8123;
        }

        location /plex/ {
                proxy_pass http://localhost:32400/web/;
        }

        location /sonarr {
                proxy_pass http://127.0.0.1:8989;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

		location /deluge {
                proxy_pass http://localhost:8112/;
                proxy_set_header X-Deluge-Base "/deluge/";
        }

        location /sabnzbd {
                proxy_pass http://localhost:8080;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        location /couchpotato {
                proxy_pass        http://localhost:5050/couchpotato;
        }

        location /octoprint/ {
                proxy_pass http://192.168.x.x/;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Scheme $scheme;
                proxy_set_header X-Script-Name /octoprint;
        }

        location /octoprint/sockjs {
                proxy_pass http://192.168.x.x/sockjs; # NO trailing slash here!
                proxy_http_version 1.1;
                proxy_redirect off;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
       }

        location /webcam/ {
            proxy_pass http://192.168.x.x:8080/;
        }

}
1 Like

Wow, that is awesome… I’ll compare it to mine and see if there is anything I can spot. Thanks!!!