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.
flowchart LR
subgraph "Nginx Request Flow"
A[Client Request] --> B[Nginx Master]
B --> C[Worker Process]
C --> D{Request Type}
D -->|Static| E[File System]
D -->|Dynamic| F[Upstream Server]
E --> G[Response]
F --> G
G --> A
end
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)
flowchart TD
A[nginx.conf] --> B[Main Context]
B --> C["events { }"]
B --> D["http { }"]
D --> E["server { }"]
D --> F["upstream { }"]
E --> G["location / { }"]
E --> H["location /api { }"]
E --> I["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:
- the block whose
server_namematches the request'sHostheader; - failing that, the block whose
listencarriesdefault_server; - 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
flowchart LR
A[Client] --> B[Nginx Reverse Proxy]
B --> C{Route}
C -->|/api| D[API Server :3000]
C -->|/auth| E[Auth Server :4000]
C -->|/static| F[File System]
D --> B
E --> B
F --> B
B --> A
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
flowchart TD
A[Client Requests] --> B[Nginx Load Balancer]
B --> C{Load Balancing Algorithm}
C -->|Round Robin| D[Server 1]
C -->|Round Robin| E[Server 2]
C -->|Round Robin| F[Server 3]
D --> G[Response]
E --> G
F --> G
G --> B --> A
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
flowchart TD
A[Client Request] --> B{Cache Hit?}
B -->|Yes| C[Return Cached Response]
B -->|No| D[Forward to Backend]
D --> E[Backend Response]
E --> F{Cacheable?}
F -->|Yes| G[Store in Cache]
F -->|No| H[Return Response]
G --> H
C --> I[Client]
H --> I
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:
- HAProxy - Alternative load balancer with advanced health checking and routing capabilities
- Let's Encrypt / Certbot - Free SSL certificate automation for securing Nginx sites
- Docker - Containerising Nginx for consistent deployments and orchestration
- Prometheus / Grafana - Monitoring Nginx metrics and creating dashboards
- Ansible - Automating Nginx configuration management across multiple servers
- API Gateway Patterns - Advanced routing, authentication, and rate limiting strategies