Dashboards don't work behind reverse proxy

Looks like openDashboard in app.js uses localIp as sent by the hub, instead of the location as seen in the browser, which doesn't work when the web UI is being proxied.

Everything else works well enough behind the proxy, and I can switch to cloud instead of local to work around it, so this isn't a very big deal, but it would be nice to have that fixed.

I think changing window.location = "http://" + localIp + "/apps/api/" to window.location = window.location.origin + "/apps/api/" will fix this, but I'll take any solution that you think is appropriate.

1 Like

If anyone else is using nginx to proxy the dashboard, this is the configuration I used. It works for everything I could test, including dashboards, device event sockets, and installed apps.

location / {
    proxy_http_version 1.1;
    proxy_pass http://HUBITAT_IP/;
    proxy_set_header Accept-Encoding "";
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    sub_filter_types text/* application/javascript;
    sub_filter_last_modified on;
    sub_filter_once off;
    sub_filter "http://HUBITAT_IP" "https://SSL_DOMAIN";
    sub_filter '"http://"+localIp' 'location.origin';
    sub_filter "ws://" "wss://";
    sub_filter ' location.port : "80"' ' location.port : "443"';
}

I'm fine with this being marked closed/solved, although it would be nice if the web UI used more protocol-agnostic and relative URIs.

2.0.5 greatly improves this. Now the configuration only needs one sub_filter (funnily enough, it's the one I originally found :wink:)

location / {
    proxy_http_version 1.1;
    proxy_pass http://HUBITAT_IP/;
    proxy_set_header Accept-Encoding "";
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;

    sub_filter_types application/javascript;
    sub_filter '"http://"+localip' 'window.location.origin';
}
3 Likes

+100 to fixing this. I ran into the same issue when setting up hub access behind an Apache server specifically so I could access my local dashboards from outside my local network. Apache lets me enforce AuthN/AuthZ and apply HTTPS.

Here is what I got working in Apache 2.

  • hubitat is the local-network DNS for my hub.
  • mod_auth_openidc with Google Auth is used for AuthN/AuthZ
  • LetsEncrypt is used for a wildcard HTTPS cert allowing my single Apache server to run a bunch of secure vhosts using SNI.
<VirtualHost *:80>
    ServerAdmin eric.admin@example.com
    ServerName ha.home.example.com

    Redirect permanent / https://ha.home.example.com/

    CustomLog ${APACHE_LOG_DIR}/ha.home.example.com.log vhost_combined
    ErrorLog ${APACHE_LOG_DIR}/ha.home.example.com_error.log
</VirtualHost>

<VirtualHost *:443>
    ServerName ha.home.example.com

    # Enable request rewriting for proxy (mod_rewrite)
    RewriteEngine On
    # Rewrite requests with a Upgrade=websocket header to the ws port (mod_proxy_wstunnel)
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule /(.*)           ws://hubitat:80/$1 [P,L]
    # Rewrite requests without a Upgrade=websocket header to the http port (mod_proxy)
    RewriteCond %{HTTP:Upgrade} !=websocket [NC]
    RewriteRule /(.*)           http://hubitat:80/$1 [P,L]

    ProxyPassReverse / http://hubitat:80/

    # Rewrite the localIp variable to use the page host
    AddOutputFilterByType INFLATE;SUBSTITUTE;DEFLATE text/html text/plain text/xml
    Substitute "s|var localIp = \"[^\"]+\"|var localIp = window.location.host|i"

    SSLEngine On
    SSLCertificateFile /etc/letsencrypt/live/home.example.com/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/home.example.com/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/home.example.com/chain.pem

    # HSTS (mod_headers is required) (15768000 seconds = 6 months)
    Header always set Strict-Transport-Security "max-age=15768000"

    CustomLog ${APACHE_LOG_DIR}/ha.home.example.com.log vhost_combined
    ErrorLog ${APACHE_LOG_DIR}/ha.home.example.com_error.log

   <Location />
      AuthType openid-connect
      Require claim email:eric.example@gmail.com
   </Location>  
   # Disable auth checks for loading the UI (reduces issues with concurrent in-flight auth)
   <Location /ui2/>
       OIDCUnAuthAction pass
    </Location>
</VirtualHost>

The stuff I changed in auth_openidc.conf:

OIDCRedirectURI /protected/redirect_uri
OIDCCryptoPassphrase MY_RANDOM_SECRET
OIDCProviderMetadataURL https://accounts.google.com/.well-known/openid-configuration
OIDCScope "openid email profile"
OIDCClientID MY_CLIENT_ID.apps.googleusercontent.com
OIDCClientSecret MY_CLIENT_SECRET
OIDCStateMaxNumberOfCookies 7 true
OIDCSessionMaxDuration 0
OIDCSessionType server-cache:persistent
OIDCCacheType file
OIDCCacheDir /var/cache/apache2/mod_auth_openidc/cache
OIDCRemoteUserClaim email
1 Like

I'll have to update my reverse now !

I'll work on putting together a full writeup and post that in Code Share in a day or two. I'm still working out a few bugs with cookie header length due to how the hubitat UI does XHRs.

1 Like

I'm able to get everything but dashboards working using the second reverse proxy example for nginx above (first one won't even fully load the dashboard page).

Everything seems to be proxying correctly, but navigating to one of my dashboards spits out my hub's private IP in the request instead. To be specific, the dashboards page loads, but the dashboards themselves are what are having the problem.

I can't put screenshots in my post for some reason, but this is the request URL it sends when navigating:

Request URL: https://10.10.10.180/apps/api/33/dashboard/34?access_token=<a-token>

You probably need to tweak the sub_filter to account for native https now. When I wrote that the hub didn't have full support for https, so "http://" + localip worked.

Now that https is more supported, it's probably easier to just replace that with

sub_filter HUBITAT_IP EXTERNAL_IP_OR_ADDRESS

(so in your case, sub_filter '10.10.10.180' 'hubitat.yourdomain.net')

1 Like

Thank you - that did get me over my first hump.

As most of us don't want to expose our hub UI without auth, I do use HTTP auth in front of the hub endpoint. Here's what I use in my location block (in addition to the block you posted):

auth_basic "Restricted";
auth_basic_user_file /config/nginx/.htpasswd;

When I browse to a dashboard, every single page element wants to authenticate and it eventually gets stuck in a loop of requesting for authentication. Any ideas how I could properly auth these requests?

@soumya92's answer got me on the right track. I wanted to leave some further information here for anyone else coming here who was in the same boat I was. I set up my reverse proxies via the Application Portal on my Synology DiskStation NAS. It works pretty well for simple reverse proxies, but it does not support custom configurations, nor does the version of nginx installed by default support the sub_filter directive.

I used the instructions at GitHub - Looooopy/patch_synology_nginx to install a version of nginx built with support for sub_filter. (I changed the version in the script to 1.15.7 to match the version installed on my Synology.)

Then I moved the reverse proxy configuration out of its default location (/etc/nginx/app.d/server.ReverseProxy.conf) into a new file (/usr/local/etc/nginx/sites-enabled/nginx-reverse-proxy.conf) and added the sub_filter directive. I also needed to change the SSL certificate paths to the system default (in /usr/syno/etc/certificate/system/default), since deleting the (now-redundant) reverse proxy in Application Portal will cause it to delete the copy of the certs it had made.

Then restarting nginx with synoservicectl --restart nginx let me use SSL access to my Hubitat hub and all my dashboards.

I hope this might be useful to anyone else using a Synology NAS to reverse proxy their Hubitat UI.

Hi Soumya - I'm having what I think is a similar issue with this... any help/feedback would be appreciated.

Basically I am trying to set up a 'guest' dashboard for my house (going to use it as a short-term vacation rental) - the idea was I would create a local DNS server (pi-hole), create a custom entry for 'house.lan', or something similar, and point it at the LAN link URL for the guest dashboard I created in Hubitat. Obviously DNS only can resolve host name to IP, so won't re-direct to a specific URL. To have 'house.lan' point at the Dashboard LAN link URL specifically I stood up an nginx reverse proxy on another raspberry pi. Now the traffic is directly correctly, and hits the page correctly, but the 'loading dashboard' logo keeps spinning an it won't load. I assume this is something related to how javascript is passed through the reverse proxy.

Here is my default file for my nginx server - pretty simple:
server{
listen 80;
server_name house.lan;

    location / {
     proxy_pass "Local Lan Dashboard Link";
    }

}

Also, I tried using your above config, and I get a slightly different behaviour - the page just loads completely white, no spinning hubitat dashboard loading logo or anything.

server{
listen 80;
server_name house.lan;

    location / {
     proxy_pass "Local Lan Dashboard Link";
     proxy_set_header Accept-Encoding "";
     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection "upgrade";
     proxy_set_header Host $host;

     sub_filter_types application/javascript;
     sub_filter '"http://"+localip' 'window.location.origin';
    }

}

it is quite annoying to have to do this. There is no real solution for HAProxy (on pfSense), as it cannot rewrite in the same way as apache/nginx.. it would be nice if hubitat just supported Let's Encrypt natively :slight_smile:

It looks like that with the upgrade to version 2.2.6.130 that this nginx setup is no longer working.
Is there some extra filtering that is now required?