If you find this useful, please consider sharing it on social media to help spread the word!

The intent of this series is to get new home labbers up and running with a beginner-friendly, versatile, and maintainable home lab, and equip them with the knowledge to confidently manage and utilize their systems, eventually working all the way up to advanced home networking, high availability systems (HA), storage area networks (SAN), Email servers, and more!

Don’t miss out on the next segment - Subscribe to my mailing list so you can stay up to date! And if you get stuck somewhere along the way, feel free to reach out to me for assistance, I’ll be happy to help out any way that I can.

Preface

In this segment you will learn about setting up an NGINX reverse proxy, adding VM disk space, and managing NodeJS apps with pm2. I am going to set up an instance of the ‘4t’ app I put together in React, which is a 20, 20, 20 timer for eye health that I use all the time, but you are free to set up any back-end host you wish.

Set up a Web Endpoint

Create a new VM

The first thing we need to do is set up a web app on a new VM to proxy traffic to, so let’s clone our template again.

Use a linked clone and choose a name for this VM.

Go ahead and start the new VM up.

You’ll want to run through the boilerplate steps from part 3 to set a unique hostname and give the new VM some entropy.

Install Dependencies

Let’s get our software suite installed (NodeJS, PM2), run the following commands in a console session on the new VM.

PM2 is a process manager for NodeJS applications, it will handle starting them up at boot time, and restart any processes that crash.

curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - sudo apt dist-upgrade -y sudo apt install -y nodejs git npm install -g pm2

Now we will run into a problem, we are almost out of disk space on this VM!

Add Disk Space

You could just as well resize the existing hard disk in most cases, but I want to demonstrate the power and flexibility of LVM here, so I will add a new disk and combine the two.

Head to Datacenter > (Name of your host) > (Name of your VM) > Hardware > Add > Hard Disk.

Back in the VM Console, run the command lsblk to list the block devices, and you will see the new disk sdb available.

The following commands will provision the new disk as an LVM physical volume (PV), extend the existing volume group (VG) to span both the new and old disk, and then expand the logical volume (LV) the OS is installed upon to its maximum available size.

First we need to know the existing VG and LV names, we can discover these with the commands sudo vgs , and sudo lvs , respectively. In my case the VG is name ubuntu-vg , and the LV is name ubuntu-lv .

sudo pvcreate /dev/sdb sudo vgextend ubuntu-vg /dev/sdb sudo lvextend ubuntu-vg/ubuntu-lv -l+100%FREE sudo resize2fs /dev/ubuntu-vg/ubuntu-lv reboot

Since the OS is running on the LV that we extended, it will have to reboot to reflect the changes. I usually just add a new disk for application data if needed and keep it separate from the OS disk to avoid this, but now you’ve seen a taste of LVM’s feature set.

After the reboot, we can see that the block device mounted as / , which is our LV, has grown to 11GiB in size.

Provision Web App

Now let’s grab the source code for my 4t app and get it configured.

git clone https://github.com/dlford/4t.git cd 4t npm install

We’ll need to change the homepage value in the package.json file to http://127.0.0.1 because it’s set by default to be deployed on GitHub pages, and that configuration won’t work in our case.

nano package.json

Now that we fixed that, we can build the app.

npm run build

We’ll write a quick script to start the app running with the serve package from NPM.

touch startup.sh chmod +x startup.sh nano startup.sh

npx serve --single --no-clipboard build

Now let’s start that script with PM2.

pm2 start startup.sh --name 4t-app --watch

PM2 needs to be configured to start at boot time, run the command pm2 startup and PM2 will spit out a command for you to run to get this done, so go ahead and re-type that into the console as shown.

Lastly, save the running configuration for PM2 with the command pm2 save .

DHCP Reservation

Follow the DHCP Reservation steps from part 3.

Don’t forget to reboot the new VM after setting up DHCP reservation so it will acquire the new IP address.

Configure Reverse Proxy

From a console session on your NGINX VM, let’s create a configuration file for the new reverse proxy.

sudo nano /etc/nginx/sites-available/4t-app.conf

server { listen 80 ; server_name 4t.burns.lab ; location / { proxy_read_timeout 36000s ; proxy_http_version 1.1 ; proxy_buffering off ; client_max_body_size 0 ; proxy_redirect off ; proxy_set_header Connection "" ; 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-Host $host ; proxy_set_header X-Forwarded-Proto $scheme ; proxy_hide_header X-Powered-By ; proxy_pass_header Authorization ; proxy_pass http://172.16.44.102:5000 ; } }

Note that not all of the options are required for all circumstances, for this particular use case we could’ve omitted most of them, and there are many others that I haven’t listed here as well. I keep all of these options in all of my configurations just so I don’t have to look them up when I need them, I just copy an existing configuration and edit it for the new back-end server. Here’s what they do:

server : Designate that this block of configuration is for a server, has nothing to do with proxying

: Designate that this block of configuration is for a server, has nothing to do with proxying listen 80 : Listen on port 80 for HTTP traffic

: Listen on port 80 for HTTP traffic server_name 4t.burns.lab : This configuration applies only to requests for the hostname 4t.burns.lab , make sure to change this to your own host

: This configuration applies only to requests for the hostname , make sure to change this to your own host location / : A location block applies to a specific URL, it is recursive, meaning /page1 would still match the location block for / , unless there is a location /page1 also specified in the file.

: A location block applies to a specific URL, it is recursive, meaning would still match the location block for , unless there is a also specified in the file. proxy_read_timeout 36000s : how long to wait before giving up on connecting to the back-end server

: how long to wait before giving up on connecting to the back-end server proxy_http_version 1.1 : Use HTTP protocol version 1.1, this is needed when the NGINX proxy serves HTTP version 2 but the back-end server only supports version 1.1

: Use HTTP protocol version 1.1, this is needed when the NGINX proxy serves HTTP version 2 but the back-end server only supports version 1.1 proxy_buffering off : Don’t wait to receive the full reponse from back-end, send the response as a real-time stream

: Don’t wait to receive the full reponse from back-end, send the response as a real-time stream client_max_body_size 0 : Set the maximum size of the body of a request, 0 is disabled to allow any size

: Set the maximum size of the body of a request, 0 is disabled to allow any size proxy_redirect off : This is off by default, but can be used for URL re-writing

: This is off by default, but can be used for URL re-writing proxy_set_header Connection "" : Overwrite the Connection header of a request to an empty value, this prevents the request from closing a connection manually which will break keepalive on the back-end host if it is used.

: Overwrite the header of a request to an empty value, this prevents the request from closing a connection manually which will break keepalive on the back-end host if it is used. proxy_set_header Host $host : Use the host header from the request when contacting the back-end server, required if the back-end host also serves different content depending on the hostname in the request

: Use the host header from the request when contacting the back-end server, required if the back-end host also serves different content depending on the hostname in the request proxy_set_header X-Real-IP $remote_addr : The back-end host will log the request as coming from the reverse proxies IP address, the X-Real-IP header is used to determine the actual source of the request

: The back-end host will log the request as coming from the reverse proxies IP address, the header is used to determine the actual source of the request proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for : Same as above but a different header name

: Same as above but a different header name proxy_set_header X-Forwarded-Host $host : Same as above, yet another header name

: Same as above, yet another header name proxy_set_header X-Forwarded-Proto $sheme : Tell the back-end host if the original request came in over HTTP or HTTPS

: Tell the back-end host if the original request came in over HTTP or HTTPS proxy_hide_header X-Powered-By : Hide the service name and version information for the back-end server

: Hide the service name and version information for the back-end server proxy_pass_header Authorization : Pass any HTTP Basic Authorization data to the back-end server, which is a dirt simple method of password protecting a web site, it’s still used sometimes but better methods exist today.

: Pass any HTTP Basic Authorization data to the back-end server, which is a dirt simple method of password protecting a web site, it’s still used sometimes but better methods exist today. proxy_pass http://172.16.44.102:5000 : This should be the internal IP address and port for the back-end host that will receive proxied requests

Now we need to symlink the configuration file into the sites-enabled directory where NGINX looks for active configuration files.

sudo ln -s /etc/nginx/sites-available/4t-app.conf /etc/nginx/sites-enabled/

You should test the configuration with the command sudo nginx -t , if it comes back successful go ahead and restart NGINX with the command sudo service nginx restart .

See it in Action

This is where we really start to see the drawback to running an internal private network on Proxmox over a custom home network solution (we will address this in a future segment). If you visit http://(IP address of your pfSense VM) , you’ll get the default site running on the NGINX server and not the reverse proxied site, because we need the host header to match the configuration file for the proxied site ( 4t.burns.lab in my example above).

You’ll need to utilize the hosts file on your workstation to test this out, the hosts file is essentially a DNS override, you put in an IP address and hostname, and any communication to that hostname will go to the IP address in the hosts file rather than the IP address a DNS lookup would return.

The syntax is simple, just the IP address of your pfSense VM, one or more spaces, and the hostname you configured in the NGINX configuration file. In my example this is 10.128.0.27 4t.burns.lab . You’ll want to add that line to the bottom of the hosts file on your workstation, which you’ll need to edit as an administrator.

Windows: C:\Windows\System32\drivers\etc\hosts

Linux/Mac: /etc/hosts

After that change, drop http://(hostname) into your browsers URL bar (e.g. http://4t.burns.lab ), and you should see the 4t app running behind your NGINX reverse proxy, that’s running behind your pfSense VM, that’s running on your Proxmox host!