Reverse proxy using NGINX

Unfortunately, I don’t think your initial observation is valid. Browsers don’t verify publicly issued certificates via the Internet (consider sites that aren’t allowed to connect to the Internet like secure government facilities). Certificates are verified through a “certificate chain” that ultimately ends at a root certificate store installed locally on the operating system. This is one reason that when operating systems go end of life (like Windows 7) things will start to break. The OS vender regularly updates root certificates through an out-of-band process like windows updates. Fortunately, root certificates have very long lifetimes, but they will eventually expire.

In NGINX did you install the server certificate as well as the intermediate and root?

1 Like

I think you face this behaviour because you cannot resolve any public dns name… if you would do split dns and add the public dns zone/name/record to your internal dns, resolving to the correct ip it should start working.

Yes- so you’re saying the redirect of my public URL should work with the internet down?

Here’s my nginx.conf:


#user  root wheel;
worker_processes  1;

error_log  /Users/user/.homeassistant/logs/nginx_error.log info;

pid        /Users/user/.homeassistant/logs/nginx_pid.log;

events {
    worker_connections  256;
}

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

    access_log  /Users/user/.homeassistant/logs/nginx_access.log;

    sendfile        on;

    keepalive_timeout  120s;

    gzip  on;

    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }
    
    server {
        server_name xxx.duckdns.org;
        listen 8080;
        listen [::]:8080;
        return 301 https://$host$request_uri;
    }
    
    server {
        server_name xxx.duckdns.org;
        listen 443 ssl default_server;
        listen [::]:443 ssl;
        
        ssl_certificate /etc/letsencrypt/live/xxx.duckdns.org/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/xxx.duckdns.org/privkey.pem;
        ssl_dhparam /usr/local/etc/ssl/certs/ssl_dhparams.pem;
        add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
        ssl_protocols TLSv1.2 TLSv1.3;
        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 on;
    
        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;
            proxy_read_timeout 300s;
            proxy_connect_timeout 75s;
            
        }
    }
}

No, I’m just saying that no Internet based certificate verification is necessary. Redirect to the public URL locally will be highly dependent on your local DNS solution and proxy configuration as mentioned in the previous reply. You can also test with a hosts file entry to short-circuit DNS from your test machine.

Ok thanks for the info

I think you meant “lookup” with internet down. I think that will not work. At least, if it was looked up before it remains in the clients cache (ttl). But the ip resolved to, your wan ip is down then…

1 Like

So the Letsencrypt documentation says that fullchain.pem contains both the server’s public cert and the chain.
Also:
“Under normal circumstances, certificates issued by Let’s Encrypt will come from “R3”, an RSA intermediate.”
This is an up-to-date system, so as you’ve explained it the root should reside in the OS’s certificate store.

I thought the redirect from AdGuard was sufficient for the split-brain part but I guess I’m missing this part:

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.

A link to where it has been migrated to would be helpfull

Hi there, dunno whether this place is still active, but I am still having this problem. I gave up the /homeassistant location-based hacks as they never worked for me, and I also don’t have a subdomain.
So, I only want then that on my root, i.e., mydomain.ddns.net/, the home assistant will come up and I can access it through Nginx from outside of home network.
I followed the descriptions here…actually, I tried all of them :smiley:
This is my current nginx config

set $upstream_proto http;
set $upstream_proto_ssl https;

set $ha_ip 127.0.0.1;
set $ha_port 8123;

   location / {
# used to play around with the /homeassistant trials
#	sub_filter 'src=/static/' 'src=/ha/static/';
#	sub_filter 'src=/hacs-files/' 'src=/ha/hacs-files/';
#	sub_filter_once off;
    proxy_set_header        Host $host;
    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        Upgrade $http_upgrade;
    proxy_set_header        X-Forwarded-Proto $scheme;
    proxy_pass              $upstream_proto://$ha_ip:$ha_port;
    proxy_redirect          $upstream_proto:// $upstream_proto_ssl://;
    access_log              on;
    access_log              /var/log/nginx/access_ha.log;
    error_log               on;
    error_log               /var/log/nginx/error_ha.log;
    }

    location /api/websocket {
        proxy_pass              $upstream_proto://$ha_ip:$ha_port/api/websocket;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        Host            $host;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-Proto $scheme;
        proxy_http_version      1.1;
#don't have the other port 80 based generic https redirection, so I have this redirect for other locations and applications too
        proxy_redirect          $upstream_proto:// $upstream_proto_ssl://;
      }

Everything almost works :smiley: I can access my homeassistant login screen from outside, but when I log in, it says it cannot connect.
The web developer firefox plugin shows this:

I just keep spinning my head around this…why is this so difficult with Nginx if no special thing is needed. No location, no path rewrites, no subdomain…just make the locally available HA exposed via NGINX.

Ohhh…and I have necessary edits in the configuration.yaml:

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 172.30.1.0/24     <--this is my docker network where all other containers are, including homeassistant docker
    - 127.0.0.1      <-- localhost where the homeassistant container's port 8123 is forwarded
    - 192.168.22.0/24  <-- my home network: the outside connection come from 192.168.22.1, i.e., from the router

Thanks, any comment is welcome :slight_smile:

update (maybe irrelevant):

If I wait for it to try to reconnect, then it does not find the auth token.

I tried setting that path as well into my nginx conf, but did not help at all. So, I removed it from the above configuration.

Here is my functioning nginx config based on swag from unraid.

homeasssistant.subdomain.conf

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name homeassistant.*;

    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    location / {
        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app 192.168.1.25;
        set $upstream_port 8123;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;

    }

    location ~ ^/(api|local|media)/ {
        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app 192.168.1.25;
        set $upstream_port 8123;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
    }
}

proxy.conf

## Version 2021/10/26 - Changelog: https://github.com/linuxserver/docker-swag/commits/master/root/defaults/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;

resolver.conf

resolver  127.0.0.11 valid=30s;

configuration.yaml

configuration.yaml
http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 172.16.0.0/12 <--- docker network
    - 192.168.1.3  <-- IP of my unraid server on which my swag/nginx are hosted (i run HOAS on a seperate device so my HA IP is not included in this list.

It still does not work. There were some issues with your config, so I appended some extra to that, but eventually, I still have the same error with unable to fetch

GET wss://mydomain.ddns.net/api/websocket

for all the paths, I amended your config to this by appending hacsfiles,auth,frontend_latest,static:

set $upstream_proto http;
set $upstream_proto_ssl https;
set $ha_ip 127.0.0.1;
set $ha_port 8123;
...
    location ~ ^/(api|local|media|hacsfiles|auth|frontend_latest|static)/ {
        include /etc/nginx/proxy.conf;
        include /etc/nginx/resolver.conf;
        proxy_pass $upstream_proto://$ha_ip:$ha_port;
    }

And I removed this line from your proxy.conf

#proxy_set_header Connection $connection_upgrade;

Because nginx was complaining about it:

nginx: [emerg] unknown "connection_upgrade" variable

btw. what is the resolver for? is that the local stub DNS resolver?

Okay, I made it working.
Googled more and found that for my error, that specific Connection upgrade part is really necessary for the nginx config.
But, as mentioned before, nginx was complaining about that.

The solution was to hardcode it without variables. So, replace

proxy_set_header Connection $connection_upgrade;

to this:

proxy_set_header Connection "upgrade";

And now it is working!
Thank you SgtBatten for your help.

Ahh yeah sorry that variable must be set elsewhere in my config.

Glad you worked it out.

I could also use some assistance with this.

Summary

I use a Raspberry Pi as a reverse proxy on my home network. I would like to be able to go to https://ha.MyURL1.com and access my Home Assistant dashboard. However, when I try, I get a Home Assistant page that reads “Unable to connect to Home Assistant,” and my NGINX error log shows several errors, mostly reading “connect() failed (111: Connection refused) while connecting to upstream”.

Details

On my network, I have one Raspberry Pi acting as a reverse proxy for three separate machines, including my Home Assistant (running on an ODROID-N2+). I’ve used the “Virtual Servers” setting of my router to route all requests on ports 443 and 80 to the Raspberry Pi.

Here is the /etc/nginx/sites-available/ha.MyURL1.com.conf file on the Raspberry Pi:

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

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

    # Ensure these lines point to your SSL certificate and key
    ssl_certificate <snip>/fullchain.pem;
    ssl_certificate_key <snip>/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/letsencrypt/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 https://<HA machine's 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;
    }
}

I have added this to the end of my configuration.yaml file for Home Assistant:

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: <Raspberry Pi proxy IP>

Steps to reproduce

  1. In a browser, go to http://ha.MyURL1.com, or https://ha.MyURL1.com.

Expected results

https://ha.MyURL1.com loads my Home Assistant dashboard.

Encountered results

The browser redirects to https://ha.MyURL1.com/lovelace and displays a Home Assistant error which reads “Unable to connect to Home Assistant.”

2022-10-23 - Unable to connect to Home Assistant

When the timeout concludes, I am sent to a Cloudflare error page reporting an error 502: Bad gateway.

In my NGINX error.log, I see several entries that correspond with the timing of the request:

[…] connect() failed (111: Connection refused) while connecting to upstream […]

Additionally, earlier in the log (but not corresponding to the latest request), I see this:

2022/10/23 11:53:24 [emerg] 492#492: bind() to [::]:443 failed (98: Address already in use)
2022/10/23 11:53:24 [emerg] 492#492: bind() to [::]:443 failed (98: Address already in use)
2022/10/23 11:53:24 [emerg] 492#492: bind() to [::]:443 failed (98: Address already in use)
2022/10/23 11:53:24 [emerg] 492#492: bind() to [::]:443 failed (98: Address already in use)
2022/10/23 11:53:24 [emerg] 492#492: bind() to [::]:443 failed (98: Address already in use)
2022/10/23 11:53:24 [emerg] 492#492: still could not bind()

Extra details

I can, of course, still access HA locally by going to http://homeassistant.local:8123.

Could any HA/NGINX wizards help me sort this out? Thank you. :slight_smile:

You can try adding dns rewrite in adguard so that your domain is pointing to your ha ip.

I found my way to a solution. I’ll spare you the full writeup, but here’s the meat of it:

Looking at that error, it appeared NGINX had a problem with how the configuration was trying to use port 443. (Or something; I don’t really understand what was going on, but I recognized 443.) When I looked at my conf file again, I saw this section:

    # 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;

I had other NGINX configuration files on the same device that had set SSL settings properly—I used Certbot to configure them, with no issue. I had also previously used certbot to generate certificates for this domain. So I commented out the block above (which I had copied from HA support resources) and instead re-added these lines:

server {
    server_name ha.MyURL1.com
    # [existing stuff]

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/<snip>/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/<snip>/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

server {
    if ($host = ha.MyURL1.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


        server_name ha.MyURL1.com;
    listen 80;
    return 404; # managed by Certbot
}

This, combined with changing proxy_pass to use http:// instead of https://, worked!

So, my solution was:

  1. Replacing the block pertaining to SSL with the stuff Certbot had automatically generated, and which I knew worked for other domains, and
  2. Changing the proxy_pass to use http:// instead of https://.

Hi everyone, I was facing to the same issue. I paste your my SIMPLE nginx configuration, working on the HA 2022.11.4

It use HTTPS between clients on a particular domain, then HTTP between nginx and HomeAssistant

server {
    listen       443 ssl;
    ssl_certificate /path_to_cert.pem;
    ssl_certificate_key /path_to_key.pem;

    server_name  home.domain.net;

    location / {
        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 "Upgrade"; #syntax change here

        proxy_pass http://<ip_ha>:8123;
    }

    #default conf for error pages, not important here
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}


If it could help someone
1 Like

Hello all,
if you are using caprover, I suggest using the following reverse proxy docker container which includes the necessary stuff for serving home assistant through the proxy
GitHub - blackwiz4rd/nginx-reverse-proxy: A very simple reverse proxy using nginx and Docker.

In caprover do the following:

  • In HTTP settings, enable: Force HTTPS by redirecting all HTTP traffic to HTTPS
  • In HTTP settings, enable: Websocket Support
  • Enable HTTPS
  • Set Environment Variables in App Configs: UPSTREAM_HTTP_ADDRESS = "http://hassio-ip:hassio-port"

Make sure to allow trusted proxy:

http:
  # when using proxy from caprover
  use_x_forwarded_for: true
  trusted_proxies:
    - 172.19.0.0/24
1 Like

The provided GitHub is 4 years old… Why should that be used instead of the official, standard jc21/nginx-proxy-manager or nginx that are regularly updated? From my understanding the SSL system has changed in the last year & there was a major change a couple years ago, both after that one was last updated.
I don’t use Caprover, not even sure what is is, but is there something that it needs that the offical images do not have?

2 Likes