Using Nginx Proxy Manager to Protect Itself

Using Nginx Proxy Manager to Protect Itself
Photo by Stephen Phillips - Hostreviews.co.uk / Unsplash

Docker is awesome,

and a ton of projects can be spun up in just a few minutes using Docker Compose. Many of these primarily use a Web UI, meaning the entire graphical interface exists within the browser. Unfortunately, most of the time these UI use plain HTTP, which is vulnerable to MITM attacks as it is completely unencrypted. Adding SSL certificates can be a pain, since each service/container will have a different method of installation and configuration. The solution to this, is to tunnel everything through a reverse proxy like nginx, but nginx can be a bit cumbersome to configure. Enter Nginx Proxy Manager, a dead simple container that allows you, with an excellent web GUI, to do just that.

The first thing you need to be aware of, is the difference between expose and ports in docker compose.

Both expose and ports are used, for separate reasons, in my NPM compose file.

The ports option essentially allows a container to communicate with the outside world. It will map a port from that specific docker container, to an external port on the machine it is running on. This means any other machine running on the network can also see whatever is on that port, by pointing at the machine's IP instead of the container.

The expose option essentially allows containers to communicate with eachother on a port, but not the outside world.

What we want to do, is have all our containers expose the ports to their web UI internally, but not externally. Next, we map the ports (using the ports option) for http(80, so we can redirect automatically) and https(443) to the nginx Proxy Manager container, so that outside machines can see it.

Now NPM is the only thing that can talk to those other containers, so we configure it to act as a proxy. Our browser talks to NPM on port 443, and NPM talks to the container on whatever port the container uses. Since we'll set NPM to use SSL, and all the non-ssl talk is only inside the docker network, we are secure.

OK, that was a lot of background to get to the one rub...

NPM itself has a web GUI running on port 81, and that web GUI is by default, plain HTTP. The key here, is first to map port 81 using the ports option, then log on to NPM and configure it to point back at itself, only encrypted. Then we hop back in to Docker Compose, and change port 81 to be exposed instead of mapped. Now when we access the web UI for NPM, we speak to it using HTTPS, and it talks to itself internally using plain HTTP. NPM has it's share of security concerns, and doing this takes care of the lion's share of them.

Here's what it all looks like:

First, we map port 81 so we can access NPM's web UI.

Now, we pop on to our browser, head to the machine's IP and port 81.
Now we log in (Default credentials are u: admin@example.com p: changeme
Click on Proxy Hosts
Click Add Proxy Host in the Top Right
Choose the DNS name you'll use to access NPM
Scheme will be http, Forward Hostname will be the NPM Container Name (See Below), and port is 81.
Container name can be specified like so in docker compose. If it isn't. it's usually the name of the service definition (line 3 in this photo)
Cache Assets can improve performance, Block Common Exploits helps with security, and Websockets support is necessary for a LOT of different services. I have never had any issues having all three checked.
Now click the SSL tab, and choose either the cert you have uploaded, or a new one from Let's Encrypt.
Each of these four options enhances seccurity, for one reason or another. I have never run into any issues having all four enabled.

Now, and this step is important, you need to pop in to your DNS server and add an entry pointing npm.yourdomain.edu to the correct IP.

In Windows DNS Server that looks like this. IP Address will be the IP of the actual machine docker is running on, not the internal IP of the container. Got it?
Head to the domain you specified in the browser, and make sure the UI Shows up.
Now, go back into our docker-compose file, and change port 81 from the ports section to the expose section. Apply and restart the container.
Head back to the domain you specified again in the browser, and make sure the UI still shows up.

And we are all good to go!
NPM's port 81 is closed off to the outside world, but we can still reach and configure NPM via the web GUI!