The Nginx Handbook: A Complete Guide to the World's Most Popular Web Server

The Nginx Handbook: A Complete Guide to the World's Most Popular Web Server

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:

  1. Try to serve the requested URI as a file
  2. Try to serve it as a directory (looking for index.html inside)
  3. 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.

Share: