Reverse proxy using NGINX

:warning: This guide has been migrated from our website and might be outdated. Feel free to edit this guide to update it, and to remove this message after that.

Using NGINX as a proxy for Home Assistant allows you to serve Home Assistant securely over standard ports. This configuration file and instructions will walk you through setting up Home Assistant over a secure connection.

1. Get a domain name forwarded to your IP

Chances are, you have a dynamic IP address (your ISP changes your address periodically). If this is true, you can use a Dynamic DNS service (like duckdns) to obtain a domain and set it up to update with you IP.
If you later purchase your own domain name, you will be able to easily get a trusted SSL certificate later.

2 Install NGINX on your server

This will vary depending on your OS. Check out Google for this.
On a Raspberry Pi, this would be:

sudo apt-get install nginx

After installing, ensure that NGINX is not running.

:information_source: You will at least need NGINX >= 1.3.13, as WebSocket support is required for the reverse proxy.

3. Obtain an SSL certificate

There are two ways of obtaining an SSL certificate.

Using Let’s Encrypt

If you purchased your own domain, you can use https://letsencrypt.org to obtain a free, publicly trusted SSL certificate. This will allow you to work with services like IFTTT. Download and install per the instructions online and get a certificate using the following command.

sudo ./letsencrypt-auto certonly --standalone -d example.com -d www.example.com

Instead of example.com, use your domain. You will need to renew this certificate every 90 days.

Or - Using openssl

If you do not own your own domain, you may generate a self-signed certificate. This will not work with IFTTT, but it will encrypt all of your Home Assistant traffic.

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 9999
openssl rsa -in key.pem -out key.pem
sudo cp key.pem cert.pem /etc/nginx/ssl
sudo chmod 600 /etc/nginx/ssl/key.pem /etc/nginx/ssl/cert.pem
sudo chown root:root /etc/nginx/ssl/key.pem /etc/nginx/ssl/cert.pem

4. Create dhparams file

As a fair warning, this file will take a while to generate.
If you don’t have the ssl subdirectory, you can either create it, or update the config below to use a different folder.

cd /etc/nginx/ssl
sudo openssl dhparam -out dhparams.pem 2048

5. Install configuration file in NGINX

Create a new file /etc/nginx/sites-available/hass and copy the configuration file (which you will need to edit) at the bottom of the page into it.

:information_source: Some Linux distributions (including CentOS and Fedora) will not have the /etc/nginx/sites-available/ directory. In this case, remove the default server {} block from the /etc/nginx/nginx.conf file and paste the contents from the bottom of the page in its place. If doing this, proceed to step 7.

6. Enable the Home Assistant NGINX configuration

cd /etc/nginx/sites-enabled
sudo unlink default
sudo ln ../sites-available/hass default

7. Start NGINX

Double-check your new configuration to ensure all settings are correct and start NGINX.
On a Raspberry Pi, this would be done with:

sudo systemctl start nginx

When it’s working you can enable it to autoload with:

sudo systemctl enable nginx

8. Port forwarding

On your router, setup port forwarding (look up the documentation for your router if you haven’t done this before).
Forward port 443 (external) to your Home Assistant local IP port 443 in order to access via https.
Also forward port 80 to your local IP port 80 if you want to access via http.
Do enable LAN Local Loopback (or similar) if you have it.
Do not forward port 8123.

9. Configure Home Assistant

Home Assistant is still available without using the NGINX proxy. Restricting it to only listen to 127.0.0.1 will forbid direct accesses.
Also, Home Assistant should be told to only trust headers coming from the NGINX proxy. Otherwise, incoming requests will always come from 127.0.0.1 and not the real IP address.

In your configuration.yaml file, edit the http setting.

http:
  # For extra security set this to only accept connections on localhost if NGINX is on the same machine
  # Uncommenting this will mean that you can only reach Home Assistant using the proxy, not directly via IP from other clients.
  # server_host: 127.0.0.1
  use_x_forwarded_for: true
  # You must set the trusted proxy IP address so that Home Assistant will properly accept connections
  # Set this to your NGINX machine IP, or localhost if hosted on the same machine.
  trusted_proxies: <NGINX IP address here, or 127.0.0.1 if hosted on the same machine>

NGINX configuration (referred to earlier)

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    # Update this line to be your domain
    server_name example.com;

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

server {
    # Update this line to be your domain
    server_name example.com;

    # Ensure these lines point to your SSL certificate and key
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # Use these lines instead if you created a self-signed certificate
    # ssl_certificate /etc/nginx/ssl/cert.pem;
    # ssl_certificate_key /etc/nginx/ssl/key.pem;

    # Ensure this line points to your dhparams file
    ssl_dhparam /etc/nginx/ssl/dhparams.pem;


    # These shouldn't need to be changed
    listen [::]:443 ssl 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; # Uncomment if you are using nginx < 1.15.0
    ssl_protocols 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;

    location / {
        proxy_pass http://127.0.0.1:8123;
        proxy_set_header Host $host;
        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 $connection_upgrade;
    }
}
15 Likes

Hello. Can any body tell me how can I use Asterisk/FreePBX and HA at the same time with NGINX. I have Ubuntu 20.04

hi, appreciate your work.

Can I somehow use the nginx add on to also listen to another port and forward it to another APP / IP than home assistant

I use different subdomains with nginx config. It’s pretty straight-forward:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    # Update this line to be your domain
    server_name myURL.com;

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

server {
    # Update this line to be your Home Assistant domain
    server_name ha.myURL.com;

    location / {
        proxy_pass http://internal_IP:8123;
        proxy_set_header Host $host;
        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 $connection_upgrade;
    }
}


server {
    #my Personal webpage without a subdomain. Repeat this block with each subdomain as needed for your apps/servers
    server_name    myURL.com www.myURL.com;
    root           /var/www/myURL.com;
    index          index.html;
    try_files $uri /index.html;

    location ~ \.wav$ {
        add_header Content-Disposition "inline";
      }
}

Note, you’ll need to make sure your DNS directs appropriately. I personally use cloudflare and need to direct each subdomain back toward the root url. NGINX makes sure the subdomain goes to the right place. That DNS config looks like this:

Type | Name
AAAA | myURL.com
CNAME | ha
CNAME | www
…etc.

3 Likes

I’m having an issue with this config where all that loads is the blue header bar and nothing else.

In Chrome Dev Tools I can see 3 errors of “Failed to load module script: The server responded with a non-JavaScript MIME type of “text/html”. Strict MIME type checking is enforced for module scripts per HTML spec.”

Looks like the proxy is not passing the content type headers correctly. Is there something I need to set in the config to get them passing correctly?

I wanted to drop a bit of information that took me all day to figure out yesterday so hopefully I save someone some time in the future.

My subdomain (for example, homeassistant.mydomain.com) would never load from an external IP after hours of trying everything. Page could not load. After scouring the net, I found some information about adding proxy_hide_header Upgrade; in the nginx config which still didn’t work. Redid the whole OS multiple times, tried different nginx proxy managers (add on through HassOS as well as a docker in Unraid). I thought it had something to do with HassOS having upstream https:// and that I was setting up the reverse proxy wrong (Adding Websocket support didn’t work).

Turns out, for a reason far beyond my ability to troubleshoot, I cannot access any of my reverse proxy domain names from devices running iOS 14 on an external IP. If I do it from my wifi on my iPhone, no problem. The second I disconnect my WiFi, to see if my reverse proxy is working externally, the pages stop working. Naturally I thought it was just a mistake on my end but I finally read something about iOS causing issues way back in 16 and instead used my hotspot to try from my mac and voila, everything worked fine.

I tried externally from an iOS 13 device and no issues.

Hopefully this saves some dumb schmuck like me from spending hours on a problem that isn’t in your own making.

1 Like

Unable to access Home Assistant behind nginx reverse proxy. Home Assistant is running on docker with host network mode.

I’m getting 404 on static files.

EDIT: Solved

If you’re using NGINX on OpenWRT, make sure you move the “root /www” within the router’s server directive.

1 Like

Hey GaNi,

Any chance you can share your complete nginx config (redacted).
I am having similar issue although, even the fonts are 404’d.

  • HA on Synology docker.
  • nginx on Asus router

Cheers

Here you go! (I use ACME Certs + DDNS Cloudflare openWrt packages)

PS: For cloudflare visitor-ip restoration (real_ip_header CF-Connecting-IP) uninstall the default nginx package and install the all-module package for your router-architecture

Example: WRT3200ACM (Cortex A9 VFPV3)
nginx-all-module_1.17.7-2_arm_cortex-a9_vfpv3.ipk

Find yours here:
https://downloads.openwrt.org/releases/19.07.3/packages/

nginx.conf

user  root;
worker_processes  2;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile on;
    keepalive_timeout 0;

    client_body_buffer_size 10K;
    client_header_buffer_size 1k;
    client_max_body_size 1G;
    large_client_header_buffers 2 1k;

    gzip on;
    gzip_http_version 1.1;
    gzip_vary on;
    gzip_comp_level 1;
    gzip_proxied any;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;

    upstream homeassistant {
        server 192.168.1.111:8123;
        keepalive 64;
    }

    server {
        listen 80 default_server;
        listen [::]:80 default_server;
        server_name _;
        return 301 https://$host$request_uri;
    }

    server {
        listen 443 ssl default_server;
        listen [::]:443 ssl default_server;
        server_name  localhost;
    	
        root /www;
        
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:DHE+AESGCM:DHE:!RSA!aNULL:!eNULL:!LOW:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!CAMELLIA:!SEED";
        ssl_session_tickets off;

        ssl_certificate /etc/nginx/nginx.cer;
        ssl_certificate_key /etc/nginx/nginx.key;

        location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
            expires 365d;
        }

        include luci_uwsgi.conf;

    }

    server {
        # delete this block if you don't access your router over www
        listen 443 ssl;
        listen [::]:443 ssl;
        server_name sub.domain.com;
		
        root /www;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:DHE+AESGCM:DHE:!RSA!aNULL:!eNULL:!LOW:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!CAMELLIA:!SEED";
        ssl_session_tickets off;

        ssl_certificate /etc/acme/sub.domain.com/fullchain.cer;
        ssl_certificate_key /etc/acme/sub.domain.com/sub.domain.com.key;

        location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
            expires 365d;
        }
        
        include luci_uwsgi.conf;

    }

    server {
        listen 443 ssl;
        listen [::]:443 ssl;
        server_name sub.ha-domain.com;

        add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
        # add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:DHE+AESGCM:DHE:!RSA!aNULL:!eNULL:!LOW:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!CAMELLIA:!SEED";
        ssl_session_tickets off;

        ssl_certificate /etc/acme/sub.ha-domain.com/fullchain.cer;
        ssl_certificate_key /etc/acme/sub.ha-domain.com/sub.ha-domain.com.key;
        
        proxy_buffering off;

        location / {
            proxy_pass http://homeassistant;
            proxy_set_header Host $host;
            proxy_http_version 1.1;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
        }

        location /api/websocket {
            proxy_pass http://homeassistant/api/websocket;
            proxy_set_header Host $host;
            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";

        }
    }

    # cloudflare ip restoration https://support.cloudflare.com/hc/en-us/articles/200170786-Restoring-original-visitor-IPs-Logging-visitor-IP-addresses-with-mod-cloudflare-
    include /etc/nginx/cloudflare.conf;

    # include /etc/nginx/conf.d/*.conf;
}

ha - configuration.yaml

http:
  server_port: 8123
  use_x_forwarded_for: true
  trusted_proxies:
    - 192.168.1.1 # openwrt's nginx server
  ip_ban_enabled: true
  login_attempts_threshold: 5

mobile_app: # ha's ios/android app
2 Likes

Thanks, I will have a dabble over the next week.

You run home assistant and NGINX on docker?

I run…

Asus Router:

  • Merlin
  • dnsmasq
  • Entware
  • ngnix

Synology NAS:

  • Docker - HomeAssistant
  • Docker - NodeRed
  • Docker - Wekan
  • Docker - Portainer
  • and many others

Still working to try and get nginx working properly for local lan.
i.e.

  • ha.home
  • nodered.home
  • wekan.home
  • etc

Running Home Assistant on Docker (Different computer) and NGINX on my WRT3200ACM router (OpenWRT).

what’s the issue you’re facing?

Can I run this in CRON task, say, once a month, so that it auto renews? By the way, the instructions worked great for me!
Thanks

Thanks for publishing this! Managed to get it to work after adding the additional http settings and additional Nginx proxy headers in step 9 on the original post.
Was driving me CRAZY!

Now working lovely in the following setup:

  • HTTPS://domain:XXXXX (custom port)
  • Router Port Forwarding XXXXX (custom port) to server running Nginx
  • Nginx collects custom port and redirects to HTTP 8123 on HASS running in Docker

Certs provided by LetsEncrypt.
Lovely!

1 Like

Howdy all, could use some help, as I’ve been banging my head against the wall trying to get this to work…

My domain is pointed to my local ISP address via CloudFlare (CloudFlare integration is setup to automatically update the records).

I’m forwarding port 80,443 on my router to my Raspberry Pi running an NGINX reverse proxy (10.0.1.111). Used Certbot to install a Let’s Encrypt cert and the proxy is running the following configuration:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
 }

server {
    # Update this line to be your domain
    server_name home.zeefarmer.com;

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

server {
    # Update this line to be your domain
    server_name home.zeefarmer.com;

    # Ensure these lines point to your SSL certificate and key
    ssl_certificate /etc/letsencrypt/live/zeefarmer.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/zeefarmer.com/privkey.pem;

    # Ensure this line points to your dhparams file
    ssl_dhparam /etc/nginx/ssl/dhparams.pem;


    # These shouldn't need to be changed
    listen [::]:443 ssl 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; # Uncomment if you are using nginx < 1.15.0
    ssl_protocols 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;

    location / {
        proxy_pass http://10.0.1.114:8123;
        proxy_set_header Host $host;
        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 $connection_upgrade;
    }
}

I have Home Assistant running on another Raspberry Pi (10.0.1.114) with the following configuration.yaml addition:

http:
  server_port: 8123
  # For extra security set this to only accept connections on localhost if NGINX is on the same machine
  # Uncommenting this will mean that you can only reach Home Assistant using the proxy, not directly via IP from other clients.
  # server_host: 127.0.0.1
  use_x_forwarded_for: true
  # You must set the trusted proxy IP address so that Home Assistant will properly accept connections
  # Set this to your NGINX machine IP, or localhost if hosted on the same machine.
  trusted_proxies: 
    - 10.0.1.111 #NGINX reverse proxy server

The SSL connection seems to work fine, but for whatever reason, it’s not proxying over to the Home Assistant server and instead points to the NGINX server:

This was all working fine prior to attempting to add SSL to the mix.

Did you add this config to your sites-enabled?

You’ll see this with the default one that comes installed.

The config you showed is probably the /ect/nginx/sites-available/XXX file.

Go to /etc/nginx/sites-enabled and look in there. This same config needs to be in this directory to be enabled. The easiest way to do it is just create a symlink so you don’t have to have duplicate files.

kingoftown@kingoftown:/etc/nginx/sites-enabled$ ls -la 
drwxr-xr-x 2 root root 4096 Nov  2 17:04 . 
drwxr-xr-x 2 root root 4096 Nov  2 17:04 ..
lrwxrwxrwx 1 root root   34 Nov  2 16:46 default -> /etc/nginx/sites-available/default

In my example, I have the file /etc/nginx/sites-available/default, then symlinked that to /etc/nginx/sites-enabled/default

Restart nginx afterwards and try again

Are there any pros to using this over just Home Assistant exposed with the DuckDNS/Let’s Encrypt Add-On?

It depends on what you want to do, but generally, yes.

  1. My ssl certs are only handled for external connections. This means my local home assistant doesn’t need to worry about certs. This is important for local devices that don’t support SSL for whatever reason. This probably doesn’t matter much for many people, but it’s a small thing. Lower overhead needed for LAN nodes.

  2. I can run multiple different servers with the single NGINX endpoint and only have to port forward 1 port for everything. If I wanted, I could do a minecraft server too and if you wanted to connect, you would just do myaddress.duckdns.org/minecraft, or however I configure it. Again, this only matters if you want to run multiple endpoints on your network.

  3. Anonymous backend services. Again, mostly related to point #2, but even if you only ran Home Assistant as the only web service, the only thing someone can find out about my exposed port is that I’m running NGINX. Without it, they can see “oh, this is a home assistant…I can try this exploit to get around the SSL”. I mean sure, they can technically do the same thing against NGINX, but the entire point of NGINX is security, so any vulnerabilities like this would hopefully be found sooner and patched sooner.

More on point 3, If I was running a minecraft server, home assistant server, octoprint server…each one of those could have different vectors of attack. It becomes exponentially harder to manage all security vulnerabilities that might arise from old versions, etc. So instead, the single NGINX endpoint is all I really have to worry about for security attacks from the outside.

There is also load balancing built in…but that would only matter if you have hundreds of people logged into your home assistant server at once lol. I wouldn’t consider it a pro for this application.

tl;dr: If the only external service you run to your house is home assistant, point #1 would probably be the only benefit. Otherwise, nah…lets encrypt addon is sufficient.

1 Like