Skip to main content

NGINX Reverse Proxy - How to Home Lab Part 4



Comments: counting...

You will learn about setting up an NGINX reverse proxy, adding VM disk space, and managing NodeJS apps with pm2


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.

Clone Template

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

VM Name

Go ahead and start the new VM up.

Start VM

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 | 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!

Low Disk Space

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.

Add Hard Disk Hard Disk Settings

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

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.

lsblk Again

Provision Web App

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

git clone
cd 4t
npm install

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

nano package.json
homepage Setting

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.

chmod +x
npx serve --single --no-clipboard build

Now let’s start that script with PM2.

pm2 start --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.

IP Address from DHCP Reservation

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;

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
  • listen 80: 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
  • 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.
  • proxy_read_timeout 36000s: 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
  • proxy_buffering off: 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
  • proxy_redirect off: 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.
  • 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
  • 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
  • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for: Same as above but a different header name
  • proxy_set_header X-Forwarded-Host $host: 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
  • proxy_hide_header X-Powered-By: 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.
  • proxy_pass 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 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!

4t App Behind Reverse Proxy