All you need to know about Nginx
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/amritdirectory was not accessible to the NGINX worker user (www-data).Fix: I allowed directory traversal with:
chmod +x /home/amritAfter 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
Dockerize a simple Node.js application.
Run multiple containers of the same Node app on the same host.
Configure NGINX installed on the host as a Layer 7 load balancer.
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
Each container has a different host port.
nodeapp1→localhost:3001nodeapp2→localhost:3002nodeapp3→localhost:3003
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_nameapi.amritpoudel.com;→ routes requests based on hostname.proxy_passhttp://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
- Test NGINX configuration:
sudo nginx -t
- Reload NGINX:
sudo systemctl reload nginx
- Test:
curl http://api.amritpoudel.com
Each request rotates between
nodeapp1,nodeapp2, andnodeapp3(round-robin).Browser can also access
http://api.amritpoudel.comif the hosts file is updated.
Step 7: Notes & Key Learnings
Load Balancing Methods
Default: round-robin.
Other options:
least_conn,ip_hash.
Port Mapping
Containers internal ports (8080) → exposed to host ports (3001, 3002, 3003).
Host-based NGINX connects to exposed host ports.
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
curland browser.
🔧 Core Parameters (performance, concurrency, scaling)
worker_processesDefault:
1Best:
auto→ spawns as many workers as CPU cores.Each worker handles thousands of concurrent connections (via event-driven model).
worker_connectionsInside
events {}block.Defines max number of concurrent connections per worker.
Example:
events { worker_connections 4096; }Total concurrent connections ≈
worker_processes × worker_connections.
multi_acceptDefault: 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)
client_header_timeoutMax time to receive headers.
Protects against slowloris attacks.
Example:
client_header_timeout 10s;
client_body_timeoutMax time to receive request body.
Example:
client_body_timeout 15s;
send_timeoutMax time between two writes to client.
Example:
send_timeout 30s;
lingering_timeoutTime to wait for extra body data after response is sent.
Example:
lingering_timeout 5s;
🔧 Buffering & Request Size
client_max_body_sizeMax allowed upload size.
Example:
client_max_body_size 50M;Very common in API/file-upload servers.
client_body_buffer_size- Buffer for client body in memory. If bigger → written to disk (slower).
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)
proxy_connect_timeout
Timeout for connecting to upstream.
Example:
proxy_connect_timeout 5s;
proxy_read_timeout
Timeout for reading response from upstream.
Example:
proxy_read_timeout 30s;
proxy_send_timeout
- Timeout for sending request to upstream.
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
server_tokens off;
- Hide NGINX version in error pages & headers.
- Rate limiting (
limit_req,limit_conn)
Prevent brute force or abuse.
Example:
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
- SSL/TLS settings (if HTTPS is enabled).
- Cipher suites, HSTS, OCSP stapling, etc.
🔧 Logging
access_log&error_log
- Control what is logged and at what level (
warn,error,info).