Nginx Reverse Proxy

I’m using Nginx as a reverse proxy in my application. It is a tried and true piece of software that seems to always be on the cusp of being replaced, but never does. Before we get to the config of the Nginx reverse proxy, I want to touch a bit on a few other reverse proxy options that are hyped up in the Docker world and I will explain why we are sticking with a regular instance of Nginx.

Traefik

Traefik is an interesting up and coming reverse proxy with auto-ssl. However, after diving in a bit deeper, you will soon find out that 99% of the online guides are pretty terrible (everything was a simple Hello World app). I caught myself asking “Does anyone really use this for production or is it just all hype?” You also can’t redirect www to non-www (or vice-versa) without breaking ssl renewal (because Traefik’s redirect uses url rewrites). I’m not sure if this has been fixed or not, but wasn’t the last time I looked.

jwilder/nginx-proxy

Jwilder/nginx-proxy is basically the same thing we are doing, except it’s pretty bloated when you just want a simple, straight to the point, setup. I feel like people use this because they don’t actually understand how easy Nginx config is (and I’m about to show you). I also had a hard time trying to understand how I could make my own custom optimizations work with this package. Plus to use HTTPS, you have to use a separate add-on project. Less is more, IMO.

linuxserver.io/docker-letsencrypt

Linuxserver.io/docker-letsencrypt is similar to the jwilder/nginx-proxy above and it’s just more and more bloated config. I also had a hard time finding real world tutorials for this, so I gave up pretty quickly. LinuxServer.io has also rubbed some people the wrong way with some hidden scripts in their product.

So why Nginx?

Because it’s easy! And I like full control.

Let’s begin.

Nginx folder contents

Inside our nginx folder, we have a few files:

production folder with a dotnetcoredocker.conf file — this folder holds our website routing configuration files. I only have one conf file for this application. If you add another application to this server, you can use this same reverse proxy setup, just add another conf file.

with a — this folder holds our website routing configuration files. I only have one conf file for this application. If you add another application to this server, you can use this same reverse proxy setup, just add another conf file. docker-compose.production.yml — this is a second docker-compose file that will be used AFTER we get our initial certificates. A second (and separate) compose file allows us to run our nginx reverse proxy independent of our application. This is good for when you want to show a maintenance page when performing application updates or database migrations.

— this is a second docker-compose file that will be used AFTER we get our initial certificates. A second (and separate) compose file allows us to run our nginx reverse proxy independent of our application. This is good for when you want to show a maintenance page when performing application updates or database migrations. Dockerfile — builds our Nginx container

— builds our Nginx container nginx.conf — this is our Nginx base config. This does not contain proxy config, but it does have performance optimizations.

Let’s start with the nginx.conf file.

Nginx.conf file

I’m not going to explain most of this, but what you should know is that worker_processes auto allows the proxy to scale with the number of VPS CPU’s you have. Most of this config is taken from here.

Now let’s check out the Dockerfile.

Nginx reverse proxy Dockerfile

This Dockerfile allows us to use the custom nginx.conf file I showed you above. This is why I use a Dockerfile instead of just pulling the Nginx image from Docker Hub (in Docker-Compose).

dotnetcoredocker.conf

This is the nginx config file that contains our reverse proxy routes. To explain, I’m going to break this up into sections.

I would advise you to open up this file from the github repository in another tab and look at it while I run through it.

Upstream servers

Nginx Upstream servers

This is primarily for load-balancing (I’m not setting that up in this demo), but what you should recognize is that upstream maps to what will soon-to-be our Docker-Compose services’ names and exposed ports. As you might have guessed, I will have a nuxt service on port 3000 and an api service on port 80.

Port 80 Redirect Config

Port 80 redirects

What you see here is our Port 80 Redirects. Whenever someone tries to connect to our app or api on non-secure http, they will be 301 redirected to https. The following is for certbot renewal:

location /.well-known/acme-challenge/ { root /var/www/certbot; }

Port 443 Server Config

I’m only going to show the https://dotnetcoredocker.com route, but you can check out the github repository to view the https://api route. I also have a port 443 www to non-www redirect in there too.

443 ssl server

What’s important here is to see the ssl_certificate locations, which will be explained in a separate article I will be providing when we get to the HTTPS section. I also want you to see the proxy_pass. It has a value of http://nuxt. What this does, is internally route the request to our nuxt upstream server from earlier. When you check out the 443 api server, you will see similar.

The proxy headers and other options you see here are just performance optimizations and forwarded headers.

Note — if you copy this config file, make sure you replace the dotnetcoredocker.com in the certificate paths with your domain name(s). This will need to be done for all 443 servers that have ssl cert locations (all of mine have them). If you do not do this, your ssl certs will fail when you run the init-letsencrypt.sh script.

Note 2.0 — I have recently updated my dotnetcoredocker.conf files with Mozilla’s recommended Nginx config: https://mozilla.github.io/server-side-tls/ssl-config-generator/.

I’m going to skip the nginx specific docker-compose file for now. Showing it before the next section will just cause confusion, but we will get back to it during deployment.