Available for day contractsFrom 21st September I have availability for day and half day contracts. Please contact for more information.

Contact →
mikepreston.org

Nginx

High-performance HTTP server and reverse proxy with load balancing, caching, and SSL termination capabilities.

Nginx

High-performance HTTP server and reverse proxy with load balancing, caching, and SSL termination capabilities.

Overview

Nginx (pronounced "engine-x") is an open-source web server that excels at serving static content, acting as a reverse proxy, and load balancing across multiple backend servers. Its event-driven, asynchronous architecture allows it to handle thousands of concurrent connections with minimal memory footprint. Nginx processes requests using a master-worker model where the master process manages configuration and worker processes handle actual client connections.

Nginx Request FlowStaticDynamicClient RequestNginx MasterWorker ProcessRequest TypeFile SystemUpstream ServerResponseNginx Request FlowStaticDynamicClient RequestNginx MasterWorker ProcessRequest TypeFile SystemUpstream ServerResponse

Configuration Structure

Nginx configuration uses a hierarchical structure with contexts that inherit and override settings from parent blocks.

Key Concepts

  • Main Context - Global settings affecting the entire Nginx process
  • Events Context - Connection handling configuration
  • HTTP Context - Web server configuration container
  • Server Block - Virtual host configuration (similar to Apache VirtualHost)
  • Location Block - URI-based request handling rules
  • Upstream Block - Backend server pool definitions
  • Directives - Configuration instructions (simple or block)
nginx.confMain Contextevents { }http { }server { }upstream { }location / { }location /api { }location ~* \.php$ {}nginx.confMain Contextevents { }http { }server { }upstream { }location / { }location /api { }location ~* \.php$ {}

Common Patterns

# Main configuration file structure
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;

events {
    worker_connections 1024;
    multi_accept on;
    use epoll;
}

http {
    # MIME types
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Logging format
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    # Performance settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript;

    # Include additional configurations
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

# Server block example
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    root /var/www/html;
    index index.html index.htm;

    # Exact match location
    location = / {
        try_files /index.html =404;
    }

    # Prefix match location
    location /images/ {
        alias /var/www/images/;
        expires 30d;
    }

    # Regular expression location (case-insensitive)
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Preferential prefix match
    location ^~ /static/ {
        alias /var/www/static/;
    }
}

Default Server Selection

For each listen address and port, nginx chooses the server block in this order:

  1. the block whose server_name matches the request's Host header;
  2. failing that, the block whose listen carries default_server;
  3. failing that, the first server block defined for that address and port.

Rule 3 is the one that bites. Includes are expanded in glob order, so with include /etc/nginx/conf.d/*.conf the site that answers for every hostname you do not serve — scanners hitting the bare IP, stale DNS, someone else's domain pointed at your server — is whichever filename sorts first. Numbering the files 00-default.conf works, but it is a convention a rename can undo; default_server says it outright.

# A catch-all that answers for everything you do not serve.
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    server_name _;              # matches nothing; a conventional placeholder

    ssl_reject_handshake on;    # 1.19.4+: refuse the TLS handshake, no cert needed
    return 444;                 # close the connection without a response
}

default_server may appear once per address and port; a second is a startup error (a duplicate default server for 0.0.0.0:80). IPv4 and IPv6 are separate listeners, and listen 80 binds IPv4 only, so a site that never declares listen [::]:80 is served by the IPv6 catch-all — which, with the block above, means it is unreachable over IPv6.

# Which block answers for a hostname you do not serve?
curl -sI -H 'Host: nobody.example' http://127.0.0.1/

# Read the whole config as nginx assembled it, includes expanded, in order
nginx -T | grep -n 'server_name\|listen'

Location Block Priority

# Location matching priority (highest to lowest):
# 1. Exact match: location = /path
# 2. Preferential prefix: location ^~ /path
# 3. Regular expression (first match): location ~ or ~*
# 4. Prefix match (longest): location /path

server {
    # 1. Exact match - highest priority
    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    # 2. Preferential prefix - stops regex search
    location ^~ /static/ {
        root /var/www;
    }

    # 3. Case-sensitive regex
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
    }

    # 3. Case-insensitive regex
    location ~* \.(gif|jpg|png)$ {
        expires 30d;
    }

    # 4. Prefix match
    location /documents/ {
        root /var/www;
    }

    # 4. Default prefix match
    location / {
        try_files $uri $uri/ =404;
    }
}

Examples

# Complete virtual host configuration
server {
    listen 80;
    server_name myapp.example.com;

    # Redirect all HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    http2 on;  # Nginx 1.25.1+; on older versions use: listen 443 ssl http2;
    server_name myapp.example.com;

    # SSL configuration
    ssl_certificate /etc/letsencrypt/live/myapp.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myapp.example.com/privkey.pem;

    root /var/www/myapp;
    index index.html;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "0" always;  # Auditor is deprecated; "0" disables it safely

    # Main application
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API proxy
    location /api/ {
        proxy_pass http://localhost:3000/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    # Health check endpoint
    location /health {
        access_log off;
        return 200 "OK\n";
        add_header Content-Type text/plain;
    }

    # Deny access to sensitive files
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

Proxy Configuration

Nginx excels as a reverse proxy, forwarding client requests to backend servers while handling SSL termination, caching, and header manipulation.

Key Concepts

  • proxy_pass - Forwards requests to upstream servers
  • Proxy Headers - Pass client information to backends
  • Buffering - Temporarily store responses before sending to clients
  • Timeouts - Control connection and response timing
  • WebSocket Support - Protocol upgrade handling
/api/auth/staticClientNginx Reverse ProxyRouteAPI Server :3000Auth Server :4000File System/api/auth/staticClientNginx Reverse ProxyRouteAPI Server :3000Auth Server :4000File System

Common Patterns

# Basic reverse proxy
location /api/ {
    proxy_pass http://localhost:3000/;

    # Essential proxy headers
    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_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;
}

# Proxy with path rewriting
location /old-api/ {
    # Strips /old-api and passes remainder to backend
    proxy_pass http://localhost:3000/v2/;
}

# Preserve original path
location /service/ {
    # Passes /service/path to backend as /service/path
    proxy_pass http://localhost:3000;
}

# Timeout configuration
location /api/ {
    proxy_pass http://backend;

    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
    send_timeout 60s;
}

# Buffer configuration
location /api/ {
    proxy_pass http://backend;

    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 4k;
    proxy_busy_buffers_size 8k;
    proxy_max_temp_file_size 1024m;
}

# Disable buffering for streaming
location /stream/ {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
}

# WebSocket proxy
location /ws/ {
    proxy_pass http://websocket_backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 86400;
}

# gRPC proxy
location /grpc/ {
    grpc_pass grpc://localhost:50051;
    error_page 502 = /error502grpc;
}

# Pass request body
location /upload/ {
    proxy_pass http://backend;
    client_max_body_size 100m;
    proxy_request_buffering off;
}

Examples

# Complete reverse proxy with all common settings
upstream api_backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 443 ssl;
    http2 on;
    server_name api.example.com;

    ssl_certificate /etc/ssl/certs/api.example.com.crt;
    ssl_certificate_key /etc/ssl/private/api.example.com.key;

    location / {
        proxy_pass http://api_backend;

        # HTTP version and connection
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        # Client information headers
        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_set_header X-Request-ID $request_id;

        # Timeouts
        proxy_connect_timeout 10s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;

        # Buffering
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 8k;

        # Error handling
        proxy_next_upstream error timeout http_500 http_502 http_503;
        proxy_next_upstream_tries 3;
        proxy_next_upstream_timeout 10s;

        # Hide backend headers
        proxy_hide_header X-Powered-By;
        proxy_hide_header Server;

        # Add custom headers
        add_header X-Cache-Status $upstream_cache_status;
    }
}

# Microservices routing
server {
    listen 80;
    server_name gateway.example.com;

    # User service
    location /users/ {
        proxy_pass http://user-service:8001/;
        proxy_set_header Host $host;
    }

    # Order service
    location /orders/ {
        proxy_pass http://order-service:8002/;
        proxy_set_header Host $host;
    }

    # Product service
    location /products/ {
        proxy_pass http://product-service:8003/;
        proxy_set_header Host $host;
    }

    # Default fallback
    location / {
        return 404 '{"error": "Service not found"}';
        add_header Content-Type application/json;
    }
}

Load Balancing

Nginx distributes incoming requests across multiple backend servers to improve reliability, scalability, and performance.

Key Concepts

  • Upstream Block - Defines a group of backend servers
  • Load Balancing Methods - Algorithms for distributing requests
  • Health Checks - Monitor backend server availability
  • Session Persistence - Sticky sessions for stateful applications
  • Weights - Proportional traffic distribution
Round RobinRound RobinRound RobinClient RequestsNginx Load BalancerLoad BalancingAlgorithmServer 1Server 2Server 3ResponseRound RobinRound RobinRound RobinClient RequestsNginx Load BalancerLoad BalancingAlgorithmServer 1Server 2Server 3Response

Common Patterns

# Basic round-robin (default)
upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

# Weighted round-robin
upstream backend {
    server 192.168.1.10:8080 weight=5;
    server 192.168.1.11:8080 weight=3;
    server 192.168.1.12:8080 weight=2;
}

# Least connections
upstream backend {
    least_conn;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

# IP hash (session persistence)
upstream backend {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

# Generic hash
upstream backend {
    hash $request_uri consistent;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

# Server parameters
upstream backend {
    server 192.168.1.10:8080 weight=5 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 backup;  # Only used when others fail
    server 192.168.1.13:8080 down;    # Marked as unavailable
}

# Keepalive connections to upstream
upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    keepalive 32;
    keepalive_requests 100;
    keepalive_timeout 60s;
}

# Health checks (Nginx Plus or with module)
upstream backend {
    zone backend_zone 64k;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;

    # Passive health check parameters
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
}

# Using upstream in server block
server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Load Balancing Methods Comparison

# Round Robin (default)
# - Requests distributed sequentially
# - Simple and predictable
# - Good for uniform servers
upstream round_robin {
    server backend1.example.com;
    server backend2.example.com;
}

# Least Connections
# - Sends to server with fewest active connections
# - Better for varying request processing times
# - Considers server weights
upstream least_conn_pool {
    least_conn;
    server backend1.example.com weight=3;
    server backend2.example.com weight=1;
}

# IP Hash
# - Same client IP always goes to same server
# - Provides session persistence
# - May cause uneven distribution
upstream ip_hash_pool {
    ip_hash;
    server backend1.example.com;
    server backend2.example.com;
}

# Random with Two Choices
# - Randomly selects two servers
# - Chooses one with fewer connections
# - Good balance of randomness and efficiency
upstream random_pool {
    random two least_conn;
    server backend1.example.com;
    server backend2.example.com;
}

Examples

# Production load balancer configuration
upstream web_cluster {
    least_conn;

    # Primary servers
    server 10.0.1.10:8080 weight=10 max_fails=3 fail_timeout=30s;
    server 10.0.1.11:8080 weight=10 max_fails=3 fail_timeout=30s;
    server 10.0.1.12:8080 weight=10 max_fails=3 fail_timeout=30s;

    # Lower capacity server
    server 10.0.1.13:8080 weight=5 max_fails=3 fail_timeout=30s;

    # Backup server
    server 10.0.2.10:8080 backup;

    # Connection pooling
    keepalive 64;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

upstream api_cluster {
    ip_hash;  # Session persistence for API

    server 10.0.3.10:3000;
    server 10.0.3.11:3000;
    server 10.0.3.12:3000;
}

server {
    listen 443 ssl;
    http2 on;
    server_name app.example.com;

    ssl_certificate /etc/ssl/certs/app.crt;
    ssl_certificate_key /etc/ssl/private/app.key;

    # Web application
    location / {
        proxy_pass http://web_cluster;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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;

        # Retry on failure
        proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
        proxy_next_upstream_tries 3;
    }

    # API with sticky sessions
    location /api/ {
        proxy_pass http://api_cluster/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Health check endpoint
    location /nginx-health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

# Blue-green deployment
upstream blue {
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
}

upstream green {
    server 10.0.2.10:8080;
    server 10.0.2.11:8080;
}

# Map to switch between deployments
map $cookie_deployment $backend {
    default "blue";
    "green" "green";
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://$backend;
    }
}

SSL/TLS Setup

Nginx provides robust SSL/TLS termination with support for modern protocols and security best practices.

Key Concepts

  • SSL Termination - Decrypt HTTPS at the proxy, forward HTTP to backends
  • Certificate Chain - Server certificate plus intermediate certificates
  • Protocol Versions - TLS 1.3 only (modern profile); add TLS 1.2 only if you must support older clients (intermediate profile)
  • Cipher Suites - Encryption algorithms (prefer forward secrecy; TLS 1.3 suites need no tuning)
  • HSTS - Force HTTPS connections

Common Patterns

# Basic SSL configuration (Mozilla modern profile, guideline 6.0)
# Fallback for older clients: the intermediate profile adds TLSv1.2
# plus an explicit ECDHE cipher list
server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    # Modern: TLS 1.3 only; cipher suites are sane by default,
    # so no ssl_ciphers needed
    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers off;

    # SSL session caching
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # Note: OCSP stapling has been dropped from current guidance --
    # Let's Encrypt ended OCSP support in 2025 (CRLs replace it)
}

# HTTP to HTTPS redirect
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

# HSTS (HTTP Strict Transport Security)
server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    # Include subdomains only if all subdomains support HTTPS
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}

# Client certificate authentication
server {
    listen 443 ssl;
    server_name secure.example.com;

    ssl_certificate /etc/ssl/certs/server.crt;
    ssl_certificate_key /etc/ssl/private/server.key;

    # Client certificate verification
    ssl_client_certificate /etc/ssl/certs/ca.crt;
    ssl_verify_client on;
    ssl_verify_depth 2;

    location / {
        # Pass client certificate info to backend
        proxy_set_header X-Client-Cert $ssl_client_cert;
        proxy_set_header X-Client-Verify $ssl_client_verify;
        proxy_pass http://backend;
    }
}

# Let's Encrypt with Certbot
server {
    listen 80;
    server_name example.com;

    # ACME challenge location
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

Examples

# Production SSL configuration with A+ rating
# (Mozilla modern profile, guideline 6.0, nginx 1.27+)
# Fallback: use the intermediate profile (TLSv1.2 + TLSv1.3 with an
# explicit ECDHE cipher list and ssl_dhparam) if you still need to
# support pre-TLS-1.3 clients
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    # Certificate files
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Protocol configuration: TLS 1.3 only; the default TLS 1.3 cipher
    # suites are correct, so no ssl_ciphers directive is required
    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers off;

    # Session configuration
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # OCSP stapling intentionally omitted: removed from current guidance
    # after Let's Encrypt ended OCSP support in 2025 in favour of CRLs

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "0" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;

    root /var/www/html;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

# Intermediate profile only: DHE ciphers need DH parameters
# (use the standard RFC 7919 ffdhe2048 group from ssl-config.mozilla.org)

# SNI (Server Name Indication) for multiple domains
server {
    listen 443 ssl;
    http2 on;
    server_name site1.example.com;
    ssl_certificate /etc/ssl/certs/site1.crt;
    ssl_certificate_key /etc/ssl/private/site1.key;
    root /var/www/site1;
}

server {
    listen 443 ssl;
    http2 on;
    server_name site2.example.com;
    ssl_certificate /etc/ssl/certs/site2.crt;
    ssl_certificate_key /etc/ssl/private/site2.key;
    root /var/www/site2;
}

Caching Strategies

Nginx provides multiple caching mechanisms to improve performance and reduce backend load.

Key Concepts

  • Proxy Cache - Cache responses from upstream servers
  • FastCGI Cache - Cache responses from FastCGI backends
  • Cache Zones - Shared memory for cache keys and metadata
  • Cache Keys - Unique identifiers for cached content
  • Cache Validation - Conditional requests and stale content serving
  • Microcaching - Short-lived caching for dynamic content
YesNoYesNoClient RequestCache Hit?Return CachedResponseForward to BackendBackend ResponseCacheable?Store in CacheReturn ResponseClientYesNoYesNoClient RequestCache Hit?Return CachedResponseForward to BackendBackend ResponseCacheable?Store in CacheReturn ResponseClient

Common Patterns

# Define cache zone in http context
http {
    # Proxy cache path
    proxy_cache_path /var/cache/nginx/proxy
        levels=1:2
        keys_zone=proxy_cache:10m
        max_size=10g
        inactive=60m
        use_temp_path=off;

    # FastCGI cache path
    fastcgi_cache_path /var/cache/nginx/fastcgi
        levels=1:2
        keys_zone=fastcgi_cache:10m
        max_size=1g
        inactive=60m;

    # Cache key definition
    proxy_cache_key "$scheme$request_method$host$request_uri";
}

# Basic proxy caching
server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend;

        # Enable caching
        proxy_cache proxy_cache;
        proxy_cache_valid 200 302 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_valid any 5m;

        # Cache status header
        add_header X-Cache-Status $upstream_cache_status;
    }
}

# Cache bypass and purge
location / {
    proxy_pass http://backend;
    proxy_cache proxy_cache;

    # Bypass cache for specific conditions
    proxy_cache_bypass $cookie_nocache $arg_nocache;
    proxy_no_cache $cookie_nocache $arg_nocache;

    # Bypass for POST requests
    proxy_cache_bypass $request_method;
}

# Stale content serving
location / {
    proxy_pass http://backend;
    proxy_cache proxy_cache;

    # Serve stale content while revalidating
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    proxy_cache_background_update on;
    proxy_cache_lock on;
    proxy_cache_lock_timeout 5s;
}

# Microcaching for dynamic content
location / {
    proxy_pass http://backend;
    proxy_cache proxy_cache;

    # Cache for 1 second
    proxy_cache_valid 200 1s;
    proxy_cache_use_stale updating;
    proxy_cache_background_update on;
    proxy_cache_lock on;
}

# Static file caching with expires
location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|txt)$ {
    expires 30d;
    add_header Cache-Control "public, no-transform";
    access_log off;
}

# Browser caching directives
location /static/ {
    alias /var/www/static/;

    # Set expiration
    expires 1y;

    # Cache control headers
    add_header Cache-Control "public, immutable";

    # Disable access logging
    access_log off;
}

Examples

# Complete caching configuration
http {
    # Cache zones
    proxy_cache_path /var/cache/nginx/api
        levels=1:2
        keys_zone=api_cache:10m
        max_size=5g
        inactive=60m
        use_temp_path=off;

    proxy_cache_path /var/cache/nginx/static
        levels=1:2
        keys_zone=static_cache:10m
        max_size=20g
        inactive=7d;

    # Custom cache key
    map $request_uri $cache_key {
        default "$scheme$request_method$host$request_uri";
        ~*\?.*$ "$scheme$request_method$host$uri";  # Ignore query strings
    }

    server {
        listen 443 ssl;
        http2 on;
        server_name app.example.com;

        # API caching with authentication awareness
        location /api/ {
            proxy_pass http://api_backend;

            proxy_cache api_cache;
            proxy_cache_key "$cache_key$http_authorization";
            proxy_cache_valid 200 5m;
            proxy_cache_valid 404 1m;

            # Don't cache if user is authenticated differently
            proxy_cache_bypass $http_pragma;
            proxy_no_cache $http_pragma;

            # Serve stale on errors
            proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
            proxy_cache_background_update on;

            # Cache status
            add_header X-Cache-Status $upstream_cache_status;
        }

        # Static assets with aggressive caching
        location /static/ {
            alias /var/www/app/static/;

            proxy_cache static_cache;
            proxy_cache_valid 200 7d;

            # Browser caching
            expires 1y;
            add_header Cache-Control "public, immutable";

            # Optimisations
            access_log off;
            tcp_nodelay off;
            open_file_cache max=3000 inactive=120s;
            open_file_cache_valid 45s;
            open_file_cache_min_uses 2;
            open_file_cache_errors off;
        }

        # Dynamic content with microcaching
        location / {
            proxy_pass http://app_backend;

            proxy_cache api_cache;
            proxy_cache_valid 200 1s;
            proxy_cache_use_stale updating;
            proxy_cache_background_update on;
            proxy_cache_lock on;

            # Vary by cookies for personalised content
            proxy_cache_key "$scheme$host$request_uri$cookie_session";

            add_header X-Cache-Status $upstream_cache_status;
        }

        # Cache purge endpoint (requires ngx_cache_purge module)
        location ~ /purge(/.*) {
            allow 127.0.0.1;
            deny all;
            proxy_cache_purge api_cache "$scheme$request_method$host$1";
        }
    }
}

Rate Limiting

Nginx can limit request rates to protect against abuse, DDoS attacks, and ensure fair resource usage.

Key Concepts

  • Limit Request Zone - Shared memory for tracking request rates
  • Limit Connections Zone - Track concurrent connections
  • Rate - Requests per second/minute allowed
  • Burst - Allow temporary spikes above the rate
  • Nodelay - Process burst requests immediately
  • Dry Run - Test rate limiting without enforcement

Common Patterns

# Define rate limit zones in http context
http {
    # Rate limit by IP address
    limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;

    # Rate limit by server name
    limit_req_zone $server_name zone=server_limit:10m rate=100r/s;

    # Rate limit by URI
    limit_req_zone $request_uri zone=uri_limit:10m rate=50r/s;

    # Rate limit by API key
    limit_req_zone $http_x_api_key zone=api_limit:10m rate=100r/s;

    # Connection limits
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    # Custom response for rate limiting
    limit_req_status 429;
    limit_conn_status 429;
}

# Basic rate limiting
server {
    listen 80;
    server_name example.com;

    location / {
        limit_req zone=ip_limit;
        proxy_pass http://backend;
    }
}

# Rate limiting with burst
location /api/ {
    # Allow 10 r/s with burst of 20, delay excess
    limit_req zone=ip_limit burst=20;
    proxy_pass http://backend;
}

# Rate limiting with nodelay
location /api/ {
    # Allow 10 r/s with burst of 20, no delay
    limit_req zone=ip_limit burst=20 nodelay;
    proxy_pass http://backend;
}

# Connection limiting
location /download/ {
    limit_conn conn_limit 5;  # Max 5 connections per IP
    limit_rate 100k;          # Bandwidth limit per connection
    proxy_pass http://backend;
}

# Multiple rate limits
location /api/ {
    limit_req zone=ip_limit burst=10 nodelay;
    limit_req zone=server_limit burst=50;
    proxy_pass http://backend;
}

# Whitelist certain IPs
geo $limit {
    default 1;
    10.0.0.0/8 0;
    192.168.0.0/16 0;
}

map $limit $limit_key {
    0 "";
    1 $binary_remote_addr;
}

limit_req_zone $limit_key zone=req_limit:10m rate=5r/s;

# Dry run mode for testing
location /api/ {
    limit_req zone=ip_limit burst=10;
    limit_req_dry_run on;
    proxy_pass http://backend;
}

Examples

# Comprehensive rate limiting configuration
http {
    # Rate limit zones
    limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
    limit_req_zone $http_x_api_key zone=api_key:10m rate=1000r/s;

    # Connection limit zones
    limit_conn_zone $binary_remote_addr zone=addr:10m;

    # Custom error responses
    limit_req_status 429;
    limit_conn_status 429;

    # Whitelist configuration
    geo $whitelist {
        default 0;
        10.0.0.0/8 1;
        172.16.0.0/12 1;
        192.168.0.0/16 1;
    }

    map $whitelist $limit_key {
        0 $binary_remote_addr;
        1 "";
    }

    server {
        listen 443 ssl;
        http2 on;
        server_name api.example.com;

        # Global connection limit
        limit_conn addr 100;

        # General pages
        location / {
            limit_req zone=general burst=20 nodelay;
            proxy_pass http://backend;
        }

        # Login endpoint - strict limiting
        location /auth/login {
            limit_req zone=login burst=5 nodelay;
            proxy_pass http://backend;

            # Custom error page for rate limiting
            error_page 429 /rate_limit.html;
        }

        # API endpoints - higher limits
        location /api/ {
            # Apply both IP and API key limits
            limit_req zone=api burst=50 nodelay;
            limit_req zone=api_key burst=100 nodelay;

            proxy_pass http://backend;

            # Add rate limit headers
            add_header X-RateLimit-Limit "100" always;
            # $limit_req_status reports PASSED/DELAYED/REJECTED, not a remaining count
            add_header X-RateLimit-Status $limit_req_status always;
        }

        # Download endpoint - bandwidth limiting
        location /downloads/ {
            limit_conn addr 2;          # Max 2 concurrent downloads
            limit_rate_after 10m;       # Full speed for first 10MB
            limit_rate 1m;              # Then limit to 1MB/s

            alias /var/www/downloads/;
        }

        # Static rate limit error page
        location = /rate_limit.html {
            internal;
            root /var/www/errors;
        }
    }
}

# API rate limiting with different tiers
map $http_x_api_key $api_tier {
    default "free";
    "key-premium-123" "premium";
    "key-enterprise-456" "enterprise";
}

map $api_tier $rate_limit_zone {
    free "free_limit";
    premium "premium_limit";
    enterprise "enterprise_limit";
}

limit_req_zone $binary_remote_addr zone=free_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=premium_limit:10m rate=100r/s;
limit_req_zone $binary_remote_addr zone=enterprise_limit:10m rate=1000r/s;

Common Patterns

Frequently used Nginx configurations for typical deployment scenarios.

Reverse Proxy for Web Application

# Single-page application (React, Vue, Angular)
server {
    listen 80;
    server_name app.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name app.example.com;

    ssl_certificate /etc/ssl/certs/app.crt;
    ssl_certificate_key /etc/ssl/private/app.key;

    root /var/www/app/dist;
    index index.html;

    # SPA routing - try file, then directory, then index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API proxy
    location /api/ {
        proxy_pass http://localhost:3000/;
        proxy_http_version 1.1;
        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;
    }

    # WebSocket support
    location /ws/ {
        proxy_pass http://localhost:3000/ws/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400;
    }

    # Static assets with caching
    location /static/ {
        alias /var/www/app/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
}

Static File Server

server {
    listen 80;
    server_name static.example.com;

    root /var/www/static;

    # Enable directory listing
    autoindex on;
    autoindex_exact_size off;
    autoindex_localtime on;

    # Optimise static file serving
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # File type specific caching
    location ~* \.(jpg|jpeg|png|gif|ico|webp)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    location ~* \.(css|js)$ {
        expires 7d;
        add_header Cache-Control "public";
    }

    location ~* \.(pdf|doc|docx|xls|xlsx)$ {
        expires 1d;
    }

    # Prevent hotlinking
    location ~* \.(gif|png|jpe?g)$ {
        valid_referers none blocked example.com *.example.com;
        if ($invalid_referer) {
            return 403;
        }
    }

    # Deny access to hidden files
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

PHP Application (WordPress, Laravel)

server {
    listen 80;
    server_name php.example.com;

    root /var/www/php/public;
    index index.php index.html;

    # Logs
    access_log /var/log/nginx/php.access.log;
    error_log /var/log/nginx/php.error.log;

    # Main location
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP processing
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # Timeouts for long-running scripts
        fastcgi_read_timeout 300;
        fastcgi_send_timeout 300;
    }

    # Deny access to sensitive files
    location ~ /\.(?!well-known).* {
        deny all;
    }

    location ~ ^/(wp-config\.php|readme\.html|license\.txt) {
        deny all;
    }

    # Static file caching
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 30d;
        log_not_found off;
        access_log off;
    }
}

Node.js Application

upstream nodejs {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 80;
    server_name node.example.com;

    location / {
        proxy_pass http://nodejs;
        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;
        proxy_read_timeout 300;
    }

    # Serve static assets directly
    location /public/ {
        alias /var/www/node/public/;
        expires 30d;
        access_log off;
    }
}

Redirect Patterns

# HTTP to HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

# WWW to non-WWW
server {
    listen 443 ssl;
    server_name www.example.com;
    return 301 https://example.com$request_uri;
}

# Non-WWW to WWW
server {
    listen 443 ssl;
    server_name example.com;
    return 301 https://www.example.com$request_uri;
}

# Old domain to new domain
server {
    listen 80;
    server_name old-domain.com;
    return 301 https://new-domain.com$request_uri;
}

# Specific path redirects
location /old-page {
    return 301 /new-page;
}

location /blog {
    return 301 https://blog.example.com$request_uri;
}

# Regex-based redirects
location ~ ^/products/([0-9]+)$ {
    return 301 /shop/item/$1;
}

Security Patterns

server {
    listen 443 ssl;
    http2 on;
    server_name secure.example.com;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "0" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    # Content Security Policy
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com;" always;

    # Hide Nginx version
    server_tokens off;

    # Limit request body size
    client_max_body_size 10m;

    # Allow framing only from a trusted site (X-Frame-Options ALLOW-FROM is obsolete)
    location /embed/ {
        add_header Content-Security-Policy "frame-ancestors https://trusted-site.com";
    }

    # Block known-bad bots. Avoid matching generic tools like curl or
    # wget: they are used by health checks, monitoring, webhooks and
    # legitimate scripts, and real scrapers just change their UA anyway.
    # Zyborg's operator is long defunct, so any traffic claiming it is spoofed.
    if ($http_user_agent ~* (zyborg|masscan|nikto|sqlmap)) {
        return 403;
    }

    # IP whitelist for admin
    location /admin/ {
        allow 10.0.0.0/8;
        allow 192.168.1.0/24;
        deny all;

        proxy_pass http://backend;
    }

    # Deny access to version control
    location ~ /\.(git|svn|hg) {
        deny all;
        return 404;
    }

    # Deny access to sensitive file types
    location ~* \.(engine|inc|install|make|module|profile|po|sh|sql|theme|twig|tpl|xtmpl|yml|yaml)$ {
        deny all;
        return 404;
    }
}

Troubleshooting

Tools and techniques for diagnosing and resolving Nginx issues.

Key Concepts

  • Access Log - Records all HTTP requests
  • Error Log - Records errors and warnings
  • Log Levels - debug, info, notice, warn, error, crit, alert, emerg
  • Status Codes - HTTP response codes indicating request outcomes
  • Stub Status - Basic server statistics module

Common Commands

# Test configuration syntax
nginx -t

# Test and show configuration
nginx -T

# Reload configuration (graceful)
nginx -s reload

# Stop gracefully (finish current requests)
nginx -s quit

# Stop immediately
nginx -s stop

# Reopen log files (for rotation)
nginx -s reopen

# Show version and configure options
nginx -V

# Check running processes
ps aux | grep nginx

# View error log in real-time
tail -f /var/log/nginx/error.log

# View access log
tail -f /var/log/nginx/access.log

# Filter specific status codes
grep " 500 " /var/log/nginx/access.log
grep " 502 " /var/log/nginx/access.log

# Count requests by status code
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

# Top requested URLs
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# Top IP addresses
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# Check open connections
netstat -tlnp | grep nginx
ss -tlnp | grep nginx

# Check file descriptors
cat /proc/$(cat /var/run/nginx.pid)/limits
ls /proc/$(cat /var/run/nginx.pid)/fd | wc -l

Log Configuration

# Custom log format
log_format detailed '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';

# JSON log format
log_format json_combined escape=json
    '{'
        '"time_local":"$time_local",'
        '"remote_addr":"$remote_addr",'
        '"remote_user":"$remote_user",'
        '"request":"$request",'
        '"status": "$status",'
        '"body_bytes_sent":"$body_bytes_sent",'
        '"request_time":"$request_time",'
        '"http_referrer":"$http_referer",'
        '"http_user_agent":"$http_user_agent",'
        '"upstream_addr":"$upstream_addr",'
        '"upstream_response_time":"$upstream_response_time"'
    '}';

# Use custom format
access_log /var/log/nginx/access.log detailed;

# Debug logging (use only temporarily)
error_log /var/log/nginx/error.log debug;

# Per-location logging
location /api/ {
    access_log /var/log/nginx/api.access.log detailed;
    error_log /var/log/nginx/api.error.log warn;
    proxy_pass http://backend;
}

# Disable logging for health checks
location /health {
    access_log off;
    return 200 "OK";
}

# Conditional logging
map $status $loggable {
    ~^[23]  0;
    default 1;
}

access_log /var/log/nginx/access.log combined if=$loggable;

Status and Monitoring

# Enable stub_status module
location /nginx_status {
    stub_status on;
    allow 127.0.0.1;
    allow 10.0.0.0/8;
    deny all;
}

# Sample output:
# Active connections: 291
# server accepts handled requests
#  16630948 16630948 31070465
# Reading: 6 Writing: 179 Waiting: 106

HTTP Status Codes

Code Meaning Common Causes
400 Bad Request Malformed request, oversized headers
403 Forbidden Permission denied, directory listing disabled
404 Not Found File doesn't exist, wrong root/alias
405 Method Not Allowed Method not configured for location
413 Request Entity Too Large Exceeds client_max_body_size
414 URI Too Long Exceeds large_client_header_buffers
499 Client Closed Request Client disconnected before response
500 Internal Server Error Configuration error, upstream error
502 Bad Gateway Upstream not responding, wrong upstream address
503 Service Unavailable Upstream overloaded, all servers down
504 Gateway Timeout Upstream timeout exceeded

Quick Reference

Essential Commands

Command Description
nginx -t Test configuration syntax
nginx -T Test and dump configuration
nginx -s reload Reload configuration gracefully
nginx -s stop Stop server immediately
nginx -s quit Stop server gracefully
nginx -s reopen Reopen log files
nginx -V Show version and modules
systemctl status nginx Check service status
systemctl restart nginx Restart service
journalctl -u nginx View systemd logs

Key Directives

Directive Context Description
listen server Port and options to listen on
server_name server Virtual host names
root http, server, location Document root directory
index http, server, location Default index files
location server, location URI matching block
proxy_pass location Upstream server address
upstream http Backend server group
ssl_certificate http, server SSL certificate file
ssl_certificate_key http, server SSL private key file
try_files server, location Check files in order
return server, location Return response code/URL
rewrite server, location Rewrite request URI
add_header http, server, location Add response header
proxy_set_header http, server, location Set proxy request header
limit_req http, server, location Rate limiting
limit_conn http, server, location Connection limiting

Common Variables

Variable Description
$host Host header or server name
$uri Current URI (normalised)
$request_uri Original URI with arguments
$args Query string arguments
$remote_addr Client IP address
$scheme Request scheme (http/https)
$request_method HTTP method (GET, POST, etc.)
$server_name Matched server name
$server_port Server port
$http_* Request header value
$upstream_cache_status Cache hit status
$request_time Request processing time
$upstream_response_time Upstream response time

Common Issues and Solutions

Issue Cause Solution
nginx: [emerg] bind() to 0.0.0.0:80 failed Port already in use Check for existing processes: lsof -i :80
connect() failed (111: Connection refused) Upstream not running Start upstream service or check port
upstream timed out Backend too slow Increase proxy_read_timeout
client intended to send too large body Upload exceeds limit Increase client_max_body_size
open() failed (13: Permission denied) File permissions Check file/directory permissions and user
could not build server_names_hash Too many server names Increase server_names_hash_bucket_size
The wrong site answers for an unknown hostname No default_server, so the first block for that address and port wins, by include order Add a catch-all default_server block
a duplicate default server for 0.0.0.0:80 default_server on two blocks for the same address and port Keep one per address and port
worker_connections are not enough Too many connections Increase worker_connections
SSL: error:0200100D:system library:fopen:Permission denied Certificate file permissions Ensure nginx user can read SSL files
rewrite or internal redirection cycle Redirect loop Check try_files and rewrite rules
no resolver defined to resolve Missing DNS resolver Add resolver directive
502 Bad Gateway with PHP FPM socket issue Check PHP-FPM is running and socket path
504 Gateway Timeout Slow upstream response Increase timeout values or optimise backend

Debugging Tips

# Enable debug logging temporarily
error_log /var/log/nginx/error.log debug;

# Check which configuration file is being used
nginx -t 2>&1 | head -1

# Verify configuration includes
nginx -T | grep "include"

# Test specific server block
curl -H "Host: example.com" http://localhost/

# Test with verbose output
curl -v https://example.com/

# Check SSL certificate
openssl s_client -connect example.com:443 -servername example.com

# Verify upstream connectivity
curl -I http://localhost:3000/

# Monitor connections in real-time
watch -n 1 'ss -s'

# Check for configuration errors
journalctl -u nginx --since "10 minutes ago"

# Analyse slow requests
awk '$NF > 1' /var/log/nginx/access.log  # Requests > 1 second

Related Topics

The following topics would complement this Nginx cheatsheet:

  1. HAProxy - Alternative load balancer with advanced health checking and routing capabilities
  2. Let's Encrypt / Certbot - Free SSL certificate automation for securing Nginx sites
  3. Docker - Containerising Nginx for consistent deployments and orchestration
  4. Prometheus / Grafana - Monitoring Nginx metrics and creating dashboards
  5. Ansible - Automating Nginx configuration management across multiple servers
  6. API Gateway Patterns - Advanced routing, authentication, and rate limiting strategies