Reverse proxy using NGINX

Hi all,
I have been trying for several days to do a reverse proxy with Nginx for homeassistant, but unfortunately the page stops at the hass logo.

I would like to pass a location (folder) for example:
https://xxxxxxxxxx.ddns.net/hass/
Pero it stops at the logo.
My Nginx configuration:

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

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

#################
server {
    listen 0.0.0.0:80;
    root /var/www/html;
    charset utf-8; 
    #sendfile off;  
    client_max_body_size 100m; 
    # Required for LE certificate enrollment using certbot
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }  
    error_page  404              /404.html;
    error_page  403              /403.html;
    location = /favicon.ico { access_log off; log_not_found off; }  
    location = /robots.txt { access_log off; log_not_found off; }
    # Deny hidden files (.htaccess, .htpasswd, .DS_Store).
    location ~ /\.ht { deny all; }  
    location ~ /\.DS { deny all; } 
    location / {
        index index.php;
    }
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
        allow 192.168.0.0/16;
        allow 172.16.0.0/12;
        allow 127.0.0.1;
        allow 10.0.0.0/8;
        deny all;
    } 
 location /api/websocket {
    proxy_pass http://homeassistant/api/websocket;
    }
 location /api/webhook/blabla {
    proxy_pass http://homeassistant/api/webhook/blabla;
    }
 location /api/alexa/smart_home {
    proxy_pass http://homeassistant/api/alexa/smart_home;
    }
################ 
    ########################
    # mappings             #
    ########################
    location ~ \.(js|css|jpg|sh|exe|asp|net|env) {
        expires 5d;
        try_files $uri $uri/ =403;
        return 403;
        access_log      off;
        log_not_found   off;
    }

}
#############################
server {
    listen 0.0.0.0:443 default_server ssl http2;
    root /var/www/html;
    server_name xxxxxxxxxxx.ddns.net;
    ssl_certificate /etc/nginx/ssl/live/xxxxxxxx.ddns.net/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/live/xxxxxxxx.ddns.net/privkey.pem;
  	# Improve HTTPS performance with session resumption
  	ssl_session_cache shared:SSL:10m;
  	ssl_session_timeout 10m;
	# Enable server-side protection against BEAST attacks
  	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-AES256-SHA384:ECDHE-RSA-AES256-SHA384";
	# Aditional Security Headers
	# HSTS
	add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";

	add_header X-Frame-Options DENY always;

	add_header X-Content-Type-Options nosniff always;

	add_header X-Xss-Protection "1; mode=block" always;

  	ssl_stapling on;
  	ssl_stapling_verify on;
  	ssl_trusted_certificate /etc/nginx/ssl/live/xxxxxxxxx.ddns.net/fullchain.pem;
  	resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001] valid=300s; # Cloudflare
  	resolver_timeout 5s;    
    charset utf-8;        
    error_page  404              /404.html;
    error_page  403              /403.html;
    proxy_buffering off;
    location = /favicon.ico { access_log off; log_not_found off; }  
    location = /robots.txt { access_log off; log_not_found off; }  
    location / {
        index index.php;
    }
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass php:9000;
        #fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    }
    # Deny hidden files (.htaccess, .htpasswd, .DS_Store).
    location ~ /\.ht { deny all; }  
    location ~ /\.DS { deny all; } 
    location ~ \.php$ {
        deny all;
        access_log      off;
        log_not_found   off;
    }
########################
# mappings             #
########################
 #location ~ \.(js|css|png|jpg|sh|exe|asp|net|env) {
 #  expires 5d;
 #   try_files $uri $uri/ =403;
 #   return 404;
 #   access_log      off;
 #   log_not_found   off;
 #   }
########################
#certbot conf
 location /.well-known/acme-challenge/ {
    root /var/www/certbot;
    }
#######

#Home Assistant
    location /ha/ {
        proxy_pass http://homeassistant/;
        proxy_redirect / /ha/;
        proxy_cookie_path / /ha/;
        proxy_set_header Host $host;
        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_set_header X-Forwarded-Protocol $scheme;
        proxy_set_header X-Url-Scheme $scheme;
    }

  location /has/ {    
    ### force timeouts if one of backend is died ##
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
    proxy_set_header        Accept-Encoding   "";
    proxy_set_header        Host            $host;
    proxy_set_header        X-Real-IP       $remote_addr;
    proxy_set_header        X-Forwarded-Proto $scheme;
    add_header                  Front-End-Https   on;
    add_header       Strict-Transport-Security "max-age=15552000";
    proxy_http_version 1.1;
    proxy_set_header        Upgrade $http_upgrade;
    proxy_set_header        Connection "upgrade";
    proxy_pass  http://homeassistant/;
    proxy_redirect     off;
  }
  location /api/websocket {
    proxy_pass http://homeassistant/api/websocket;
    proxy_set_header        Accept-Encoding   "";
    proxy_set_header        Host            $host;
    proxy_set_header        X-Real-IP       $remote_addr;
    proxy_set_header        Host $host;
    proxy_set_header        X-Forwarded-Proto $scheme;
    add_header                 Front-End-Https   on;
    add_header       Strict-Transport-Security "max-age=15552000";
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_redirect     off;   
  }
# location /api/websocket {
#    proxy_pass http://homeassistant/api/websocket;
#    }
 location /api/webhook/blabla  {
    proxy_pass http://homeassistant/api/webhook/blabla;
    }
 location /api/alexa/smart_home {
    proxy_pass http://homeassistant/api/alexa/smart_home;
    }
################    
}
########################

proxy.conf:

# Timeout if the real server is dead
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;

# Proxy Connection Settings
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size    256k;
proxy_connect_timeout 240;
proxy_headers_hash_bucket_size 128;
proxy_headers_hash_max_size 1024;
proxy_http_version 1.1;
proxy_read_timeout 240;
proxy_redirect  http://  $scheme://;
proxy_send_timeout 240;

# Proxy Cache and Cookie Settings
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;

# Proxy Header Settings
proxy_set_header Connection $connection_upgrade;
proxy_set_header Early-Data $ssl_early_data;
proxy_set_header Host $host;
proxy_set_header Proxy "";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Real-IP $remote_addr;

Log Nginx:

172.19.0.1 - - [07/Nov/2023:17:16:00 +0100] "GET /ha/ HTTP/2.0" 200 4876 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" "-"
172.19.0.1 - - [07/Nov/2023:17:16:01 +0100] "GET /fontawesome/main.js HTTP/2.0" 404 589 "https://xxxxxxxxxx.ddns.net/ha/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" "-"
172.19.0.1 - - [07/Nov/2023:17:16:01 +0100] "GET /webrtc/webrtc-camera.js?v=v3.5.0 HTTP/2.0" 404 589 "https://xxxxxxxxxx.ddns.net/ha/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" "-"
172.19.0.1 - - [07/Nov/2023:17:16:01 +0100] "GET /hacsfiles/iconset.js HTTP/2.0" 404 589 "https://xxxxxxxxxx.ddns.net/ha/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" "-"
172.19.0.1 - - [07/Nov/2023:17:16:01 +0100] "GET /manifest.json HTTP/2.0" 404 589 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" "-"
172.19.0.1 - - [07/Nov/2023:17:16:01 +0100] "GET /hacsfiles/lovelace-card-mod/card-mod.js HTTP/2.0" 404 589 "https://xxxxxxxxxx.ddns.net/ha/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" "-"
172.19.0.1 - - [07/Nov/2023:17:16:01 +0100] "GET /browser_mod.js HTTP/2.0" 404 589 "https://xxxxxxxxx.ddns.net/ha/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" "-"

Could somebody help me?

does it work when you are not on home network?
some home routers/CPE don’t support “hairpinning” and wont let you use the external DNS name

does http://192.168.1.2:8123 work?

Hello,

it doesn’t work either inside the home network or outside.

Thank you very much.

how do I find the trusted proxies?

guys with the latest update with the SWAG container it has been so rubbish I have lost all connectivity to my HA outside my LAN. I have followed every single step in reconfiguring according to SWAG instructions but it wouldnt work.

I just struggled with my reverse proxy setup and homeassistant. Then I enabled websockets support on my proxy. Voila! It Works!

Hello guys, I am getting 502 Bad Gateway. My nginx conf:
Can you help me please?

server {
listen 80 default_server;
server_name domain.xyz;

return 301 https://$host$request_uri;

}

server {
listen 443 ssl;
server_name domain.xyz;

ssl on;
ssl_certificate /etc/letsencrypt/live/domain.xyz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain.xyz/privkey.pem;
ssl_prefer_server_ciphers on;

location / {
    proxy_pass http://192.168.0.73:8123;
    proxy_set_header Host $host;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

location /api/websocket {
    proxy_pass http://192.168.0.73:8123/api/websocket;
    proxy_set_header Host $host;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

}

}

My host machine is 192.168.0.110
And HA is running (the HAOS) on that machine via official KVM image

I run HAOS on a VM with a NixOS host. I wanted to share my NixOS reverse proxy config in case that’s useful for anyone.

Disclaimer: This is lightly modified from my config and thus is technically untested.

This assumes the use of Let’s Encrypt and replaces steps 2 - 7 and the nginx configuration part of step 9 (as of the time I’m writing this). Be sure to change the VirtualHost to your dns entry and proxyPass to your HAOS IP and defaults.email to whatever email address you want to give to Let’s Encrypt.

  services.nginx = {
    enable = true;
    recommendedProxySettings = true;
    recommendedTlsSettings = true;    
    virtualHosts."your.example.com" = {
      forceSSL = true;
      enableACME = true;
      locations."/" = {
        proxyPass = "http://192.168.1.9:8123"; 
        proxyWebsockets = true;
      };
    };

  security.acme = {
    acceptTerms = true;
    defaults.email = "[email protected]";
  };

Make sure to poke the appropriate holes (80, 443) in networking.firewall.allowedTCPPorts as well if needed.

I’d like to also suggest adding another empty VirtualHost with a different but valid dns entry and setting it as default so if someone or some script stumbles across your IP, it obfuscates your HAOS instance.

That’d probably look like this, also untested but I do something similar.

    virtualHosts."obfuscation.your.example" = {
      default = true;
      forceSSL = true;
      enableACME = true;
    };

Thanks a lot for this guide.
I have spent a day on integrating alexa… It was working with my previous smarthome solution… But I could not get it to work with HA.
Using your nginx config I made it. Thanks!

I’d be curious to know the reason for my failure before though…

server {
    listen [::]:443 ssl;
    server_name homeassistant.x.myonlineportal.net;
    ssl_certificate /etc/letsencrypt/live/x.myonlineportal.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/x.myonlineportal.net/privkey.pem;
    access_log /var/log/nginx/homeassistant.log combined;
    #ssl_protocols TLSv1 TSLv1.3
    #ssl_protocols TLSv1.2 TLSv1.3;
    error_log /var/log/nginx/homeassistant-error.log debug;
    include /etc/nginx/include.d/common;
    client_max_body_size 512M;
    client_body_timeout 300s;
    location / {
        proxy_pass http://192.168.177.185:8123/;
        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 $connection_upgrade;
        proxy_set_header Upgrade       $http_upgrade;
        proxy_set_header Connection    $connection_upgrade;
        #proxy_set_header Host $host;
        #proxy_set_header X-Real-IP $remote_addr;
        #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }


}

If you see an obvious reason, I’d be happy to learn. Otherwise: never mind :slight_smile: I just came here to thank you!

Greetings,
Hendrik

Can recommend everyone that’s using nginx to add this to there serverblock

    location ~ /\.ht {
       access_log off;
       log_not_found off;
       deny all;
    }
    location = /robots.txt {
       allow all;
       log_not_found off;
       access_log off;
       try_files $uri /index.php?$args;
    }

Do you have this above your server block?

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

Note, if you are using docker-compose with homeassistant, nginx, and certbot containers on the same device, you would have to create a docker network to allow communication between home assistant and nginx. Without a docker network, I had Bad 400 and 502 errors. Unfortunately, this solution would break network_mode: host on homeassistant. Therefore, I had to migrate from nginx and certbot to swag to allow me to maintain homeassistant network discovery.

For those running ESPHome and having problems talking to devices (i.e. logs / upgrades), if you’re exposing HA on a non-standard port (i.e. not 443), you need to slightly modify your nginx config as follows:

proxy_set_header Host $host;

becomes

proxy_set_header Host $host:8123;

(assuming you’re exposing HA on TCP:8123 rather than TCP:443

This allows web sockets to be correctly mapped through, and ESPHome returned to full health.

1 Like

How is your esphome device connnected to your HA then? Externally and via proxy?

Ah, no - you misunderstand me. I use nginx to access HA remotely and while everything else works fine, you can’t use the ESPHome UI to access the device (e.g. open the log window, recompile updates) as nginx incorrect rewrites the external port. All of the tutorials (including this one) seem to use this same nginx config, which works fine for everything, apart from esphome, so I thought I’d share this fix.

1 Like

You should just make the static ip of nginx-proxy-manager static by --ip 172.18.0.4
Use the ip that you see from “docker inspect network …”.
All related docker container should be in the same network.

And then you can set the config like this:

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: 172.18.0.4

Hi there,

I’ve same problem but never fund a solution.

Do you do it?

Hate to necropost, but I just got Nginx working as a reverse proxy for HA. I’m running Ubuntu, and I’m running HA in a container (not that it should make a big difference).

# /etc/nginx/sites-available/default
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        # put your desired domain(s) here.
        #
        # I have a bunch of stuff on this server, so I use *
        # If HA is the only thing on this machine, I could have
        # used ha.robin.lan instead.
        server_name *.robin.lan;

        return 302 https://$server_name$request_uri;
}
# /etc/nginx/sites-available/home-assistant
server {
        # SSL configuration
        listen 443 ssl;
        listen [::]:443 ssl;

        # all the SSL junk lives here.
        # I followed this guide to set it up: https://www.howtogeek.com/devops/how-to-create-and-use-self-signed-ssl-on-nginx/
        include snippets/self-signed.conf;

        # put your domain(s) here too
        server_name ha.robin.lan www.ha.robin.lan;

        location / {
                proxy_pass http://127.0.0.1:8123/;
                proxy_set_header Host $http_host;
                proxy_redirect off;
                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 X-Forwarded-Proto $scheme;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
        }
}
# Add this to HA's configuration.yaml:
http:
  use_x_forwarded_for: true
  trusted_proxies: 127.0.0.1

Don’t forget to enable the nginx sites:

ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
ln -s /etc/nginx/sites-available/home-assistant /etc/nginx/sites-enabled/home-assistant

Then restart the services:

systemctl restart home-assistant@user
systemctl restart nginx

Hi, the guide should be updated regarding the nginx configuration.

It is better to use the /conf.d/ directory instead of sites-available and sites-enabled.
As soon as a configuration file in /conf.d/ has the ending .conf, it is used by nginx.
If the .conf file-extension is removed, the configuration file is ignored by nginx.

This makes it easier to manage the various pages, you immediately have an overview of which pages exist and which of them are enabled, and you don’t have to fiddle around with symlinks, where a lot can go wrong.

Furthermore, step 6. Enable the Home Assistant NGINX configuration would also be omitted, as the activation is already done, adding the .config extension.

Step 5 would have to be updated to the following:

## 5. Install configuration file in NGINX

Create a new file `/etc/nginx/conf.d/hass.conf` and paste the content of the below shown configuration file (which you will need to edit) into the new file.

it’s definitely working here. I’ve just adapted the configuration for a proxy running on a distant server but on the same network.
Thank you for sharing.