NGnix - So Confused - Doing something wrong!

Hey All-

Platform: HA .38.3 rpi3 Jessie (full)

So I went through all my config without opening up to the world… Now that I am happy with my configuration, I decided it was time to re-enable NGinx. I installed NGinx, used Let’s Encrypt and forwarded my router.

When my http section had nothing more than the api_password, all was ok (before using NGinx). Now that I am secure (and NGinx is forwarding bth 443 and 80 fine), I can’t login. If I leave my HA http section as is, I get the login page but get invalid login (whether or not I enable the api_password).

When I set the cert stuff in HA, I get the following errors (and the cert and key paths are the exact same as what is configured in NGinx). (Replaced my domain with )

Questions:

  • If I set SSL on NGINX, do I still need to set the cert stuff on HA in the configuration.yaml?
  • If I set SSL AND Basic Auth in NGinx, should I still set a password in HA, or rely on basic http auth (I will likely set satisfy rules in NGinx for my 192… network once I get this stuff right)
    ** when I comment out the password in HA config, it still drops me on the password page.
  • If forwarded 80/443 via NGinx, do I set my base_url parameter to :8123 or, , or :8123, or <127.0.0.1>???

I can access HA fine via the 192 address internally, but when I access via my DDNS address, I get the splash login page, but can’t login.

Error in my log (BTW, I set both Key and cert files to sudo chmod 775…)

My configuration: https://github.com/alanranciato/home-assistant-config

17-02-26 15:44:05 ERROR (MainThread) [homeassistant.bootstrap] Invalid config for [http]: not a file for dictionary value @ data['http']['ssl_certificate']. Got '/etc/letsencrypt/live/<MY PERSONAL PATH>/fullchain.pem'
    not a file for dictionary value @ data['http']['ssl_key']. Got '/etc/letsencrypt/live/<MY PERSONAL PATH>/privkey.pem'. (See /home/homeassistant/.homeassistant/configuration.yaml, line 24). Please check the docs at https://home-assistant.io/components/http/
    17-02-26 15:44:05 ERROR (MainThread) [homeassistant.bootstrap] Not initializing history because not all dependencies loaded: http
    17-02-26 15:44:05 ERROR (MainThread) [homeassistant.bootstrap] Not initializing media_player because not all dependencies loaded: http
    17-02-26 15:44:05 ERROR (MainThread) [homeassistant.bootstrap] Not initializing api because not all dependencies loaded: http
    17-02-26 15:44:05 ERROR (MainThread) [homeassistant.bootstrap] Not initializing websocket_api because not all dependencies loaded: http
    17-02-26 15:44:05 ERROR (MainThread) [homeassistant.bootstrap] Not initializing frontend because not all dependencies loaded: api, websocket_api
    17-02-26 15:44:05 ERROR (MainThread) [homeassistant.bootstrap] Not initializing tts because not all dependencies loaded: http
    17-02-26 15:44:05 ERROR (MainThread) [homeassistant.bootstrap] Not initializing camera because not all dependencies loaded: http
    17-02-26 15:44:05 ERROR (MainThread) [homeassistant.bootstrap] Not initializing logbook because not all dependencies loaded: frontend
    Failed config
      http: 
        base_url: <MY PERSONAL PATH>:8123
        ssl_certificate: /etc/letsencrypt/live/<MY PERSONAL PATH>/fullchain.pem
        ssl_key: /etc/letsencrypt/live/<MY PERSONAL PATH>/privkey.pem

Do you want to use http and https? or force port 80 to go to 443 to create a secure connection.

I have a nginx reverse proxy setup with DDNS and lets encrypt running with personal certificate auth with fallback to basic auth. I never was able to get the satisify rule to work with my LAN.

I followed this thread and managed to put together something that works.

configuration.yaml

http:
   server_host: 127.0.0.1

Removed api_password

I have my nginx configuration broken up, so nginx.conf is just the default and a custom file in /etc/nginx/sites-available/

hass

server {
        # Update this line to be your domain
        server_name domain.duckdns.org;

        # These shouldn't need to be changed
        listen 80;
        # listen [::]:80  ipv6only=on;
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;

        # Update this line to be your domain
        server_name domain.duckdns.org;
        ssl on;
        # Ensure these lines point to your SSL certificate and key
        ssl_certificate /etc/letsencrypt/live/domain.duckdns.org/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/domain.duckdns.org/privkey.pem;
        ssl_client_certificate /etc/nginx/ssl/auth/client.pem;
        ssl_verify_client optional; # or `on` if you require client key

        # These shouldn't need to be changed
        ssl_protocols TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-$
        ssl_session_timeout 1h;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets on;
        ssl_session_ticket_key file.key;

        # OCSP Stapling ---
        # fetch OCSP records from URL in ssl_certificate and cache them
        ssl_stapling on;
        ssl_stapling_verify on;

        #ssl_buffer_size 16k;   #for throughput, video applications
        ssl_buffer_size 4k;     #for quick first byte delivery
        client_body_buffer_size 8K;
        client_max_body_size 20m;
        client_body_timeout 10s;
        client_header_buffer_size 1k;
        large_client_header_buffers 2 16k;
        client_header_timeout 5s;
        proxy_buffering off;
        proxy_connect_timeout 90;
        proxy_send_timeout 90;
        proxy_read_timeout 90;
        proxy_buffers 32 4k;
        server_tokens off;

        add_header Strict-Transport-Security "max-age=31536000; includeSubdomains" always;
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Xss-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;

        keepalive_timeout 40;

        location / {
            if ($ssl_client_verify != SUCCESS)
            {
                ## if no ssl-client-auth forward to port 444 (see below)
                proxy_pass https://127.0.0.1:444;
            }
            proxy_pass http://127.0.0.1:8123;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_redirect http:// https://;
            proxy_http_version 1.1;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

    }

## fallback server for basic-auth if ssl-client-auth failed
server {
   listen 444 ssl http2;
   server_name domain.duckdns.org;

   ssl_certificate /etc/letsencrypt/live/domain.duckdns.org/fullchain.pem;
   ssl_certificate_key /etc/letsencrypt/live/domain.duckdns.org/privkey.pem;

    # These shouldn't need to be changed
        ssl_protocols TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-$
        ssl_prefer_server_ciphers on;
        ssl_session_timeout 1h;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets on;
        ssl_session_ticket_key file.key;

        # OCSP Stapling ---
        # fetch OCSP records from URL in ssl_certificate and cache them
        ssl_stapling on;
        ssl_stapling_verify on;

        #ssl_buffer_size 16k;   #for throughput, video applications
        ssl_buffer_size 4k;     #for quick first byte delivery

        client_body_buffer_size 8K;
        client_max_body_size 20m;
        client_body_timeout 10s;
        client_header_buffer_size 1k;
        large_client_header_buffers 2 16k;
        client_header_timeout 5s;

        proxy_buffering off;
        proxy_connect_timeout 90;
        proxy_send_timeout 90;
        proxy_read_timeout 90;
        proxy_buffers 32 4k;

   satisfy any;
   allow 192.168.x.0/24;
   deny all;

   auth_basic "Restricted";
   # Create this with: htpasswd -c /etc/nginx/.htpasswd some-username
   # If you add more users, omit the -c
 auth_basic_user_file /etc/nginx/.htpasswd;

 location / {
            proxy_pass http://127.0.0.1:8123;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_redirect http:// https://;
            proxy_http_version 1.1;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
}

This is very crude and probably much simpler to implement, but it seems to work. Basically port 80 requests are redirected to https, and a simple check personal certificate check is asked. I have it optional so it can be denied and just use basic authentication. The fallback goes to another port (444) that allows authentication.

Please make sure your port forward this port or whatever port you change it to.

Thanks. It’s very similar to what I have with slight tweaks. I will do some comparing and see what shakes out. It is very strange that removing the api_password in HA still drops me on the sign in page (and doesn’t let me in) if I’m coming from my domain, but all is fine if I bypass NGnix and hit via 192:::8123.

All of the cert stuff works via NGnix as I can inspect and see it all correctly. I used this article to get me going: https://objectpartners.com/2016/05/26/setting-up-nginx-and-ssl-for-home-assistant/

Make sure the websocket support is included in the location, the link above doesn’t work (https://objectpartners.com/2016/05/26/setting-up-nginx-and-ssl-for-home-assistant/)

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;

        # WebSocket support (nginx 1.4)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

}

Thanks! Now I am so close. Your tweaks helped. Now it works - even on mobile, UNLESS I turn on basic auth in mobile. If I do it from my laptop (on local network, but using domain), I get prompted for u/pass from nginx and it drops me at the HA page.

If I come in from mobile (using domain - if I use 192.1.1.54:8123, it’s fine), it prompts for u/p and if correct, churns a bit then drops me to the HA signin page (I have signin disabled in HA). I have tried safari, chrome and FF on mobile with the same result.

Also, doesn’t seem that the satisfy_all rule is working as it is still prompting whether I am on a 192.1.1.x address or not.

Here is my nginx config (main config is default. This is from the site_enabled/default file:

server {
        listen 80;
        server_name <my domain - no port>;
        rewrite ^ https://$host$request_uri permanent;
     }

server {
        # SSL configuration
        #        #
        listen 443 ssl default_server;
        server_name <my domain - no port>; # Your site

        proxy_buffering off;

        ssl_certificate /etc/letsencrypt/live/<my domain>/fullchain.pem; # The key location from earlier
        ssl_certificate_key /etc/letsencrypt/live/<my domain>/privkey.pem; # The key location from earlier
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers <REDACTED>
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout 10m;
        add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        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;

            # WebSocket support (nginx 1.4)
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";

            satisfy all;
            allow 192.168.1.1/24;
            auth_basic "Home Assistant";
            auth_basic_user_file /etc/nginx/.htpasswd; # Your .htpasswd file

       }

}

I’ve noticed with Chrome that it’ll cache the credentials, but not pass them (or something like that - the NGINX logs show 401 errors). If I clear the cache storage and local storage for my HA instance, it’ll then prompt for the credentials, and it all works again.

1 Like

Hmm… been trying with new incognito windows with the same issues. I get prompted for creds each time with no issue. Just cleared on mobile and still having problem.

So this time, did this:

  • Tethered laptop to mobile and it worked on laptop (multiple browsers on osx)
  • On mobile (iOS), cleared cache and history in chrome, safari and FF. All three browsers prompted for creds from nginx and dropped me at the hass login page (I don’t have hass login enabled). I can get in on wifi mobile by hitting the ip:8123 directly, but the domain on 443 does not work on any mobile browser (but seems to be working via laptop tethered to the same connection fine).

I couldn’t get the satisify condition to work out. I just made a client certificate and put on my phone and computer so I could skip the basic auth.

I had same issues and found that the api needed to be accessible without auth.

     location /api {
         proxy_pass http://127.0.0.1:8123/api;
           # WebSocket support (nginx 1.4)
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
                                      }

    location / {

         if ($ssl_client_verify = SUCCESS) {
            set $auth_basic off;
        }

            auth_basic $auth_basic;
            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;

            # WebSocket support (nginx 1.4)
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade"; }

Also I have put auth_basic_user_file in the server directive and not the location

auth_basic_user_file /etc/nginx/.htpasswd; # Your .htpasswd file

So the if statement checks to see if the personal ssl certificate was used and turns off basic authentication. If invalid or no certificate is used or found, prompts for authentication.

Thanks, your configuration seem to woork. I have only a question: if I expose the “location /api” as you did it’s safe or i’m giving ability to someone to control my switchse, lights, ecc?

EDIT: as I read here: (REST API | Home Assistant Developer Docs)

The API accepts and returns only JSON encoded objects. All API calls have to be accompanied by the header X-HA-Access: YOUR_PASSWORD (YOUR_PASSWORD as specified in your configuration.yaml file in the http: section).

So If i put in configuration.yaml:

http:

Uncomment this to add a password (recommended!)

api_password: “PASSWORD”

EDIT2:

in configuration.yaml I had also:

trusted_networks:
    - 127.0.0.1

so I think that with HickHackerz’s configuration the api are exposed to internet without any authentication. Someone can confirm?

Your correct to assume the /api location is not authenticated. At the time I was looking to get ssl client authentication working so I would not have to enter a password for HA.

Since nginx is running reverse proxy, the trusted_networks: will not work correctly. I lived with fact the api was not secure, and hoping someone would provide an elegant solution.

at the end I solved with a lua cookie using certification authenticantion and if not authenticated with a certificate fall back to authentication with password. All using Let’s encrypt SSL.

here is my nginx sites_available config (XXXX is for deleted sensitive data):

init_by_lua 'ACCESS_TOKEN_VALUE = ngx.md5("" .. math.random(10000, 90000))';

server {
    # Update this line to be your domain
    server_name XXXX.XXXX.XXXXX;

    # Ensure these lines point to your SSL certificate and key
    ssl_certificate /etc/nginx/sslau/cert.pem;
    ssl_certificate_key /etc/nginx/sslau/key.pem;
    # Ensure this line points to your dhparams file
    ssl_dhparam /etc/nginx/ssl/dhparams.pem;

    ##Client auth
    ssl_client_certificate /etc/nginx/sslcl/ca.crt;
    #ssl_crl                /etc/nginx/sslcl/ca.crl;
    ssl_verify_client      optional;
    auth_basic_user_file /etc/nginx/.htpasswd;

    # These shouldn't need to be changed
    listen [::]:XXXX default_server ipv6only=off; # if your nginx version is >= 1.9.5 you can also add the "http2" flag here
    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
    ssl on;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;

    proxy_buffering off;
    proxy_redirect http:// https://;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;

    
    location /auth {
                 if ($ssl_client_verify = SUCCESS) {
                    set $auth_basic off;
               }
                auth_basic $auth_basic;
                access_by_lua '
                    local expires = 3600 * 24 * 30 -- 30 days
                    ngx.header["Set-Cookie"] = "ACCESS_TOKEN=" ..
                                                ACCESS_TOKEN_VALUE ..
                                               "; Path=/; Expires=" ..
                                               ngx.cookie_time(ngx.time() + expires)
                    return ngx.redirect("/");                ';
        }


    location / {
            access_by_lua '
                local cookie_value = ngx.var.cookie_ACCESS_TOKEN
                if cookie_value == ACCESS_TOKEN_VALUE then
                    return
                end

                ngx.exec("/auth/")
            ';

            proxy_pass          http://127.0.0.1:XXXX;

            proxy_http_version  1.1;
            proxy_set_header    Upgrade $http_upgrade;
            proxy_set_header    Connection "upgrade";
            proxy_set_header    Host            $host;
            proxy_set_header    X-Real-IP       $remote_addr;
            proxy_set_header    X-Forwarded-for $remote_addr;
            proxy_buffering     off;
            proxy_read_timeout  386400;
        }
}