What is Nginx? #
Every time you visit a website — opening a page, clicking a link, or watching a video — there is a program working behind the scenes to receive your request and send back the right response. That program is called a web server. Nginx (pronounced “engine-x”) is one of the most popular web servers in the world, and also one of the most misunderstood pieces of infrastructure software. This article builds the foundation you need: what Nginx actually is, what it can and cannot do, and why it has become the dominant choice across the industry.
Definition and Core Role #
Nginx is open-source software that, at its heart, does one thing: accept incoming network connections, process HTTP requests, and send back responses. Conceptually that simple — but the implementation of that “simple” idea turns out to hold a great deal of depth.
Nginx can fill several roles at once, depending on how you configure it:
flowchart TD
subgraph NGX["NGINX Traffic Processing Platform"]
direction TB
WS["Web Server<br/>(Serves static files: HTML, CSS, JS, Images)"]
RP["Reverse Proxy<br/>(Forwards requests to internal backend servers)"]
LB["Load Balancer<br/>(Distributes traffic across a server cluster)"]
HC["HTTP Cache<br/>(Stores responses to spare the backend)"]
ST["SSL Terminator<br/>(Handles HTTPS handshake & encryption)"]
end
style NGX stroke:#0288d1,stroke-width:3px
style WS stroke:#8e24aa,stroke-width:1.5px
style RP stroke:#8e24aa,stroke-width:1.5px
style LB stroke:#8e24aa,stroke-width:1.5px
style HC stroke:#8e24aa,stroke-width:1.5px
style ST stroke:#8e24aa,stroke-width:1.5pxWhat makes Nginx special is not just its ability to play all of the roles above — it’s its ability to do all of them simultaneously, with remarkably low memory usage, even under very heavy traffic.
How Nginx Works — A High-Level View #
Before diving into technical detail, it helps to understand the big picture of how Nginx operates. When someone visits https://example.com/about, here is what happens:
sequenceDiagram
autonumber
participant Klien as User's Browser
participant DNS as DNS Server
participant NGX as Nginx Server
participant Backend as Application Server (NodeJS/PHP)
participant Disk as Local Asset Disk
Klien->>DNS: 1. DNS Lookup (Get example.com IP)
DNS-->>Klien: Return Server IP
Klien->>NGX: 2. Send TCP Request (Port 443)
Note over NGX: 3. Decrypt TLS & Parse HTTP Request
Note over NGX: 4. Match server_name & location block
alt Scenario A: Static File Request
NGX->>Disk: Request static file (/var/www/html/about.html)
Disk-->>NGX: Send file contents
NGX-->>Klien: Send HTTP Response (200 OK + Payload)
else Scenario B: Dynamic Content Request
NGX->>Backend: proxy_pass http://localhost:3000 (Internal Socket)
Backend-->>NGX: App Response (JSON / Dynamic HTML)
NGX-->>Klien: Send HTTP Response (200 OK + Payload)
endThis whole process happens in milliseconds, and Nginx can run thousands of these processes concurrently thanks to the concurrency model covered in the Architecture article.
Nginx as a Web Server #
In its most basic role as a web server, Nginx serves static files — HTML, CSS, JavaScript, images, videos, fonts, and other files stored on disk.
When you visit a simple website page, this is what happens on the Nginx side:
# Simplest configuration: serving static files
server {
listen 80;
server_name example.com;
root /var/www/html;
location / {
# Nginx looks for the file in /var/www/html matching the URL path
# GET /about → look for /var/www/html/about (or about.html, about/index.html)
try_files $uri $uri/ =404;
}
}
Nginx is extremely efficient at serving static files because it uses an optimized kernel system call — sendfile() on Linux — that ships the file straight from disk to the network socket without copying the data into user space first. This is called “zero-copy” and it’s one of the reasons Nginx’s static-file performance is so high.
flowchart TD
subgraph Konvensional["Conventional Process (Without sendfile)"]
direction TB
D1[("Disk")] -->|"Copy 1"| K1["Kernel Read Buffer"]
K1 -->|"Copy 2"| U1["User Space (Nginx)"]
U1 -->|"Copy 3"| K2["Kernel Socket Buffer"]
K2 -->|"Copy 4"| N1["Network Interface Card"]
end
subgraph ZeroCopy["Nginx Zero-Copy Process (With sendfile)"]
direction TB
D2[("Disk")] -->|"Copy 1"| K3["Kernel Read Buffer"]
K3 -->|"Direct Kernel Transfer"| N2["Network Interface Card"]
end
style D1 stroke:#d32f2f,stroke-width:1.5px
style D2 stroke:#388e3c,stroke-width:1.5px
style U1 stroke:#d32f2f,stroke-width:1.5px
style K3 stroke:#388e3c,stroke-width:1.5pxThe result: 2-3x fewer data copies in RAM and much lower CPU usage.
Nginx as a Reverse Proxy #
Modern web applications — Node.js, Django, Laravel, Rails — are not designed to face the internet directly. They run on internal ports (say, 3000, 8000, 8080) and don’t handle HTTPS, rate limiting, or caching efficiently. This is where Nginx steps in as a reverse proxy.
The word “reverse” distinguishes it from a “forward proxy” (like a VPN or an office proxy you use to reach the internet). In a reverse proxy setup, Nginx stands in front of the server — not in front of the client.
flowchart LR
subgraph FP["Forward Proxy (Represents the Client)"]
direction LR
K1["Client"] --> FProxy["Forward Proxy / VPN"]
FProxy --> Internet["Internet / Public Server"]
end
subgraph RP["Reverse Proxy (Represents the Server)"]
direction LR
Internet2["Internet / Public"] --> RProxy["Nginx (Reverse Proxy)"]
RProxy --> Backend["Internal Server (Port 3000)"]
end
style FProxy stroke:#8e24aa,stroke-width:2px
style RProxy stroke:#0288d1,stroke-width:3pxAs a reverse proxy, Nginx accepts every request from the internet and forwards it to one or more backend servers:
server {
listen 80;
server_name api.example.com;
location / {
# Forward all requests to the Node.js app on port 3000
proxy_pass http://localhost:3000;
# Add headers so the backend knows the client's real IP
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
}
}
This architecture pays off in many ways: the backend doesn’t have to deal with SSL, a single Nginx server can serve many applications on different ports, and Nginx can cache responses so the same request doesn’t always have to hit the backend.
Nginx as a Load Balancer #
When a single backend server isn’t enough to handle the traffic, you run several instances. Nginx then acts as a load balancer — spreading requests evenly across all available instances.
upstream backend_app {
server 192.168.1.10:3000; # Server 1
server 192.168.1.11:3000; # Server 2
server 192.168.1.12:3000; # Server 3
}
server {
listen 80;
location / {
proxy_pass http://backend_app;
# Nginx automatically distributes requests
# to Server 1, 2, 3 in turn (round-robin)
}
}
Nginx supports several load balancing algorithms — round-robin, least connections, IP hash — covered in full in Section 06.
Nginx as an SSL Terminator #
HTTPS requires computationally intensive encryption and decryption. Instead of every backend server managing its own SSL certificate, Nginx handles all of it in one place.
flowchart TD
subgraph TanpaTerm["Without SSL Termination (Heavy CPU Load on Backends)"]
direction TB
C1["Client"] -->|"HTTPS (Port 443)"| B1["NodeJS App (Encryption/Decryption)"]
C2["Client"] -->|"HTTPS (Port 443)"| B2["PHP App (Encryption/Decryption)"]
end
subgraph DenganTerm["With SSL Termination at Nginx (Optimal)"]
direction TB
C3["Client"] -->|"HTTPS (Encrypted Traffic)"| NGX["Nginx SSL Terminator"]
NGX -->|"HTTP (Light & Fast Traffic)"| B3["NodeJS App"]
NGX -->|"HTTP (Light & Fast Traffic)"| B4["PHP App"]
end
style NGX stroke:#0288d1,stroke-width:2.5px
style B1 stroke:#d32f2f,stroke-width:1.5px
style B2 stroke:#d32f2f,stroke-width:1.5px
style B3 stroke:#388e3c,stroke-width:1.5px
style B4 stroke:#388e3c,stroke-width:1.5pxThe configuration looks like this:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
# The backend receives plain HTTP; Nginx handles HTTPS
proxy_pass http://localhost:3000;
}
}
What Nginx Is Not #
Understanding Nginx’s limits matters just as much as understanding its capabilities.
- Nginx is not an application server: Nginx cannot run PHP, Python, or JavaScript code directly. It can only forward requests to the process that runs that code. For PHP you need PHP-FPM. For Node.js you need a separately running Node process.
ANTI-PATTERN (common misconception): "Nginx processes index.php files all on its own" CORRECT: Nginx receives the request → forwards it to the PHP-FPM socket → PHP-FPM runs the PHP code → PHP-FPM returns the response to Nginx → Nginx sends it to the client's browser - Nginx is not a database: Nginx does not store application data. The caching Nginx performs is HTTP response caching — not business data.
- Nginx is not a firewall: Even though Nginx can do IP restriction and rate limiting, it is not a replacement for a proper network firewall (iptables, UFW, or cloud solutions like Security Groups).
- Nginx is not a CDN substitute for global distribution: Nginx runs in a single location. To distribute content worldwide with low latency, you still need a CDN (Cloudflare, Fastly, AWS CloudFront).
Nginx in the Modern Infrastructure Stack #
In real deployments, Nginx rarely stands alone. It is one component in a larger stack:
flowchart TD
Klien["Internet / External Clients"] -->|"Port 80/443"| CDN["CDN Layer (Cloudflare / Fastly)"]
CDN -->|"Filtered Traffic"| NGX["Nginx Edge Server"]
subgraph Edge["Edge Services (Nginx)"]
NGX -->|"SSL Termination"| SSL["HTTPS Block"]
NGX -->|"Rate Limiting"| RL["IP Rate Zone"]
NGX -->|"Serving File"| SF["Static Files Folder"]
end
NGX -->|"proxy_pass"| App1["App Server 1 (Node.js)"]
NGX -->|"proxy_pass"| App2["App Server 2 (Go App)"]
App1 --> DB[("Database Cluster (PostgreSQL)")]
App2 --> DB
App1 --> Cache[("Cache Memory (Redis)")]
App2 --> Cache
style NGX stroke:#0288d1,stroke-width:3px
style CDN stroke:#ffb300,stroke-width:2px
style DB stroke:#43a047,stroke-width:2px
style Cache stroke:#e53935,stroke-width:2pxNginx sits in a critical position: between the outside world and your internal infrastructure. This position gives it full visibility over all incoming traffic, along with the responsibility of making sure every request reaches the right place safely and efficiently.
Nginx Open Source vs Nginx Plus #
There are two versions of Nginx you should know about:
- Nginx Open Source (nginx.org) is the free version available in every Linux package manager. It’s what most installations use, and it’s what this entire book covers.
- Nginx Plus (nginx.com) is the commercial version with extra features: more advanced active health checks, a live activity monitoring dashboard, built-in JWT authentication, and official support from the Nginx team. It costs around $2,500/year per instance.
| Key Feature | Nginx Open Source | Nginx Plus |
|---|---|---|
| Web Server & Reverse Proxy | ✓ | ✓ |
| Load Balancing & SSL/TLS | ✓ | ✓ |
| Passive Health Check | ✓ | ✓ |
| Active Health Check | ✗ | ✓ |
| Live Activity Dashboard | ✗ | ✓ |
| JWT Authentication (Native) | ✗ | ✓ |
| Official Technical Support (SLA) | ✗ | ✓ |
| Price | Free (Open Source) | ~$2,500 / instance / year |
For the vast majority of use cases — including medium-scale production deployments — Nginx Open Source is more than enough.
Nginx as an HTTP Cache #
Beyond the roles above, Nginx also works as a powerful HTTP cache. Caching here means storing responses from the backend and serving identical requests from the cache instead of hitting the backend every time.
Imagine a news page visited 10,000 times in one minute, whose content only changes every hour. Without caching, the backend would have to respond 10,000 times — building HTML, querying the database, and so on. With Nginx caching, the backend only needs to respond once, and the other 9,999 requests are served straight from Nginx’s cache.
# Basic proxy cache configuration
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=app_cache:10m
max_size=1g
inactive=60m;
server {
listen 80;
server_name news.example.com;
location / {
proxy_cache app_cache;
proxy_cache_valid 200 1h; # Cache 200 OK responses for 1 hour
proxy_cache_valid 404 1m; # Cache 404s for 1 minute
proxy_pass http://backend_app;
# Add a header for debugging: hit or miss
add_header X-Cache-Status $upstream_cache_status;
}
}
This scenario can cut backend load dramatically — by up to 99% if the content is stable and traffic is high. This caching feature is why Nginx is often positioned as the first line of defense in front of the backend.
flowchart TD
subgraph Request1["1. First Request (Cache Miss)"]
direction LR
K1["Client Browser"] -->|"HTTP GET"| N1["Nginx Proxy"]
N1 -->|"Cache MISS"| B1["Backend Application"]
B1 -->|"200 OK Response"| N1
N1 -->|"Save to Disk Cache"| Disk["Disk Storage"]
N1 -->|"Send Response"| K1
end
subgraph RequestNext["2. Next Request (Cache Hit)"]
direction LR
K2["Client Browser"] -->|"HTTP GET"| N2["Nginx Proxy"]
N2 -->|"Cache HIT (Fast)"| Disk2["Disk Storage"]
Disk2 -->|"Send Asset"| N2
N2 -->|"Send Response"| K2
end
style N1 stroke:#0288d1,stroke-width:2px
style N2 stroke:#0288d1,stroke-width:2px
style B1 stroke:#8e24aa,stroke-width:2pxNginx as a Mail Proxy #
This is a lesser-known feature: Nginx also supports email protocols — SMTP, IMAP, and POP3 — as a proxy. While not as popular as its HTTP features, it’s useful for large-scale email infrastructure that needs to route mail to various backend mail servers.
mail {
server_name mail.example.com;
auth_http http://localhost:8080/auth; # Authentication via HTTP
server {
listen 143; # IMAP
protocol imap;
starttls on;
}
server {
listen 25; # SMTP
protocol smtp;
starttls on;
}
}
This mail proxy feature is rarely used in ordinary web deployments, but it shows just how flexible Nginx is as a proxy layer.
Anatomy of an Nginx Installation #
When you install Nginx on a Linux system, several important directories and files are created:
Nginx File Structure (Ubuntu/Debian):
/etc/nginx/
├── nginx.conf # Main configuration file
├── conf.d/ # Additional configs (*.conf are included)
│ └── default.conf
├── sites-available/ # Available virtual hosts (not yet active)
│ └── default
├── sites-enabled/ # Symlinks to active sites-available entries
│ └── default → ../sites-available/default
├── mime.types # Maps file extensions to MIME types
├── fastcgi_params # FastCGI parameters for PHP integration
├── proxy_params # Default HTTP proxy forwarding parameters
├── snippets/ # Modular configurations that can be included
│ ├── fastcgi-php.conf
│ └── snakeoil.conf
└── modules-enabled/ # Symlinks to enabled dynamic modules
/var/log/nginx/
├── access.log # Logs every incoming HTTP request
└── error.log # Logs engine errors and warnings
/var/www/html/ # Default root directory for web assets
└── index.html
/usr/lib/nginx/ # Nginx binaries and module libraries
/var/cache/nginx/ # Cache storage (if enabled)
Understanding this structure matters because almost every Nginx operation — adding a virtual host, enabling SSL, configuring cache — revolves around these files and directories.
Inside Nginx: Looking at the Processes #
Once Nginx is running, you can see its processes in the task manager:
# See the running Nginx processes on the server
ps aux | grep nginx
# Output (server with 4 CPU cores):
# root 12345 0.0 0.0 nginx: master process /usr/sbin/nginx
# www-data 12346 0.1 0.5 nginx: worker process
# www-data 12347 0.1 0.5 nginx: worker process
# www-data 12348 0.1 0.5 nginx: worker process
# www-data 12349 0.1 0.5 nginx: worker process
# The master process runs as root (to bind ports 80/443)
# Worker processes run as www-data (non-privileged/safe user)
Notice there is exactly one master process and a few worker processes (usually matching the number of CPU cores). This contrasts with Apache, which can have dozens or hundreds of processes.
# Reload the configuration without downtime (zero-downtime)
sudo nginx -s reload
# Test the configuration before reloading (very important!)
sudo nginx -t
# Output if valid:
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
# Stop gracefully (wait for in-flight requests to finish)
sudo nginx -s quit
# Stop immediately (force)
sudo nginx -s stop
Nginx vs Modern Alternatives #
Besides Apache, there are several modern web servers and reverse proxies often compared with Nginx:
| Web Server Category | Key Characteristics | Ideal Use Case |
|---|---|---|
| Caddy | Auto-HTTPS, written in Go, minimal Caddyfile config | Small projects, fast deployment, independent developers |
| Traefik | Cloud-native, dynamic auto-discovery based on container labels | Docker Swarm and Kubernetes microservices clusters |
| HAProxy | Pure TCP/HTTP load balancer, very high load balancing performance | Pure large-scale traffic load balancing without static serving |
| Envoy | Designed specifically for service mesh, Istio’s data plane proxy | Highly complex enterprise microservices architectures |
| Nginx | Battle-tested, huge ecosystem, generalist with balanced performance | Default web server, reverse proxy, and universal SSL terminator |
Nginx remains the default choice for most web deployments because of its unique combination of traits: mature, thoroughly documented, high performance, and extremely flexible.
Why Nginx Is So Popular #
Nginx’s popularity is no accident. There are concrete reasons it dominates the web server market:
- Performance under heavy load: Nginx was designed from the ground up to handle tens of thousands of concurrent connections with predictable memory usage. This differs from Apache, whose memory consumption grows linearly with the number of connections.
- Expressive configuration: Nginx config files use a directive-and-block syntax that can describe complex routing logic in a relatively readable way.
- A mature ecosystem: After more than 20 years, almost every problem you could run into has been experienced by someone else. Extensive documentation, tutorials, and a large community make finding solutions easy.
- Low and stable memory usage: A single Nginx worker process typically uses around 2-3 MB of memory. A server with 1 GB of RAM can handle thousands of concurrent connections without worrying about running out of memory.
- Broad cloud platform support: All major cloud platforms — AWS, GCP, Azure — ship official images and documentation for Nginx. Kubernetes also uses Nginx as its most popular default Ingress Controller.
Nginx in Practice: Real-World Configuration Examples #
To give you a more concrete picture, here are the most frequently encountered configuration scenarios:
Scenario 1: A Simple Static Website #
Great for landing pages, documentation, or portfolios:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html index.htm;
# Enable gzip to shrink transfer sizes
gzip on;
gzip_types text/html text/css application/javascript image/svg+xml;
location / {
try_files $uri $uri/ =404;
}
# Cache static files in the client's browser
location ~* \.(css|js|png|jpg|gif|ico|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Logs
access_log /var/log/nginx/example.com-access.log;
error_log /var/log/nginx/example.com-error.log;
}
Scenario 2: An API with a Node.js Backend #
The most common setup in modern startups:
upstream nodejs_backend {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# Rate limiting: 100 requests per second per IP
limit_req zone=api_limit burst=200 nodelay;
location / {
proxy_pass http://nodejs_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
}
}
Scenario 3: Multiple Applications on One Server #
One server, several different apps accessed via subdomains:
# App 1: Main website (PHP-FPM)
server {
listen 80;
server_name example.com www.example.com;
root /var/www/main;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include fastcgi_params;
}
}
# App 2: API (Node.js port 3000)
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
}
}
# App 3: Admin panel (Python/Django port 8000)
server {
listen 80;
server_name admin.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
# Restrict access to internal IPs only
allow 10.0.0.0/8;
deny all;
}
}
With Nginx, all of this can run on a single physical server with a clean, well-structured configuration.
Monitoring Nginx with Popular Tools #
In production, you need to monitor Nginx actively. Here are some commonly used tools:
Prometheus + Grafana #
flowchart LR
NGX["Nginx Web Server"] -->|"Metrics Export"| PE["Prometheus Exporter"]
PE -->|"Pull Scraping"| PM["Prometheus Server"]
PM -->|"Query & Visualization"| GF["Grafana Dashboard"]
style NGX stroke:#0288d1,stroke-width:2px
style PM stroke:#e53935,stroke-width:2px
style GF stroke:#ffb300,stroke-width:2px- Metrics to watch: Request rate (req/sec), Error rate (5xx, 4xx), Active connections, Request duration (p50, p95, p99), Upstream response time, and Cache hit rate.
GoAccess: Real-time Log Analysis #
# Analyze Nginx logs in real time straight from the terminal
goaccess /var/log/nginx/access.log --log-format=COMBINED
# Or for a real-time HTML web interface:
tail -f /var/log/nginx/access.log | goaccess -a -o /var/www/html/report.html --real-time-html
Nginx Security by Default #
Nginx is relatively secure out of the box, but there are a few extra configurations recommended for production:
server {
# Hide the Nginx version from the Server response header
server_tokens off;
# Add security headers
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
# Limit request body size (default: 1m)
client_max_body_size 10m;
# Limit timeouts so slow/idle connections don't pile up
client_header_timeout 10;
client_body_timeout 10;
keepalive_timeout 30;
send_timeout 10;
}
More detailed security configuration is covered in Section 08 — Security.
Summary #
- Nginx is both a web server and a reverse proxy — it can serve static files straight from disk and forward requests to application backends.
- Nginx’s main roles: web server, reverse proxy, load balancer, SSL terminator, and HTTP cache.
- Nginx is not an application server — it doesn’t run PHP, Python, or JavaScript. It only forwards requests to the processes that do.
- Zero-copy with sendfile() makes Nginx extremely efficient at serving static files.
- SSL Termination lets backends run over internal HTTP while Nginx handles HTTPS encryption.
- Nginx Open Source is enough for most production needs — Nginx Plus is only required for certain enterprise features.
- Nginx’s position in the stack: between the internet and the backend, giving it full visibility and control over all incoming traffic.