Introduction

Nginx is one of the most popular and stable web servers in the world. It is used by most traffic receiving sites, but cloud providers also use a managed nginx reverse proxy. Its performant, light weight nature is just one of the reasons of its popularity, with its configuration flexibility being another.

This article explores use of an Nginx reverse proxy to serve two different apps via two subdomains. We'll also be showing you how to setup an Nginx reverse proxy using local resolution so that you can easily replicate the setup. For simplicity, we will only use HTTP in this tutorial, but you can use the same configurations for HTTPS as well adding the HTTPS configuration on top.

Finally, we'll be using Python to create our apps, with Python's SimpleHTTPServer allowing us to easily create a static web server.

How-to-use-NGNIX-as-a-reverse-proxy-network-diagram

Prerequisites

Any recent Ubuntu Server would work with all the commands provided.

We'll be using a BitLaunch Ubuntu 20.04 LTS VPS for the sake of this Nginx reverse proxy tutorial. You can connect to your VPS using PuTTY or another SSH client.

Preparing Our Server

Let's prepare our server for our setup. We will first update the local package index and upgrade any packages that are outdated.

$ apt-get update
Hit:1 http://security.ubuntu.com/ubuntu focal-security InRelease
Hit:2 http://archive.ubuntu.com/ubuntu focal InRelease
Hit:3 http://archive.ubuntu.com/ubuntu focal-updates InRelease
Hit:4 http://archive.ubuntu.com/ubuntu focal-backports InRelease
Reading package lists... Done                        


$ apt-get -y upgrade
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Calculating upgrade... Done
The following packages were automatically installed and are no longer required:
....
....

Installing Nginx Server

Ubuntu default repositories includes Nginx so we do not need to add any external repositories. Please note that these repositories are following a tad behind of the official versions, if you require the latest and the greatest follow Nginx's installation document for the official external repositories.

Let's install NGINX package using apt package manager.You can follow our Nginx installation tutorial for different methods and more detail.

$ apt install -y nginx
Reading package lists... Done
Building dependency tree       
Reading state information... Done
....
....

Verifying Nginx Version and Options

After the installation we can verify the version and configure arguments with the -V flag.

$ nginx -V
nginx version: nginx/1.17.10 (Ubuntu)
built with OpenSSL 1.1.1f  31 Mar 2020
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -fdebug-prefix-map=/build/nginx-Pmk9_C/nginx-1.17.10=. -fstack-protector-strong -
....
....

Configuring Firewall to Allow Port 80

We need to allow port 80 for external access to our server. If you are not going to access it externally you can skip this step.

$ sudo ufw allow 80

Instaling Python and HTTPie

We will use Python to create our two apps, so let's install the required packages. Python is included in the default package repositories.

$ apt install -y python

We will use HTTPie for our testing and verification, but you can also use curl for verification.

$ apt install -y httpie

Making Sure NGINX Starts After Boot

We need to tell systemd that we want our Nginx service to start at boot:

$ sudo systemctl enable nginx
Synchronizing state of nginx.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable nginx

Start NGINX Server

At last we can start our Nginx server.

$ systemctl start nginx

Creating two Apps and their Web Servers

We need to create two apps to test our setup. For this we will use python and its module SimpleHTTPServer. This is a static web server you can just run from the shell. It's a very nice tool for local testing but not recommended for production use.

Service A

Creating the Content for App A

$ mkdir appA
$ cd appA
$ cat <<EOF >index.html
this is app A !
EOF

Starting Our Web Server for App A

We are using "&" at the end of our command to make sure the process runs in the background and we can keep using our shell. Also any STDOUT logs will be printed to our shell.

For Python 2

$ python -m SimpleHTTPServer 3000 &
$ Serving HTTP on 0.0.0.0 port 3000 ...

For Python 3

$ python3 -m http.server 3000 &
Serving HTTP on 0.0.0.0 port 3000 (http://0.0.0.0:3000/) ...

Testing App A

Let's test our app, using the HTTPie http client.

$ http http://localhost:3000
127.0.0.1 - - [24/May/2020 13:30:59] "GET / HTTP/1.1" 200 -
HTTP/1.0 200 OK
Content-Length: 16
Content-type: text/html
Date: Sun, 24 May 2020 13:30:59 GMT
Last-Modified: Sun, 24 May 2020 13:28:42 GMT
Server: SimpleHTTP/0.6 Python/2.7.18rc1

this is app A !

Perfect, we can see "app A" content on port 3000.

Service B

Creating the Content for App B

$ mkdir appB
$ cd appB
$ cat <<EOF >index.html
this is app B !
EOF

Starting Our Web Server for App B

We are using "&" at the end of our command to make sure the process runs in the background and we can keep using our shell. Also any STDOUT logs will be printed to our shell.

For Python 2

$ python -m SimpleHTTPServer 4000 &
$ Serving HTTP on 0.0.0.0 port 4000 ...

For Python 3

$ python3 -m http.server 4000 &
Serving HTTP on 0.0.0.0 port 4000 (http://0.0.0.0:4000/) ...

Testing App B

Let's test our app, using the HTTPie http client.

$ http http://localhost:4000
127.0.0.1 - - [24/May/2020 13:39:18] "GET / HTTP/1.1" 200 -
HTTP/1.0 200 OK
Content-Length: 16
Content-type: text/html
Date: Sun, 24 May 2020 13:39:18 GMT
Last-Modified: Sun, 24 May 2020 13:38:23 GMT
Server: SimpleHTTP/0.6 Python/2.7.18rc1

this is app B !

Perfect, we can see "app B" content on port 4000.

Domain Resolution for Our Local Setup

To distinguish our apps, we need to use the two subdomains we defined earlier. We will use a fake domain "domain.com" for this Nginx reverse proxy example.

  • appa.domain.com should go to app A
  • appb.domain.com should go to app B

Let's create two entries in the /etc/hosts file which are queried before the DNS resolution.

$ sudo cat <<EOF >> /etc/hosts
127.0.0.1 appa.domain.com
127.0.0.1 appb.domain.com
EOF

Configuring NGINX as a Reverse Proxy

At last we have finished all our preparations and we can start the main task. We will now configure Nginx so that it will look to the HOST header and determine which requests will be forwarded to which web server. This will effectively let us use it as an Nginx reverse proxy.

Let's remove default configuration's symlink from sites-enabled:

$ rm /etc/nginx/sites-enabled/default

Let's create our configuration file under sites-available:

$ touch /etc/nginx/sites-available/reverse-proxy

Now we need to create a symlink under sites-enabled

$ ln -s /etc/nginx/sites-available/reverse-proxy /etc/nginx/sites-enabled/reverse-proxy

Now edit the configuration file using nano or editor of your choice:

$ nano /etc/nginx/sites-available/reverse-proxy

Here are all the required values to configure our reverse proxy.

We prefer to use the upstream module as it can be used for load balancing later on, but please note that there are other methods to define servers.

Paste the following into nano and exit with Ctrl-X

upstream appA {
        server 127.0.0.1:3000;
}

upstream appB {
        server 127.0.0.1:4000;
}


server {
  listen 80;
  server_name appa.domain.com;
  
  location / {
        proxy_pass http://appA;
    }
}

server {
  listen 80;
  server_name appb.domain.com;

  location / {
        proxy_pass http://appB;
    }
}

Two upstream definitions define our apps; one goes to port 3000 and the other goes to port 4000. Using the server directive and server_name option we define the subdomain and upstream mapping using the HOST header. This is the minimal required configuration to configure our Nginx reverse proxy.

Testing the Configuration

We should test the NGINX configuration to make sure we don't have a typo or missing parts.

Note: If you do not test and you have a non valid configuration once you restart the server you will cause more down time. It is always better, if the changes allow,to use a simple reload.

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Reload NGINX Server

Let's reload Nginx server so that our new configuration is loaded:

$ sudo systemctl reload nginx

Checking the status of NGINX Server

Just check the status of the Nginx server.

$ sudo systemctl status nginx
● 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 Sun 2020-05-24 13:16:41 UTC; 3min 16s ago
       Docs: man:nginx(8)
    Process: 3525 ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload (code=exited, status=0/SUCCESS)
   Main PID: 3331 (nginx)
      Tasks: 2 (limit: 614)
     Memory: 4.2M
     CGroup: /system.slice/nginx.service
             ├─3331 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
             └─3526 nginx: worker process

May 24 13:16:41 5eca7132aa8cc30001a9eb8b systemd[1]: Starting A high performance web server and a reverse proxy server...
May 24 13:16:41 5eca7132aa8cc30001a9eb8b systemd[1]: Started A high performance web server and a reverse proxy server.
May 24 13:19:38 5eca7132aa8cc30001a9eb8b systemd[1]: Reloading A high performance web server and a reverse proxy server.
May 24 13:19:38 5eca7132aa8cc30001a9eb8b systemd[1]: Reloaded A high performance web server and a reverse proxy server.

Verifiying our Setup

Let's eat our cake now: we will test our two apps and our Nginx configuration. We will use a http client called HTTPie, sending requests to appa.domain.com and appb.domain.com. The expected result is that our different contents are served from the correct web server.

App A

Sending a HTTP request to host appa.domain.com

http -v http://appa.domain.com
127.0.0.1 - - [25/May/2020 11:45:25] "GET / HTTP/1.0" 200 -
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: appa.domain.com
User-Agent: HTTPie/1.0.3



HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 16
Content-Type: text/html
Date: Mon, 25 May 2020 11:45:25 GMT
Last-Modified: Sun, 24 May 2020 13:28:42 GMT
Server: nginx/1.17.10 (Ubuntu)

this is app A !

Great! An expected result: we can reach appA via appa.domain.com.

App B

Send a HTTP request to host appb.domain.com

http -v http://appb.domain.com
127.0.0.1 - - [25/May/2020 11:45:46] "GET / HTTP/1.0" 200 -
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: appb.domain.com
User-Agent: HTTPie/1.0.3



HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 16
Content-Type: text/html
Date: Mon, 25 May 2020 11:45:46 GMT
Last-Modified: Sun, 24 May 2020 13:38:23 GMT
Server: nginx/1.17.10 (Ubuntu)

this is app B !

Great, the expected result, we can also reach appB via appb.domain.com.

Conculusion

We successfully configured and setup two different web servers and our Nginx reverse proxy. As you can see, the simplest form of the configuration is quite short. There are many options that can be used to adjust the performance and toggle different settings.

Need a fast VPS for your Nginx reverse proxy server? Sign up to BitLaunch