Introduction

Nginx is one of the most popular and stable web servers in the world. It is used by most traffic receiving sites and also some cloud providers are use it as as a managed reverse proxy as well. It is performnance, being light weight and low resource requirements are just one of the reasons of its popularity. The configuration flexibility is another.

This article explores Nginx's reverse proxy features to serve two different apps via two subdomains. For simplicity we will use local resolution so that you can easily replicate the setup. For simplicity we will only use HTTP but you can use the same configurations for HTTPS as well adding the HTTPS configuration on top.

Also we will use Python to create our apps, Python's SimpleHTTPServer module easily allows us to 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 are using an Ubuntu 20.04 LTS in this article.

Preparing Our Server

Let's prepare our server for our setup, we will 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.

$ 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 -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 externally you can skip this step.

$ sudo ufw allow 80

Instaling Python and HTTPie

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

$ apt install -y python

We will use HTTPie for our testing and verification, 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 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 it's module SimpleHTTPServer. Which is a static web server you can just run from the shell. It is 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 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 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 our testing setup.

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

Let's create two entries on the /etc/hosts file which is 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 are in the main task. We will now configure Nginx so that it will look to the HOST header and determine which requests will be forward to which web server.

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 is the all required configuration to configure our reverse proxy.

We prefer to use 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 HOST header. This is the minimal required configuration to configure our 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 if 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 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

Reloading the NGINX Server

Let's reload the nginx server so that our new configuration is loaded.

$ sudo systemctl reload nginx

Checking the status of NGINX Server

Just check the status ofm 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 request to appa.domain.com and appb.domain.com. Expected result is that our different contents are served from the correct web server.

App A

Sending 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 expected result, we can reach appA via appa.domain.com.

App B

Sending 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 expected result, we can reach appB via appb.domain.com.

Conculusion

We successfully configured and setup two different web servers and our 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.