Building a reverse proxy server with Nginx, Certbot, Raspbian Stretch Lite and Raspberry Pi 3

The Nginx reverse proxy server runs well on Raspberry Pi 3 and you can use it behind a router to route HTTP traffic to upstream web applications.

A Raspberry Pi 3 reverse proxy server is a very useful appliance to help us host multiple websites from home.

Some examples of web applications that you may want to host at home includes:

This post discusses how you can build a reverse proxy server with Nginx, Certbot, Raspbian Stretch Lite and Raspberry Pi 3 to proxy HTTP traffic directed at applications on your home network.

Recommended hardware list for your reverse proxy server

So what can you buy for a Raspberry Pi 3 project like this? In case you need it, the following is a list of hardware that you may want to consider to get for your reverse proxy server:

Setting up Raspbian Stretch Lite with SSH server enabled on your microSD card

Once you had gathered all the necessary hardware, proceed to setup Raspbian Stretch Lite with SSH server enabled on your microSD card. Doing so will allow you to SSH into your Raspbian Stretch Lite to perform further configurations in this post.

Assembling the hardware for the Raspberry Pi 3 reverse proxy server

Once the ssh file is created in the boot partition of the microSD card, remove the microSD card from your SD card reader and insert it to the microSD card slot on the Raspberry Pi 3 board. After that, proceed to assemble the Raspberry Pi 3 board to the Official Raspberry Pi case.

Starting the Raspbian Stretch Lite operating system

With the assembly of your Raspberry Pi 3 board and Official Raspberry Pi case, connect one end of the RJ45 cable to the RJ45 port on the Raspberry Pi 3 board and the other end of the cable to one of the switch port of your home router. After that, connect the micro USB power supply to the Raspberry Pi 3 board and a wall socket. Turn on the power socket to supply power to the Raspberry Pi 3 board.

Changing default password, Locale and Timezone of your Raspbian Stretch Lite

There are a few configurations that you should perform on the first run of your Raspbian Stretch Lite. Proceed on to change the default password, Locale and Timezone of your Raspbian Stretch Lite.

Installing Nginx on Raspbian Stretch Lite

Once you are done with changing the the default password, Locale and Timezone of your Raspbian Stretch Lite, the next software to install is Nginx. To install Nginx on Raspbian Stretch Lite, run the following commands:

sudo apt-get update
sudo apt-get install nginx -y 

Once the installation for Nginx completes, you can verify whether it is successfully started with the following command:

systemctl status nginx.service

which should give you output similar to the following:

● nginx.service - A high performance web server and a reverse proxy server
   Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2018-02-01 15:39:41 UTC; 34s ago
     Docs: man:nginx(8)
  Process: 1715 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCES
  Process: 1712 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status
 Main PID: 1716 (nginx)
   CGroup: /system.slice/nginx.service
           ├─1716 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
           ├─1717 nginx: worker process
           ├─1718 nginx: worker process
           ├─1719 nginx: worker process
           └─1720 nginx: worker process

Feb 01 15:39:41 raspberrypi systemd[1]: Starting A high performance web server and a reverse proxy serve
Feb 01 15:39:41 raspberrypi systemd[1]: Started A high performance web server and a reverse proxy server

Installing Certbot on Raspbian Stretch Lite

Once you had installed Nginx, proceed to install Certbot on Raspbian Stretch Lite. Certbot is a ACME client that can help us get free browser-trusted SSL/TLS certificates from Let's Encrypt. With Let's Encrypt certificates, our reverse proxy server will be able to serve HTTPS traffic on behalf of other upstream web applications.

Using Certbot to get free Let's Encrypt SSL/TLS certificates for your domains

Getting free Let's Encrypt SSL/TLS certificates for the domains that our reverse proxy will serve HTTPS traffic with is a three step process.

Step 1: Setting up a web directory for ACME challenges

We first help Certbot proof that the web server controls the domains that the SSL/TLS certificates will be issued to. To do so, we will need to configure Nginx to serve ACME challenges from a directory that Certbot will write to.

Suppose we are configuring HTTPS for the domain example.com, we will create an nginx configuration file located at /etc/nginx/sites-enabled/example.com.conf which the following contents:

server {
	listen 80;
	server_name  example.com;

	root /var/www/example.com;

	location ~ /.well-known {
		allow all;
	}
}

After saving /etc/nginx/sites-enabled/example.com.conf, we will then proceed to create /var/www/example.com directory and change the owner to www-data:

sudo mkdir /var/www/example.com
sudo chown www-data:www-data /var/www/example.com

Once the directory is created, restart Nginx with the following command:

sudo systemctl restart nginx.service

Step 2: Running Certbot to acquire the artefacts that are needed for serving HTTPS traffic

Once we had configured Nginx to serve HTTP requests made by Let's Encrypt CA to retrieve the ACME challenges, we then proceed to run the following command to get Certbot to acquire the artefacts that are needed for Nginx to serve HTTPS traffic:

sudo certbot certonly -a webroot --webroot-path=/var/www/example.com -d example.com

Step 3: Configuring Nginx to serve HTTPS traffic

If you had not already computed a Diffie-Hellman group for Nginx to use for exchanging cryptographic keys with its clients, run the following command to generate one:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

This process will take some time to complete.

Assuming that we want our Nginx to proxy HTTPS traffic directed at example.com to an upstream web application reachable via http://192.168.1.123:12345, we will update /etc/nginx/sites-enabled/example.com.conf with the following content:

# Redirect HTTP requests to HTTPS 
server {
    listen 80;
    server_name  example.com;
    return 301 https://$host$request_uri;
}
 
# For ssl
server {
    ssl on;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security max-age=15768000;
     
    default_type  application/octet-stream;
     
    listen 443;
    server_name  example.com;
 
    root /var/www/example.com;
 
    location ~ /.well-known {
        allow all;
    }
 
    location / {
        proxy_pass http://192.168.1.123:12345;
    }
}

Renewing the SSL certificate in the future

The SSL certificate issued by Let's Encrypt will expire. Before your SSL certificate for your domain expires, you should renew it so that browsers will continue to trust your website.

To renew the SSL certificate issued for example.com and www.example.com, run the following command:

sudo certbot certonly --force-renewal -a webroot --webroot-path=/var/www/example.com -d example.com 

Common Nginx configurations for reverse proxies

Once we had performed the system administrative stuff for our Raspberry Pi 3 reverse proxy server, the final step will be to configure Nginx to perform the proxying of HTTP / HTTPS traffic for upstream servers.

Configurations for load balancing between multiple upstream servers

If you find that the WordPress site on your Raspberry Pi 3 is not coping up with your surging traffic and you want another WordPress site on your Raspberry Pi Zero W to share some load, you can configure Nginx to perform load balancing for HTTP / HTTPS traffic directed at the domain for your WordPress site.

Suppose that:

  • One of your WordPress site is served by an instance of PHP 7.0 FPM residing on the same machine as your Nginx reverse proxy server.
  • Another one is served by an instance of PHP 7.0 FPM via port 8000 of 192.168.1.155 as an IP address.
  • Both WordPress sites share the same codebase with the same directory structure.

You will first define a server grouping with the upstream directive:

upstream wordpress_cluster {
    server 192.168.1.155:8000;
    server unix:/run/php/php7.0-fpm.sock;
}

After you had defined the server group that represent your upstream PHP FastCGI Process Managers, you can then proceed with creating the configurations for proxying HTTP requests to the PHP FastCGI Process Managers.

Configurations for proxying to PHP FastCGI Process Manager

Undeniably, one popular administrative task is to configure Nginx for PHP web applications.

For example, the following is a set of configuration that was taken from WordPress's guide for configuring Nginx:


server {
        listen   80;
        ## Your website name goes here.
        server_name anewwebsite.com www.anewwebsite.com;
        ## Your only path reference.
        root /var/www/my_new_wordpress_site;
        ## This should be in your http block and if it is, it's not needed here.
        index index.php;
 
        location = /favicon.ico {
                log_not_found off;
                access_log off;
        }
 
        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;
        }
 
        location / {
                # This is cool because no php is touched for static content.
                # include the "?$args" part so non-default permalinks doesn't break when using query string
                try_files $uri $uri/ /index.php?$args;
        }
 
        location ~ \.php$ {
                #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
                include fastcgi.conf;
                fastcgi_intercept_errors on;
                fastcgi_pass wordpress_cluster;
                fastcgi_buffers 16 16k;
                fastcgi_buffer_size 32k;
        }
 
        location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
                expires max;
                log_not_found off;
        }
}

What the "location ~ \.php$" block does?

The gist of proxying HTTP traffic to instance(s) of PHP FastCGI Process Manager lies in the "location ~ \.php$" block which defines the behaviour of Nginx when it encounters a HTTP request for urls ending with .php for either the anewwebsite.com or www.anewwebsite.com domain.

In our example, we used the fastcgi_pass directive to set the destination where such HTTP requests will be directed. In this case, we used the upstream block wordpress_cluster that we had defined earlier for load balancing.

Along with the fastcgi_pass directive, we also used directives from ngx_http_fastcgi_module to describe the HTTP request to the PHP FastCGI Process Manager that will process the HTTP request on behalf of Nginx.

Since the fastcgi.conf file contains common applications of directives from ngx_http_fastcgi_module, we included them in the "location ~ \.php$" block too.

Configurations for proxying to any web application

When in doubt, the best way to proxy HTTP requests to upstream servers is via the proxy_pass directive.

As an example, the following is a set of Nginx configurations that proxies HTTPS traffic to the MotionEye web interface:

# Redirect HTTP requests to HTTPS 
server {
    listen 80;
    server_name  cctv.adomainname.com;
    return 301 https://$host$request_uri;
}
 
# For ssl
server {
    ssl on;
    ssl_certificate /etc/letsencrypt/live/cctv.adomainname.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cctv.adomainname.com/privkey.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security max-age=15768000;
     
    default_type  application/octet-stream;
     
    listen 443;
    server_name  cctv.adomainname.com;
 
    root /var/www/cctv.adomainname.com;
 
    location ~ /.well-known {
        allow all;
    }
 
    location / {
        include proxy_params;
        proxy_pass http://192.168.0.107;
    }
}

The proxy_pass directive describes the HTTP request to the upstream server with mechanisms from the HTTP protocol. In the example, we simply pass the HTTPS request directed at cctv.adomainname.com domain to the motionEye web interface at 192.168.0.107.

We can use other directives in ngx_http_proxy_module to describe the HTTP request to the upstream web server that will process the HTTP request on behalf of Nginx.

Buying the recommended hardware list to setup your own Raspberry Pi 3 reverse proxy server

If you do not have the Raspberry Pi 3 hardware mentioned in this post yet, you may want to purchase them from Amazon. Simply click on the button below to add the Raspberry Pi 3 hardware to your cart. You may remove anything that you already have or replace some of the hardware with other hardware.


About Clivant

Clivant a.k.a Chai Heng enjoys composing software and building systems to serve people. He owns techcoil.com and hopes that whatever he had written and built so far had benefited people. All views expressed belongs to him and are not representative of the company that he works/worked for.