Zach's Mugspideyclick logo

GitHub

GitLab

Linkedin

Instagram

Youtube

SoundCloud

Email

Reverse Proxy Basics

A reverse proxy is the best way to access lots of containers running on one IP without having to assign tons of IP Addresses. When I create multiple subdomains pointing to the same IP, I am able to use the Reverse Proxy to decide what container traffic from a given subdomain should go to.

Reverse Proxy Container Setup

Prerequisites:

  1. You need to assign subdomains to point to the host the reverse proxy is running on.
  2. You'll need a few docker containers to point the traffic to.
  3. A wildcard cert for the domain will allow you to enable SSL pretty easily.
docker pull nginx
docker network create reverse-proxy
sudo mkdir /dockerdata/reverse-proxy/
sudo mkdir /dockerdata/reverse-proxy/config/
sudo mkdir /dockerdata/reverse-proxy/config/sites-enabled/
sudo mkdir /dockerdata/reverse-proxy/config/sites-available/
sudo mkdir /dockerdata/reverse-proxy/certs/
docker run -d \
    --name reverse-proxy \
    --restart unless-stopped \
    --network reverse-proxy \
    -p 80:80 \
    -p 443:443 \
    -v /dockerdata/reverse-proxy/config/:/etc/nginx/ \
    -v /dockerdata/reverse-proxy/certs/:/etc/ssl/private/ \
    nginx:latest \

At this point, I copied everything under /etc/nginx/ to /dockerdata/reverse-proxy/config. I also made a backup of the default config file to be safe.

Editing /dockerdata/reverse-proxy/config/nginx.conf, I added one server per each container:

http {
    ...
    server {
        server_name  <yourservername>;
        include /etc/nginx/default.d/*.conf;
        location / {
            proxy_pass http://<youraddress>:<yourport>/;
        }
    }
}

Remember to restart the container after making changes to NGINX config: docker restart reverse-proxy

After that, it's just a matter of connecting the containers to the reverse-proxy network:

docker network connect reverse-proxy <yourcontainername>

Remember to check firewall rules on the docker host to ensure port 80 and/or 443 is exposed!

You can test the routing on the host by running: curl --header "Host: <yourdomainname.com>" localhost

SSL Setup

Having created a wildcard certificate using the Certbot Container, I now have these files under /dockerdata/certs/:

cert1.pem  chain1.pem  fullchain1.pem  privkey1.pem

Not sure if you have to do this, but I renamed privkey1.pem to privkey1.key.

I copied those files to the folder mounted by the reverse-proxy docker container, then updated the reverse-proxy NGINX Config:

server {
  listen 443 ssl;
  ssl on;
  ssl_certificate </your/ssl/cert.pem>;
  ssl_certificate_key </your/ssl/cert.key>;
  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto https;
    proxy_redirect off;
    proxy_pass http://<docker-server-name>:<port>/;
    proxy_http_version 1.1;
  }
}

Optional Re-Encryption

I've rolled back the changes shown below but am including them in case we ever decide we want to go this route.

Before realizing that the traffic behind the reverse proxy is all contained within the virtual Docker network and likely won't really help our security that much, I had read the following article and had set up encryption to the containers themselves: https://reinout.vanrees.org/weblog/2017/05/02/https-behind-proxy.html

Apparently this is called re-encrypting.

The only change needed in the proxy's config is to change the proxy_pass line from http:// to https://, and update the port if needed as well.

Then in the internal server's NGINX config:

server {
    listen 443 ssl;
    server_name _;

    ssl on;
    ssl_certificate /app/certs/fullchain1.pem;
    ssl_certificate_key /app/certs/privkey1.key;

    client_max_body_size 0;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /var/www/;
    }

    location / {
        include         uwsgi_params;
        uwsgi_pass      unix:/app/wsgi.sock;
    }
}

However: I'm reading now that encrypting between the reverse proxy and the server behind isn't really needed unless that traffic is exposed somehow. With us having this traffic run between docker containers (inside of a virtual docker network even), I'm thinking we only need to encrypt the part of the journey that is exposed: From client to reverse proxy. See this question for more.

If I did want to do that, it might be better to use the Ubuntu Snakeoil cert rather than repeat the same wildcard cert.

Since we're not going to worry about that, though, then I don't have to worry about changing how things work when using the dev server. Our docker containers behind the reverse proxy don't have to think about or know about the SSL encryption at all!