Nginx Reverse Proxy Set Up Guide – Docker

Thanks for these great instructions, after trying multiple other things for days this is what actually worked to get the secure external https connection working for my homeasssistant. I have an odd setup of running HA on a macmini that dual boots into MACOS and Ubuntu, and HA runs in a docker container on Ubuntu, so typical instructions and documentation generally don’t work for me. But my mac mini, although old, is SO much faster then a PI. One thing I ran across was letsencrypt apparently was recently renamed to “swag” due to a trademark issue. I wasn’t able to get this working at first but that’s one of the things I updated and now it works. Also I couldn’t get things working on ports 180 and 1443, not sure what I was doing wrong or if it was in my router’s port forwarding settings, but since I don’t have anything used on ports 80 or 443 I just stuck with those and everything is now working with those changes. I used the config file from the top post exactly with the replacements for mydomain obtained from duckdns and the replacement of “hostip” with my computer’s actual local 192.etc IP address This was my docker-compose if anyone runs into similar problems, or wants to suggest anything else that should be updated with this “swag” name change. :

version: "2.1"
services:
  swag:
    image: linuxserver/swag
    container_name: swag
    restart: unless-stopped
    cap_add:
    - NET_ADMIN
    volumes:
    - /home/user/docker/swag/config:/config
    - /etc/localtime:/etc/localtime:ro
    environment:
    - PGID=1000 (replace with yours obtained from $ id username command)
    - PUID=1000 (replace with yours obtained from $ id username command)
    - [email protected]
    - URL=MYDOMAIN.duckdns.org
    - SUBDOMAINS=wildcard
    - VALIDATION=duckdns
    - TZ=America/New_York
    - DUCKDNSTOKEN=XXXXXXX (replace with your token from duckdns.org)
    ports:
    - "80:80"
    - "443:443"

Thank you for sharing with others. You are correct, the container was indeed renamed swag, but other than that I understand there’s no other significant change.
Happy to hear this guide still helps others.

1 Like

anyone any idea how can I resolve this issue

“Received X-Forwarded-For header from untrusted proxy 192.168.1.11, headers not processed”

@juan11perez, having been using this since pretty much the day you posted, I have a small suggestion for a change.

I would split out the config in the site-confs/default. Only change the line server_name _; to add whatever your url is instead of the underscore.

Then I’d (as I have done today) create more files and also put in the site-confs directory. I have created one for each service I want served on a sub-domain, but you could also add all in one new file (call it whatever you want, as long as it’s in that directory).

This makes updating MUCH easier.

I found I had a very old site-confs/default file (from 2018/08/15, now it’s from 2020/05/23) because as soon as you change anything in it, it will not update.

You do have to add your server_name, so the file won’t be updated regardless, but the point is that you’ll then just delete the site-confs/default whenever you update the container, and it will recreate the newest version. Then it’s only one line you need to change back again.

I use a script to update all containers in a docker-compose.yaml file, I’ve added a few lines to take care of the site-confs/default as well (haven’t tested with the additions yet, but pretty sure it should do the trick):

#!/bin/bash

cd /mnt/data/docker
docker kill $(docker ps -q)
rm /PATH/TO/site-confs/default
docker-compose build
docker-compose pull
docker-compose up -d swag
docker kill swag
sed -i 's/server_name _;/server_name YOUR_SERVER_NAME;/g' /PATH/TO/site-confs/default
docker-compose up -d

Assuming you’ve named the container “swag”. Replace “YOUR_SERVER_NAME” with your server (e.g. something.duckdns.org), and “/PATH/TO/site-confs/default” to whereever you’ve put that file. Not sure if you’d need to run as sudo, that depends on permissions you have.

Maybe it can be done nicer (it probably can), so someone feel free to post a better version. Just thought I’d add what I finally learned after a few years :slight_smile:

Hey guys,
What is the correct / best practice / least confusing way to get the SSL certs out of “letsencrypt/config/etc/letsencrypt/live/domain” in the SWAG docker container and into the Home assistant SSL folder so I can get the the Supervisor add-ins working? (without using the lets encrypt add-in)

I’ve read the “Using certs in other containers” part of the SWAG guide but I’m unclear on how to implement it. I’ve also read a few guides off these forums but don’t seem to relate exactly to my situation.

In short,

  • Running Lets Encrypt/Swag and HassIO docker containers
  • Getting SSL errors trying to connect to supervisor add-ins
  • Need to pass SSL cert from swag to hassio but not sure how

Example error:
( 20-11-16 00:23:40 ERROR (MainThread) [supervisor.api.ingress] Ingress error: Cannot connect to host 172.30.32.1:62507 ssl:default [Connect call failed (‘172.30.32.1’, 62507)]
)

Hey there, thanks for the guide!

How would you integrate SWAG in the supervised installation method? (Installing Home Assistant Supervised on a Raspberry Pi with Debian 10)

As my setup isn’t completely starting with docker-compose, how would i provide HA with the needed config? Is modifying “http:” in configuration.yaml enough?

Do you have any experience in running additional addons like mqtt? Is it also working?

It will work as per the example. In the process of setting up supervised, you’ll install Dockers and therefore you can run any Dockers.

You’d only need to add docker-compose for your raspberry pi.

A quick search will offer guides on that. Once you have docker-compose installed you can add any other docker,

An alternative is to install portainer via command line and then use the built in docker-compose. Once installed you go into stacks, add your docker-compose content and hit deploy. Keep in mind it currently uses docker-compose version 2, but that’s not a hindrance at all.

Finally I’d suggest you look at this https://nginxproxymanager.com/ instead of swag.
It has a graphical interface and it’s very easy to use. It provides generic nginx config, fetches the certificate etc. It’s just easy.

I’m having trouble starting Nginx Proxy Manager because hassio ist already running and restarting all the time. Do I need to make any changes to the ha and hassio containers? (I’m already using portainer and set up Nginx Proxy Server as a stack)

if hassio is restarting then you have another problem. what’s causing to restart?

First of all, I appreciate your support!

It seems that the supervisor installation starts a systemd service which causes hassio to restart, no matter how you set the restart policy in docker.

So in theory i could start Nginx Proxy Manager but now i get the following errors:

From db:
standard_init_linux.go:211: exec user process caused "exec format error"

And from app:
[4:17:17 PM] [Global ] › ✖ error getaddrinfo ENOTFOUND db

Any idea? Do I have to set up MySQL myself?

that’s correct. hassion uses systemd to start the containers.
I just saw that there’s a community addon for nproxy manager. That may be easier.
in any case this is my compose for nproxy

  nproxy:
    container_name: nproxy
    image: jlesage/nginx-proxy-manager
    restart: unless-stopped
    hostname: UNRAID  
    volumes:
    - /mnt/cache/appdata/nproxy:/config:rw
    environment: 
    - USER_ID=99
    - GROUP_ID=100
    - TZ=Asia/Dubai
    - DISABLE_IPV6=1
    - UMASK=000
    ports:
    - "7818:8181"
    - "180:8080"
    - "1443:4443"

Im using the default sqlite, no need to create another database

1 Like

Thanks for sharing, you saved me A LOT of time! Cheers mate

1 Like

This is the best explanation I found so far, look like it required

There appears to be a new “block” on reverse proxies that will require an extra config entry for this Nginx Reverse Proxy to work properly. According to the latest release notes - 2021.6: A little bit of everything - Home Assistant

HTTP (using reverse proxies)

Home Assistant will now warn when a misconfigured reverse proxy, or misconfigured Home Assistant instance when using a reverse proxy, has been detected.

These warnings will become an error in Home Assistant 2021.7.

If you are using a reverse proxy, and see these warnings, please make sure you have configured use_x_forwarded_for and trusted_proxies in your HTTP integration configuration.

For more information, see the HTTP integration documentation.

(@frenck - #51332)

I had to add the following to my config using this NGINX/Swag container to clear the log error:

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 172.21.0.2

Not exactly sure why I had to use 172.21.0.2, when most posting on this thread Reverse proxy error needed 172.21.0.1 . The 172.21.0.2 is listed in Portainer under “networks” and then when I click the “swag container” it is towards the bottom. Maybe someone who knows a little more about Docker and networks can chime in on why.

Posting this here as according to the log error, its a warning for now, but coming in the July release, it will block reverse proxy requests if you do not set the http options in the config.yaml file like mentioned here - HTTP - Home Assistant . I get this is an important security update, but it definitely adds another layer of complication to setting up a reverse proxy when running Home Assistant in a container and I imagine a lot of people will be caught off guard by it when it just stops working next month without the extra http config settings being added.

Update - I also wanted to chime in on the fastcgi settings. I saw that someone posted about it recently above, and in the beginning I always saw fastcgi errors in my nginx error logs, and also noticed a lot of issues with my Ring and Blink integrations which I believe use fastcgi to send videos to home assistant. I previously had changed this to the IP address of the host machine which was causing the issues. Aparently NGINX can’t handle fastcgi on its own, so needs a “helper” which in this case is PHP. The Swag container has PHP and can actually handle fastcgi requests within its own port 9000. So, in the default config, the fast CGI is setup this way:

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include /etc/nginx/fastcgi_params;
    }

You DO NOT want to change anything in this block. Do not change the IP address of 127.0.0.1 to your host machine. This IP address is the “loopback” or “home” address of the actual Swag container itself, and is necessary to reference PHP for fastcgi to process properly. If you change it to your hostip (192.168.0.whatever), fastcgi requests won’t work because you’re not maping port 9000 out of the Swag container (only port 443 which is Https and port 80 which is http are mapped out) and not getting it to the right place anymore. Since port 9000 is not mapped out of the Swag container it will only exist locally within that container and not outside it, and even if you run portainer mapped to port 9000 on the host machine, you still should not have a conflict with this setting.

The specifics under 172.16.0.0/12 network would be different from one setup to another - depends on your environment and how you do your dockers or install types.

So maybe consider this?

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 127.0.0.1       # in case the reverse proxy being on the same IPv4 as the HA
    - ::1             # in case the reverse proxy being on the same IPv6 as the HA
    - 172.16.0.0/12   # in case the reverse proxy being under the same docker environment as the HA, and in case the docker IP would change after reboot/restart
    - xxx.xxx.xxx.xxx # specifying the IP of the reverse proxy, if none of the above applies

… and maybe one would need multiple lines under trusted_proxies: to work, depends on the setup.

Thanks @k8gg . So I never really payed attention to docker network settings before, but with the new requirement to add the IP address of the trusted proxy to Home Assistant’s config now it matters. If that trusted IP is wrong the reverse proxy will fail on the next release.

So here are my Docker network settings for all my containers:

And specifics for the Swag container

The 172.21.0.2 is what I had to add to my home assistant config as a trusted proxy which was specified in the swag_default network. This cleared any log warnings about the proxy. Based on your post, I assume these IP’s are going to be different for everyone depending on the number of containers/installation order or otherwise somehow specifying network settings in docker. My questions would be:
1- Will these IP’s randomly change for the containers if not “statically” set somehow - ie upon docker restart, other containers restarting, or deleting/adding the containers like after an image is updated?
2- If they might change, how should I set them up so the IP addresses/network settings stay the same all the time? Anything that I can just configure right through portainer?

Gents, i dont believe the solution is to widen the trusted_proxies: list. Seems counter intuitieve.
What I did was give the proxy (here swag) a static address like so:

    networks:
      mynet:
        ipv4_address: 172.10.0.10    # set up static ip to prevent HA blocking

Add that snipet to corresponding docker-compose entry, associated to a docker network I created with the following:

networks:
  mynet:  # set up static ip to prevent HA blocking
    driver: bridge
    driver_opts:
      com.docker.network.enable_ipv6: "false"
    ipam:
      driver: default
      config:
      - subnet: 172.10.0.0/24 # 

then add only this line:

  trusted_proxies:
  - 172.10.0.10 #as set up in docker-compose for Nginx/Letsencrypt
  - ::1
1 Like

Thanks @juan11perez . I was having some issues with trying to change the IP address to something new, thinking it’s probably browser and app caching issues of the old settings, or it was conflicting with something else. I ended up just specifying the IP address of 172.21.0.2 in the compose for the default swag network (which is what it was randomly given already) so it remains static and won’t change on me upon restarts/re-installs based on your post. Hopefully I’m not missing something here, but my whole compose if it helps:

version: "2.1"
services:
  swag:
    image: linuxserver/swag
    container_name: swag
    restart: unless-stopped
    cap_add:
    - NET_ADMIN
    volumes:
    - /home/tim/docker/swag/config:/config
    - /etc/localtime:/etc/localtime:ro
    environment:
    - PGID=1000
    - PUID=1000
    - [email protected]
    - URL=yourdomain.duckdns.org
    - SUBDOMAINS=wildcard
    - VALIDATION=duckdns
    - TZ=yourtimezone
    - DUCKDNSTOKEN=yourtoken
    - MAXMINDDB_LICENSE_KEY=yourkey #this is optional for location based IP banning
    ports:
    - "80:80"
    - "443:443"
    networks:
      default:
        ipv4_address: 172.21.0.2

Then these lines in my home assistant config

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 172.21.0.2

I left out ::1 since I’m not using IPV6 at all for anything, but may need to add that later

1 Like

Hi all!
First of all thank @juan11perez for the great guide!
Unfortunately I’m facing some issues with setting this thing up.

I got the swag container running and I can acces the nginx main page from mydomain.duckdns.org.
But, when I try to access homeassistant by going to hass.mydomain.duckdns.org I’m getting a 504 Gateway Time-Out error…

This is my docker-compose setup -

version: '3'
services:
  homeassistant:
    container_name: hass
    image: homeassistant/home-assistant
    volumes:
      - ./hass-config:/config
      - /etc/localtime:/etc/localtime:ro
    restart: unless-stopped
    network_mode: host
    depends_on:
      - mariadb
      - mosquitto
  swag:
    image: linuxserver/swag
    container_name: swag
    restart: unless-stopped
    cap_add:
    - NET_ADMIN
    volumes:
    - /etc/localtime:/etc/localtime:ro
    - /home/pi/homeassistant/swag/config:/config
    environment:
    - PGID=${PGID}
    - PUID=${PUID}
    - [email protected]
    - URL=mydomain.duckdns.org
    - SUBDOMAINS=wildcard
    - VALIDATION=duckdns
    - TZ=Asia/XXXX
    - DUCKDNSTOKEN=XXXXX
    ports:
    - "80:80"
    - "443:443"
    networks:
      default:
        ipv4_address: 172.10.0.100

And this is my default nginx file -

## Version 2021/04/27 - Changelog: https://github.com/linuxserver/docker-swag/commits/master/root/defaults/default

error_page 502 /502.html;
server_names_hash_bucket_size  64;

# redirect all traffic to https
server {
    listen 80;
    server_name mydomain.duckdns.org;
    return 301 https://$host$request_uri;
}

# main server block
server {
    listen 443 ssl default_server;

    root /config/www;
    index index.html index.htm index.php;

    server_name mydomain.duckdns.org;

    # enable subfolder method reverse proxy confs
    include /config/nginx/proxy-confs/*.subfolder.conf;

    # all ssl related config moved to ssl.conf
    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    location / {
        try_files $uri $uri/ /index.html /index.php?$args =404;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
	    fastcgi_pass hostip:9000;
        #fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include /etc/nginx/fastcgi_params;
    }

}
server {
	listen 443 ssl;

	root /config/www;
	index index.html index.htm index.php;

	server_name hass.mydomain.duckdns.org;

	include /config/nginx/ssl.conf;

	client_max_body_size 0;

	location / {
#		auth_basic "Restricted";
#		auth_basic_user_file /config/nginx/.htpasswd;
		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";
		proxy_buffering               off;
		proxy_ssl_verify              off;
#		include /config/nginx/proxy.conf;
		proxy_pass http://hostip:8123;
	}
}



# enable subdomain method reverse proxy confs
include /config/nginx/proxy-confs/*.subdomain.conf;
# enable proxy cache for auth
proxy_cache_path cache/ keys_zone=auth_cache:10m;


I also added the following in my home assistant config -

http:
  ip_ban_enabled: true
  login_attempts_threshold: 3
  use_x_forwarded_for: true
  base_url: hass.mydomain.duckdns.org
  trusted_proxies:
    - 192.168.31.0/24  # Local Lan
    - 172.10.0.0/24  # Docker network

What am I doing wrong?

Thanks :slight_smile:

not sure this is your problem, but base_url does not go under http. You need to remove that.

you probably need to add the following:

  internal_url: http://192.168.1.xx:8123 # introduced with HA 110.0
  external_url: hass.mydomain.duckdns.org

it goes under homeassistant