What Is Nginx?
Igor Sysoev created Nginx in 2004 to solve the "C10K problem" — how to handle 10,000 concurrent connections efficiently. Unlike Apache's process-per-connection model, Nginx uses an event-driven, asynchronous architecture that handles many connections with minimal memory.
Common Nginx use cases:
- Web server: Serving static files (HTML, CSS, JS, images) extremely efficiently
- Reverse proxy: Forwarding requests to backend application servers (Node.js, Python, Ruby)
- Load balancer: Distributing traffic across multiple backend servers
- SSL termination: Handling HTTPS at the proxy layer, freeing backends
- HTTP cache: Caching responses to reduce backend load
- API gateway: Rate limiting, authentication, routing
Installation
# Ubuntu/Debian
sudo apt update && sudo apt install nginx
sudo systemctl enable nginx
sudo systemctl start nginx
# CentOS/RHEL
sudo yum install nginx
sudo systemctl enable --now nginx
# macOS
brew install nginx
brew services start nginx
Check that it's running by visiting http://localhost — you should see the default Nginx welcome page.
Configuration Structure
Nginx's main configuration lives at /etc/nginx/nginx.conf. The standard layout:
/etc/nginx/
├── nginx.conf # Main config (includes others)
├── sites-available/ # Virtual host configs (Ubuntu/Debian convention)
│ ├── default
│ └── myapp.conf
├── sites-enabled/ # Symlinks to active configs
│ └── myapp.conf -> ../sites-available/myapp.conf
├── conf.d/ # Additional configs (CentOS/RHEL convention)
├── snippets/ # Reusable config fragments
│ └── ssl-params.conf
└── mime.types
The main nginx.conf:
user www-data;
worker_processes auto; # One worker per CPU core
pid /run/nginx.pid;
events {
worker_connections 1024; # Max connections per worker
multi_accept on;
}
http {
# Basic settings
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 256;
# Include virtual host configs
include /etc/nginx/sites-enabled/*;
}
Serving Static Files
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Block hidden files
location ~ /\. {
deny all;
}
}
try_files $uri $uri/ =404 tells Nginx to:
- Try to serve the requested URI as a file
- Try to serve it as a directory (looking for index.html inside)
- Return 404 if neither exists
Reverse Proxy Configuration
The most common Nginx use case in modern development — forwarding requests to an app server:
upstream nodejs_app {
server 127.0.0.1:3000;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://nodejs_app;
# Pass important headers to the 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;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
The X-Forwarded-For header passes the real client IP to your app (instead of Nginx's IP). The X-Forwarded-Proto tells your app whether the original request was HTTP or HTTPS.
SSL/TLS with Let's Encrypt
Obtain a free SSL certificate with Certbot:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
# Certbot automatically modifies your Nginx config
The resulting SSL configuration:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri; # Redirect HTTP to HTTPS
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
# Modern SSL settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# HSTS (Strict Transport Security)
add_header Strict-Transport-Security "max-age=63072000" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
root /var/www/example.com;
# ... rest of config
}
Load Balancing
Distribute traffic across multiple backend servers:
upstream backend {
# Strategies: round-robin (default), least_conn, ip_hash, weight
least_conn;
server 10.0.0.1:3000 weight=3; # Gets 3x more traffic
server 10.0.0.2:3000;
server 10.0.0.3:3000 backup; # Only used if others are down
# Health check (Nginx Plus feature; use nginx_upstream_check for open source)
keepalive 32; # Keep connections alive
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
Load balancing strategies:
- round-robin (default): Requests distributed sequentially
- least_conn: Route to server with fewest active connections
- ip_hash: Same client always goes to same server (sticky sessions)
- weight: Servers with higher weight receive more requests
Rate Limiting
Protect your application from abuse and DDoS:
http {
# Define a rate limit zone: 10 req/second per IP
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_conn conn 10; # Max 10 concurrent connections per IP
proxy_pass http://backend;
}
}
}
burst=20 allows a burst of up to 20 requests before rate limiting kicks in. nodelay processes the burst immediately rather than spreading it over time.
Useful Nginx Commands
nginx -t # Test configuration for syntax errors
nginx -T # Print entire config after parsing
nginx -s reload # Reload config without downtime
nginx -s stop # Fast shutdown
systemctl reload nginx # Graceful reload (preferred)
tail -f /var/log/nginx/access.log # Watch access logs
tail -f /var/log/nginx/error.log # Watch error logs
Always run nginx -t before reloading to catch syntax errors.
Performance Tuning
Key settings for high-traffic sites:
# In nginx.conf
worker_processes auto; # Match CPU count
worker_rlimit_nofile 65535; # Max file descriptors per worker
events {
worker_connections 4096; # Connections per worker
multi_accept on;
use epoll; # Best event model on Linux
}
http {
# Enable sendfile for static files
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Connection keep-alive
keepalive_timeout 75s;
keepalive_requests 1000;
# Buffer tuning
client_body_buffer_size 128k;
client_max_body_size 10m;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
# Open file cache
open_file_cache max=10000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
}
Conclusion
Nginx's combination of raw performance, flexible configuration, and wide feature set makes it the web server of choice for production workloads. Whether you're serving a simple static site, reverse proxying a Node.js API, or load balancing a distributed system, mastering Nginx configuration pays dividends in performance, security, and reliability.