Skip to main content

Command Palette

Search for a command to run...

All you need to know about Nginx

Published
8 min read

Nginx is a high performance web server developed to facilitate the increasing needs of the modern web. It focuses on high performance, high concurrency, and low resource usage. Although it's mostly known as a web server, NGINX at its core is a reverse proxy server.

why nginx is high concurrency and low resource usage ??

  • Connection creation is always the OS’s job.

    • When a client connects to your server (TCP handshake), it’s the Linux kernel that allocates the socket and puts it in the “listen queue” of your listening port (e.g., port 80/443).

    • NGINX doesn’t “make” the connection — it just tells the kernel:

        listen(80, backlog_size);
      

      and the kernel does the rest.

  • NGINX gets notified of connections via epoll.

    • NGINX registers its listening sockets with epoll.

    • When the kernel accepts a new connection, epoll signals NGINX: "hey, fd #1234 is ready".

    • NGINX then calls accept() on that socket and starts handling it.

  • Why this is efficient:

    • The kernel can manage thousands of sockets without NGINX looping through them.

    • NGINX doesn’t block on one socket — it only acts when the kernel says there’s data or an event.

    • One worker process can therefore handle 10k+ concurrent connections.

so all overhead is on OS not on nginx connection and other stuff it just takes connections from queue and server that request.

PRACTICE TIME

1.Hosting Multiple Static Sites with NGINX

Introduction

When learning NGINX, a fun and practical mini-project is to host multiple static websites on the same server. Instead of using different ports, we can use domain-based virtual hosting. This means NGINX will look at the Host header in the HTTP request and serve different sites based on the requested domain.

This project covers how I ran three static websites on my local machine using NGINX, each mapped to a different domain (site1.com, site2.com, site3.com).


Steps I Followed

1. Create Directories for Each Site

mkdir -p /home/amrit/sites/site1
mkdir -p /home/amrit/sites/site2
mkdir -p /home/amrit/sites/site3

2. Add HTML Files

echo "<h1>Welcome to Site 1</h1>" > /home/amrit/sites/site1/index.html
echo "<h1>Welcome to Site 2</h1>" > /home/amrit/sites/site2/index.html
echo "<h1>Welcome to Site 3</h1>" > /home/amrit/sites/site3/index.html

3. Configure NGINX

Edited /etc/nginx/nginx.conf (or a separate file under /etc/nginx/conf.d/):

worker_processes auto; // create equal number of nginx worker as CPU cores
events {} // you need to write this forced 
// this should contain how worker handle connections 
//example  worker_connections 1024;  ##1024 connection per worker


http {
// here we create server 
//syntax is always similar listening port server_name  and from where to server files
    server {
        listen 80;
        server_name site1.com;
        root /home/amrit/sites/site1;
        index index.html;
    }

    server {
        listen 80;
        server_name site2.com;
        root /home/amrit/sites/site2;
        index index.html;
    }

    server {
        listen 80;
        server_name site3.com;
        root /home/amrit/sites/site3;
        index index.html;
    }
}

4. Update /etc/hosts

To make the domains resolve locally:

// or host it somewhere else on aws or on your network or vm or anywhere and make change to ip
127.0.0.1 site1.com
127.0.0.1 site2.com
127.0.0.1 site3.com

5. Reload NGINX

nginx -t
systemctl reload nginx

6. Test the Sites

curl http://site1.com
# <h1>Welcome to Site 1</h1>

curl http://site2.com
# <h1>Welcome to Site 2</h1>

curl http://site3.com
# <h1>Welcome to Site 3</h1>

Or simply open the domains in a browser.


Notes / Issues I Faced

  • At first, I got a 403 Forbidden error. This happened because /home/amrit directory was not accessible to the NGINX worker user (www-data).

  • Fix: I allowed directory traversal with:

      chmod +x /home/amrit
    
  • After that, all sites loaded properly.


Conclusion

This simple exercise demonstrates how virtual hosting in NGINX works using the server_name directive. Instead of different ports, we can use different domain names and serve multiple sites from the same server. This is the exact same mechanism used in production hosting providers.

2.Host-Based NGINX Load Balancer for Multiple Node.js Docker Containers


Introduction

  1. Dockerize a simple Node.js application.

  2. Run multiple containers of the same Node app on the same host.

  3. Configure NGINX installed on the host as a Layer 7 load balancer.

  4. Route requests to the containers using round-robin strategy.

This setup is useful for local development, testing, or small-scale deployments without Docker Swarm or Kubernetes.


Step 1: Create Node.js App

Create project structure:

mkdir ~/nodeapp
cd ~/nodeapp
mkdir app

index.js:

const express = require("express");
const app = express();
const os = require("os");
const hostname = os.hostname();

app.get("/", (req, res) => {
    res.send(`Hello from ${hostname}`);
});

app.listen(8080, () => {
    console.log(`Listening on port 8080 on ${hostname}`);
});

package.json:

{
  "name": "nodeapp",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "express": "^4.18.2"
  }
}

Step 2: Dockerize Node.js App

Dockerfile:

FROM node:18
WORKDIR /home/node/app
COPY app /home/node/app
RUN npm install
EXPOSE 8080
CMD ["node", "index.js"]

Build the Docker image:

docker build -t nodeapp .

Step 3: Run Multiple Containers

Map internal container port (8080) to unique host ports:

docker run -d -p 3001:8080 --name nodeapp1 nodeapp
docker run -d -p 3002:8080 --name nodeapp2 nodeapp
docker run -d -p 3003:8080 --name nodeapp3 nodeapp

Step 4: Configure Host-Based NGINX

nginx.conf:

worker_processes auto;

events {}

http {

    # 1. Upstream block → defines backend servers and load balancing method
    upstream nodebackend {
        # Default: round-robin
        server 127.0.0.1:3001;
        server 127.0.0.1:3002;
        server 127.0.0.1:3003;

        # Optional load-balancing methods:
        # least_conn;       # sends request to server with least active connections
        # ip_hash;          # sticky session based on client IP
    }

    # 2. Server block → defines how requests are handled and proxied
    server {
        listen 80;
        server_name api.amritpoudel.com;

        location / {
            proxy_pass http://nodebackend;   # points to upstream group
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

Explanation

  • worker_processes auto; → NGINX will create one worker per CPU core.

  • events {} → required block for NGINX, handles connections.

  • upstream nodebackend { ... } → defines backend servers for load balancing.

  • server_name api.amritpoudel.com; → routes requests based on hostname.

  • proxy_pass http://nodebackend; → forwards requests to upstream backend.


Step 5: Update Hosts File

For local testing:

127.0.0.1 api.amritpoudel.com
or different ip if hosted on another server and trying to get data from local pc
  • Ensures browser/curl can resolve the domain locally.

Step 6: Test and Reload NGINX

  1. Test NGINX configuration:
sudo nginx -t
  1. Reload NGINX:
sudo systemctl reload nginx
  1. Test:
curl http://api.amritpoudel.com
  • Each request rotates between nodeapp1, nodeapp2, and nodeapp3 (round-robin).

  • Browser can also access http://api.amritpoudel.com if the hosts file is updated.


Step 7: Notes & Key Learnings

  1. Load Balancing Methods

    • Default: round-robin.

    • Other options: least_conn, ip_hash.

  2. Port Mapping

    • Containers internal ports (8080) → exposed to host ports (3001, 3002, 3003).

    • Host-based NGINX connects to exposed host ports.

  3. Practical Tip

    • Use unique host ports to avoid conflicts.

    • For production: consider Docker Swarm, Kubernetes, or overlay networks to avoid manual port mapping.


Result:

  • Node.js app is containerized.

  • Multiple containers run on host with different ports.

  • Host-based NGINX load balances requests to containers.

  • Fully testable via curl and browser.

🔧 Core Parameters (performance, concurrency, scaling)

  1. worker_processes

    • Default: 1

    • Best: auto → spawns as many workers as CPU cores.

    • Each worker handles thousands of concurrent connections (via event-driven model).

  2. worker_connections

    • Inside events {} block.

    • Defines max number of concurrent connections per worker.

    • Example:

        events {
            worker_connections 4096;
        }
      
    • Total concurrent connections ≈ worker_processes × worker_connections.

  3. multi_accept

    • Default: off.

    • If on, worker accepts as many new connections as possible in one loop iteration.

    • Useful for high-load systems.


🔧 HTTP Timeouts (control slow clients, DoS protection)

  1. client_header_timeout

    • Max time to receive headers.

    • Protects against slowloris attacks.

    • Example: client_header_timeout 10s;

  2. client_body_timeout

    • Max time to receive request body.

    • Example: client_body_timeout 15s;

  3. send_timeout

    • Max time between two writes to client.

    • Example: send_timeout 30s;

  4. lingering_timeout

    • Time to wait for extra body data after response is sent.

    • Example: lingering_timeout 5s;


🔧 Buffering & Request Size

  1. client_max_body_size

    • Max allowed upload size.

    • Example: client_max_body_size 50M;

    • Very common in API/file-upload servers.

  2. client_body_buffer_size

    • Buffer for client body in memory. If bigger → written to disk (slower).
  3. proxy_buffer_size, proxy_buffers, proxy_busy_buffers_size

  • Control buffering of responses from upstream.

  • Helps with large headers or responses.


🔧 Proxy Parameters (when reverse proxying)

  1. proxy_connect_timeout
  • Timeout for connecting to upstream.

  • Example: proxy_connect_timeout 5s;

  1. proxy_read_timeout
  • Timeout for reading response from upstream.

  • Example: proxy_read_timeout 30s;

  1. proxy_send_timeout
  • Timeout for sending request to upstream.
  1. proxy_set_header
  • Pass important headers to upstream:

      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-Proto $scheme;
    

🔧 Security & Hardening

  1. server_tokens off;
  • Hide NGINX version in error pages & headers.
  1. Rate limiting (limit_req, limit_conn)
  • Prevent brute force or abuse.

  • Example:

      limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    
  1. SSL/TLS settings (if HTTPS is enabled).
  • Cipher suites, HSTS, OCSP stapling, etc.

🔧 Logging

  1. access_log & error_log
  • Control what is logged and at what level (warn, error, info).