My home server setup is composed of several Raspberry Pi, where I host different web applications (this blog, an RSS reader, some home IOT apps…). I’ve decided to setup a front gateway, that proxies the request to the right server:

The requests are proxied by an NGINX reverse proxy, running in a Docker container on the gateway. It redirects the HTTP requests based on the host (eg. remyg.ovh runs on rpi1 when rss.remyg.ovh runs on rpi2).

NGINX Configuration

The main NGINX conf file ( nginx.conf ) looks like this:

user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/sites-enabled/*.*; }

The only difference with the base conf file (from the default NGINX Docker image) is the last line:

include /etc/nginx/conf.d/*.conf;

is replaced by

include /etc/nginx/sites-enabled/*.*;

It ignores the default configuration ( /etc/nginx/conf.d/default.conf ) and uses the proxy configuration files that I defined.

Hosts Configuration

Each host has its own configuration file:

for remyg.ovh, running on rpi1 (with a local IP 192.168.0.10, and port 8080):

server { listen 80; server_name remyg.ovh; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; location / { proxy_pass http://192.168.0.10:8080; proxy_set_header Host $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_redirect off; } }

for rss.remyg.ovh, running on rpi2 (with a local IP 192.168.0.11, and port 8081):

server { listen 80; server_name rss.remyg.ovh; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; location / { proxy_pass http://192.168.0.11:8081; proxy_set_header Host $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_redirect off; } }

These files indicate that a request incoming to rss.remyg.ovh:80 ( server_name and listen ) will be redirected to 192.168.0.11:8081 ( proxy_pass ).

That’s all the configuration you need to serve websites on HTTP.

Running in Container

To run the reverse proxy in a Docker container, the file tree looks like this:

nginx-reverse-proxy -> conf -> nginx.conf -> sites -> remyg.ovh rss.remyg.ovh

With this structure, the command launching the container will be:

docker run --name mynginx-proxy \ -v /home/pi/nginx-reverse-proxy/sites:/etc/nginx/sites-enabled:ro \ -v /home/pi/nginx-reverse-proxy/conf/nginx.conf:/etc/nginx/nginx.conf:ro \ -p 80:80 -d nginx:alpine

HTTPS

To enable HTTPS on the different sites, I’m using Let’s Encrypt, and their utility app Certbot.

I’m starting by installing the certbot package:

sudo apt install certbot

When generating a certificate, Certbot will need to validate that it can access a specific file that it generates, pointing to the URL http://your-host/.well-known/acme-challenge/{token} . To do that, start by creating and mounting a new volume on the reverse proxy container:

docker run --name mynginx-proxy \ -v /home/pi/nginx-reverse-proxy/sites:/etc/nginx/sites-enabled:ro \ -v /home/pi/nginx-reverse-proxy/conf/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /home/pi/letsencrypt_www:/var/www/letsencrypt \ -p 80:80 -p 443:443 -d nginx:alpine

Then specify in the sites proxy configuration that this volume is used when pointing to /.well-known/acme-challenge/ :

server { listen 80; server_name remyg.ovh; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; location /.well-known/acme-challenge/ { root /var/www/letsencrypt; } location / { proxy_pass http://192.168.0.10:8080; proxy_set_header Host $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_redirect off; } }

And reload your NGINX config:

docker exec -it mynginx-proxy nginx -s reload

Now you can generate the certificate(s) :

sudo certbot certonly --authenticator webroot -w /home/pi/letsencrypt_www -d remyg.ovh -d rss.remyg.ovh

This will generate the ACME challenge files in /home/pi/letsencrypt_www , and validate the challenge. It will also generate the certificates, in /etc/letsencrypt/certs/live/remyg.ovh/ and /etc/letsencrypt/certs/live/rss.remyg.ovh/ .

The last step is to use the new certificates, and only allow HTTPS requests.

Start by mounting a new volume, containing the certificates:

docker run --name mynginx-proxy \ -v /home/pi/nginx-reverse-proxy/sites:/etc/nginx/sites-enabled:ro \ -v /home/pi/nginx-reverse-proxy/conf/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /etc/letsencrypt:/etc/nginx/certs \ -v /home/pi/letsencrypt_www:/var/www/letsencrypt \ -p 80:80 -p 443:443 -d nginx:alpine

Then update your proxy configuration:

server { listen 80; server_name remyg.ovh; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; location /.well-known/acme-challenge/ { root /var/www/letsencrypt; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name remyg.ovh; ssl_certificate certs/live/remyg.ovh/fullchain.pem; ssl_certificate_key certs/live/remyg.ovh/privkey.pem; location / { proxy_pass http://192.168.0.10:8080; proxy_set_header Host $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_redirect off; } }

Reload the NGINX configuration, and you’re all set!