Learn Nginx and its basics in 2022

ajayv1

Ajay Kumar Verma

Posted on February 5, 2022

Learn Nginx and its basics in 2022

A few weeks ago, I had to do a complex redirect of a request e.g if the request coming to nginx has a specific query param or is coming from a specific host then internally redirect to a different path.

I had completed my logic and was certain about its working but as the saying goes —

Your software will eventually fail unless it's verified with all the edge cases.

However, due to our system dependency, I cannot merge the code to staging dev for testing as in case of nginx failure, it will block other developers to write/test their node API or client code.

So, to mitigate this, I have set up the nginx in my local computer and did thorough testing. Once it’s fine locally, the code is ready to be pushed in staging for further testing. This way I save lots of time without hampering others' work.

In this article, I’ll walk through the basics of nginx, installation, and set up locally, setting up logs, and a few others.

Let’s start with the definition of nginx.

What is Nginx?

Nginx is a short form of engine x is an HTTP and reverse proxy server. It is heavily used as a load balancer and serves static files, sometimes even complete static websites like the company’s blog hosted on Nginx!.

Load balancer

In simple terms, the Load balancer is like a middle man sitting in between the concerned parties e.g assume A is the list of clients and B is the list of the servers then —

Case 1: With No Load Balancer

All incoming requests would be going to just one server which in the worst case, makes it hang or crash. You probably have heard the term Node API or Service API is down which means the box or the server serving that API request is hung or crashed due to request overload or OutOfMemory etc. Thus making the UX unresponsive.

Case 2: With Load Balancer

All incoming requests will have to go through the Load Balancer. It maintains the routing table and gets notification if any of the boxes or server goes down (through polling).

It efficiently distributes the network requests across servers and if one server goes down it redirects the requests to others servers that are online. Thus, maintaining the availability of the server always online.


Nginx Configuration File

This file is a tree-like structure and contains the instructions in the form of rules/blocks.

# main context (outside any other context i.e global context)

# event context
event {
  worker_connections 1024;
}

#http context
http {

  # server context 
  server {
     # code goes here
  }
  server {
     # another server context, code goes here
  }
}
Enter fullscreen mode Exit fullscreen mode

Before we dive into creating our own web server, let's learn the Nginx configuration file structure in a crisp mode -

Main Context —

The main context a.k.a. global context is the topmost context and all other contexts are part of it e.g Event context, HTTP context. It is used to configure details that affect the entire application on a granular level.

Event Context —

Event Context is contained within the Main context. It deals with connection handling in general. All the directives defined within this context deals with how worker processes should handle the incoming connections.

The HTTP Context —

This is the sibling of the Event context and written side-by-side of the event context rather than nested. This context will hold the majority of the configurations if we are using Nginx as a web server or reverse proxy.

Note:-

There can only be one Event context and HTTP context within the nginx configuration.

Later in the article, We will see 2 more contexts — server context and location context.


How to install Nginx in macOS?

If you are not using brew then install it first. Open your terminal and do the followings —

install brew

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Enter fullscreen mode Exit fullscreen mode

Once the brew is installed, do

brew install nginx
Enter fullscreen mode Exit fullscreen mode

Once nginx is installed, you can verify it by

nginx -v
Enter fullscreen mode Exit fullscreen mode

Above should print nginx version: nginx/<some version number>

e.g.
nginx version: nginx/1.21.0
Enter fullscreen mode Exit fullscreen mode

Once nginx is installed, the brew will create the nginx folder at the below location -

/usr/local/etc/nginx
Enter fullscreen mode Exit fullscreen mode

the default nginx.conf will look like this -

events {
  worker_connections 1024;
}

http {
  server {
    listen       8080;
    server_name  localhost;
    location / {
      root   html;
      index  index.html index.htm;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

To start the nginx services do the following —

nginx 
Enter fullscreen mode Exit fullscreen mode

if there is any error it will be logged in the terminal, to test if it is serving the default HTML file, hit the URL -

http://localhost:8080
Enter fullscreen mode Exit fullscreen mode

and to stop it —

nginx -s stop
Enter fullscreen mode Exit fullscreen mode

How to serve files from a different location?

Let's modify the nginx.conf file to read the HTML file from a different location —

First, create a folder that contains the HTML file index.html (with below content) that you want to serve e.g I’ve created nginx-poc inside the Download folder.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>This is index html file from nginx-poc folder</h1>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Copy the complete path of this folder that will be the root and open the nginx.conf to update it

using vim editor or nano or any other way you prefer to open the file (e.g nano) —

nano /usr/local/etc/nginx/nginx.conf
Enter fullscreen mode Exit fullscreen mode

and update the root location

events {
  worker_connections 1024;
}

http {
  server {
    listen       8080;
    server_name  localhost;
    location / {
      root   /Users/Download/nginx-poc;
      index  index.html index.htm;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now stop the nginx and start it again to see the updated changes. Hit the localhost URL with port 8080 -

Nginx Serving HTML from different location


How to do Url redirection?

Sometimes, the need may arise for URL redirection when the existing resource is moved to a different location. Let’s see how can we achieve this —

Create a js folder and a few js files in it inside the nginx-poc folder —

|-- nginx-poc
| |-- index.html
| |-- js
| | |-- a.js
| | |-- b.js
| | |-- c.js
| | |-- b-latest.js
Enter fullscreen mode Exit fullscreen mode

Folder structure

Just have one simple console.log(<filename>) inside each js file -

e.g for a.js

console.log('serving a.js');
Enter fullscreen mode Exit fullscreen mode

for b-latest.js

console.log('serving b-latest.js');
Enter fullscreen mode Exit fullscreen mode

and so on.

Let’s say the file b.js is no longer useful and we want to serve the b-latest.js in place of it. Of course, we can say that wherever our anchor link is pointing to b.js we’ll replace it with the b-latest.js, but it has 2 issues -

  1. It is time-consuming and is error-prone.
  2. The old URL will give the 404 and that's something we should thrive to reduce. Ideally, there shouldn’t be any 404 redirects, it should be kept as low as possible.

One simple solution would be to do it from nginx internal redirect —

events {
  worker_connections 1024;
}

http {
  server {
    listen       8080;
    server_name  localhost;

    root   /Users/Download/nginx-poc;

    location /js {
      rewrite /js/b.js /js/b-latest.js break;
    }

    location / {
      # root   /Users/Download/nginx-poc;
      index  index.html index.htm;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Let me go through each change to make it clearer —

  1. Commented root in location / — This is moved to the server context. Adding the root to the server context makes it available for all the location contexts within it.

  2. Added location context to handle the /js request — This request will handle all the request coming from /js, /js/* i.e request for /js/b.js will fall in this location. We are rewriting the request URL internally from /js/b.js to /js/b-latest.js and then we are adding a break which means no more parsing of any other rewrite!

Note:—

  1. The server context is a child of the HTTP context. There could be multiple server contexts, unlike event and HTTP context which are allow allowed once.

  2. The location context is a child of the server context. Similar to server context, multiple location contexts are allowed. They are the ones where actual handling of the incoming request is done.


How to add custom logs?

Logs are really important, they are needed to test our logic. In case any issue comes in production code, we can easily debug by seeing the nginx logs. Let me show you how can we set it up locally so that we can test and view the complete logic along with logs in localhost.

By default, nginx has 2 types of logs — access log and error log

Access log —

This logs the visitor's activity e.g requested URL, IP addresses, host, referrer, etc. If a request is served successfully it will log in access.log file.

access_log <location of log file> log_format;
Enter fullscreen mode Exit fullscreen mode

In log_format, we can add what data we want to log but just a note, it is an optional thing.

One important point to remember is log_format must be placed under the HTTP context otherwise it will throw an error.

e.g.

Syntax - 

log_format <log_format_name> string;

log_format custom '$remote_addr - $remote_user [$time_local] '
'"$request" "$uri" $status $body_bytes_sent ''"$http_referer" "$http_user_agent" "$gzip_ratio"';
Enter fullscreen mode Exit fullscreen mode

Sample Nginx Access log

Error log —

This logs each glitch and Syslog i.e if a request was not served by any means it will be recorded in the error.log file.

Syntax -

error_log <location of error.log file> <error-severity-level>

error_log /usr/local/etc/nginx/logs/error.log notice;
Enter fullscreen mode Exit fullscreen mode

Configuration file after adding the logs —

events {
  worker_connections 1024;
}

http {
  log_format custom '$remote_addr - $remote_user [$time_local] '
      '"$request" "$uri" $status $body_bytes_sent '
      '"$http_referer" "$http_user_agent" "$gzip_ratio"';

  server {

     listen       80;

     server_name  localhost;

     root   /Users/Downloads/nginx-poc;

     access_log /usr/local/etc/nginx/logs/acess.log custom;
     error_log /usr/local/etc/nginx/logs/error.log notice;
     rewrite_log on;

     location /js {
       rewrite /js/b.js /js/b-latest.js break;
     }

     location / {
       index  index.html index.htm;
     }
   }
}
Enter fullscreen mode Exit fullscreen mode

The rewrite_log should be ON to record the internal redirect. Also, notice severity level means it is just a notice which can be simply ignored i.e nothing serious.

Nginx error.log logging the redirect


How to handle query params?

It may arise a case when we want to internally redirect the request based on query parameters. Let’s see how can we implement the below 3 cases in the nginx.conf file —

events {
  worker_connections 1024;
}

http {
  log_format custom '$remote_addr - $remote_user [$time_local] '
      '"$request" "$uri" $status $body_bytes_sent '
      '"$http_referer" "$http_user_agent" "$gzip_ratio"';

  server {

     listen       80;

     server_name  localhost;

     root   /Users/Downloads/nginx-poc;

     access_log /usr/local/etc/nginx/logs/acess.log custom;
     error_log /usr/local/etc/nginx/logs/error.log notice;
     rewrite_log on;

     location /js {
       if ($query_string ~* "latest=true") {
         rewrite /js/b.js /js/b-latest.js break;
       }

       if ($query_string ~* "latest=false") {
         rewrite /js/b.js /js/c.js  break;
       }

       rewrite /js/b.js /js/a.js break;
     }

     location / {
       index  index.html index.htm;
     }
   }
}
Enter fullscreen mode Exit fullscreen mode

Case 1 —

request is for b.js → serve b-latest.js iff query params have latest=true

serve b-latest.js case 1 when query param latest is true

Case 2 —

request is for b.js → serve c.js iff query params have latest=false

serve c.js case 2 when query param latest is false

Case 3 —

request is for b.js → serve a.js default

No Query param


Conclusion

Nginx is not just it, and can’t be covered in just one article. But, I hope this will let you get started to know more. With the local setup, It becomes really handy when you want to test your nginx logic locally before deploying it to your staging or production.

I really hope you like the article, if yes, please follow me and if possible buy me a coffee. This article is originally published on my website, keep visiting that also for regular updates.

Thank You! Stay tuned for more articles.

💖 💪 🙅 🚩
ajayv1
Ajay Kumar Verma

Posted on February 5, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Learn Nginx and its basics in 2022
webdev Learn Nginx and its basics in 2022

February 5, 2022