A reverse proxy is a web server that sits between client devices and backend servers, receiving requests from clients and directing them to the appropriate server. The backend servers are shielded from direct internet access and their IP addresses are hidden, providing an additional layer of security.

reverse-proxy

Overall, reverse proxies are useful for improving security, performance, and scalability of web applications and services. They’re commonly used for load balacing traffic between any number of backend servers, and for SSL offloading.

In this brief post I provide a template for setting up an nginx reverse proxy using docker.

Docker-compose

This is a docker-compose file with two services, the nginx web server that will act as a reverse proxy, and a certbot agent for enabling SSL connections to it:

version: '3.3'

services:

  nginx:
    image: nginx:1.19-alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./volumes/nginx:/etc/nginx/conf.d
      - ./volumes/certbot/conf:/etc/letsencrypt
      - ./volumes/certbot/www:/var/www/certbot
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"

  certbot:
    image: certbot/certbot
    restart: always
    volumes:
      - ./volumes/certbot/conf:/etc/letsencrypt
      - ./volumes/certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

The nginx service uses the official Nginx Docker image with version 1.19-alpine. It maps ports 80 and 443 to the host machine, which allows incoming HTTP and HTTPS requests to be forwarded to the Nginx container. The volumes section maps three directories on the host machine to directories in the container:

./volumes/nginx is mapped to /etc/nginx/conf.d, which allows custom Nginx configuration files to be added to the container.

./volumes/certbot/conf is mapped to /etc/letsencrypt, which stores the SSL/TLS certificates generated by Certbot.

./volumes/certbot/www is mapped to /var/www/certbot, which is where Certbot writes temporary files during the certificate renewal process.

The certbot service uses the official Certbot Docker image. It also maps the same volumes as the nginx service. The entrypoint section specifies a shell command that is executed when the container starts up. This command runs a loop that sleeps for 12 hours before evaluating the renewal of SSL/TLS certificates using Certbot.

Now let’s see how each service is configured.

Nginx

Below you’ll’ find an nginx configuration file that sets it up as a load balancer and reverse proxy for the thomasvilhena.com domain:

### Nginx Load Balancer

upstream webapi {
	server 10.0.0.10;
	server 10.0.0.11;
	server 10.0.0.12; down;
}

server {
	listen 80;
	server_name localhost thomasvilhena.com;
	server_tokens off;
		
	location ^~ /.well-known/acme-challenge/ {
		default_type "text/plain";
		alias /var/www/certbot/.well-known/acme-challenge/;
	}
		
	location = /.well-known/acme-challenge/ {
		return 404;
	}
		
	location / {
		return 301 https://thomasvilhena.com$request_uri;
	}
}


server {
	listen 443 ssl http2;
	server_name localhost thomasvilhena.com;
	server_tokens off;

	ssl_certificate /etc/letsencrypt/live/thomasvilhena.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/thomasvilhena.com/privkey.pem;
	include /etc/letsencrypt/options-ssl-nginx.conf;
	ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

	location / {
		proxy_pass http://webapi;
		proxy_set_header Host $http_host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_set_header X-NginX-Proxy true;
		proxy_redirect off;
	}
}

The “upstream” section defines a group of servers to be load balanced, with three sample servers listed (10.0.0.10, 10.0.0.11, and 10.0.0.12). One server is marked as “down” which means it won’t receive requests.

The first “server” block listens on port 80 and redirects all requests to the HTTPS version of the site. It also includes some configuration for serving temporary files over HTTP which are required for the SSL certificate renewal process through Let’s Encrypt.

The second “server” block listens on port 443 for HTTPS traffic and proxies requests to the defined “upstream” group of servers. The “location /” block specifies that all URLs will be proxied. The various “proxy_set_header” directives are used to set the headers needed for the upstream servers to function correctly.

Certbot

Certbot requires two configuration files:

/volumes/certbot/conf/options-ssl-nginx.conf contains recommended security settings for SSL/TLS configurations in Nginx. Here’s a sample content:

ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";

/volumes/certbot/conf/ssl-dhparams.pem contains Diffie-Hellman parameters used for SSL/TLS connections. It is generated by running the following command:

openssl dhparam -out /etc/letsencrypt/ssl-dhparams.pem 2048

Here’s a sample content:

-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA3r1mOXp1FZPW+8kRJGBOBGGg/R87EBfBrrQ2BdyLj3r3OvXX1e+E
8ZdKahgB/z/dw0a+PmuIjqAZpXeEQK/OJdKP5x5G5I5bE11t0fbj2hLWTiJyKjYl
/2n2QvNslPjZ8TpKyEBl1gMDzN6jux1yVm8U9oMcT34T38uVfjKZoBCmV7g4OD4M
QlN2I7dxHqLShrYXfxlNfyMDZpwBpNzNwCTcetNtW+ZHtPMyoCkPLi15UBXeL1I8
v5x5m5DilKzJmOy8MPvKOkB2QIFdYlOFL6/d8fuVZKj+iFBNemO7Blp6WjKsl7Hg
T89Sg7Rln2j8uVfMNc3eM4d0SEzJ6uRGswIBAg==
-----END DH PARAMETERS-----

That’s it, now you just need to docker-compose up and your reverse proxy should be up and running ✅