NGINX
Beginner to Advanced
Complete Tutorial for [Link] Developers
Installation · Configuration · Reverse Proxy · Load Balancing
SSL/TLS · HTTP/2 · Caching · Security · Performance Tuning
Every directive explained — with why, how, and [Link] examples
Nginx Complete Tutorial — [Link] Developer Edition Page 1
■ Table of Contents
1. What is Nginx? Architecture & Core Concepts
2. Installation & First Steps
3. Nginx Configuration – Deep Dive (Every Directive)
4. Serving Static Files
5. Nginx as Reverse Proxy for [Link]
6. Load Balancing
7. SSL / TLS & HTTPS
8. HTTP/2 & HTTP/3
9. Caching
10. Security Hardening
11. Gzip & Brotli Compression
12. Logging & Monitoring
13. Rate Limiting & DDoS Protection
14. WebSockets with [Link]
15. Nginx with Docker & [Link]
16. Performance Tuning
17. Common Patterns & Best Practices
18. Troubleshooting Guide
Nginx Complete Tutorial — [Link] Developer Edition Page 2
1. What is Nginx? Architecture & Core
Concepts
Nginx (pronounced "engine-x") is a high-performance, open-source web server, reverse proxy, load
balancer, mail proxy, and HTTP cache. Created by Igor Sysoev in 2004, it was designed to solve the C10k
problem — handling 10,000 simultaneous connections efficiently.
Why Should a [Link] Developer Learn Nginx?
[Link] is excellent at application logic, but Nginx excels at:
• Serving static files (HTML, CSS, JS, images) much faster than Node
• Terminating SSL/TLS so your Node app handles plain HTTP internally
• Load balancing across multiple [Link] processes or servers
• Caching responses — reducing load on your [Link] app
• Protecting your [Link] app from direct internet exposure
• Rate limiting, DDoS mitigation, and request filtering
• WebSocket proxying with connection upgrade handling
• Gzip/Brotli compression without burdening Node
Event-Driven Architecture (Why Nginx is Fast)
Unlike Apache's thread-per-request model, Nginx uses an asynchronous, event-driven architecture.
One master process manages multiple worker processes. Each worker handles thousands of connections
using non-blocking I/O — very similar to how [Link] works internally.
Component Role
Master Process Reads config, manages workers, handles signals (reload/stop)
Worker Processes Handle actual client connections (1 per CPU core recommended)
Cache Manager Manages cache storage and expiry
Cache Loader Loads cache metadata from disk on startup
Nginx Complete Tutorial — [Link] Developer Edition Page 3
2. Installation & First Steps
Installation on Ubuntu / Debian
# Update package list
sudo apt update
# Install Nginx
sudo apt install nginx -y
# Start and enable Nginx
sudo systemctl start nginx
sudo systemctl enable nginx
# Check status
sudo systemctl status nginx
# Verify version
nginx -v
Installation on CentOS / RHEL / Amazon Linux
# Add Nginx repo
sudo yum install epel-release -y
sudo yum install nginx -y
# Start
sudo systemctl start nginx
sudo systemctl enable nginx
Installation via Docker (Common for [Link] devs)
# Pull official Nginx image
docker pull nginx:latest
# Run with config
docker run -d -p 80:80 -p 443:443 \
-v /path/to/[Link]:/etc/nginx/[Link] \
-v /path/to/certs:/etc/nginx/certs \
--name nginx nginx:latest
Nginx Complete Tutorial — [Link] Developer Edition Page 4
Key File & Directory Locations
Path Purpose
/etc/nginx/[Link] Main configuration file
/etc/nginx/conf.d/ Drop-in config files (*.conf loaded automatically)
/etc/nginx/sites-available/ Virtual host configs (Debian/Ubuntu)
/etc/nginx/sites-enabled/ Symlinks to active virtual hosts
/var/log/nginx/[Link] Access log (every request)
/var/log/nginx/[Link] Error log (problems & warnings)
/var/www/html/ Default webroot
/run/[Link] PID file of master process
/usr/share/nginx/html/ Default static files
Essential CLI Commands
# Test configuration syntax BEFORE reloading (always do this!)
sudo nginx -t
# Reload config without downtime (graceful)
sudo nginx -s reload
# Stop gracefully (waits for connections to finish)
sudo nginx -s quit
# Stop immediately
sudo nginx -s stop
# Show compiled modules and config
nginx -V
# Reload via systemctl
sudo systemctl reload nginx
■ Tip: Always run 'sudo nginx -t' before reloading. A bad config will crash Nginx on reload!
Nginx Complete Tutorial — [Link] Developer Edition Page 5
3. Nginx Configuration – Complete Deep
Dive
The Nginx configuration uses a hierarchical structure of contexts (blocks). Directives in outer contexts are
inherited by inner ones unless overridden. Understanding this hierarchy is the #1 key to mastering Nginx.
Config Structure Overview
# [Link] — top-level structure
# MAIN context (global settings)
user www-data;
worker_processes auto;
error_log /var/log/nginx/[Link] warn;
pid /run/[Link];
events {
# EVENT context — connection handling
worker_connections 1024;
http {
# HTTP context — all HTTP settings
server {
# SERVER (virtual host) context
location / {
# LOCATION context — URL matching
3.1 Main Context Directives
Directive / Option Default Description & When to Use
user nobody OS user/group that worker processes run as. Use 'www-data' (Debian) or 'nginx' (CentO
worker_processes 1 Number of worker processes. Set to 'auto' to match CPU cores. Each worker handles th
worker_rlimit_nofile — Max open file descriptors per worker. Increase for high-traffic (e.g., 65535). Must be >=
error_log logs/[Link] Path and level: debug, info, notice, warn, error, crit, alert, emerg. Use 'warn' in productio
pid logs/[Link] File that stores master process PID. Used by systemd and init scripts.
Nginx Complete Tutorial — [Link] Developer Edition Page 6
Directive / Option Default Description & When to Use
include — Include other config files. Example: include /etc/nginx/conf.d/*.conf;
daemon on Run in background. Set 'off' for Docker containers so the process stays in foreground.
env — Pass environment variables to Nginx (e.g., env TZ; to set timezone).
3.2 Events Context Directives
Directive / Option Default Description & When to Use
worker_connections 512 Max simultaneous connections per worker. Total = worker_processes × worker_connec
use auto Connection processing method. Linux: use 'epoll' (fastest). macOS: use 'kqueue'. Auto-
multi_accept off If 'on', worker accepts all new connections at once instead of one at a time. Better unde
accept_mutex on Serialize accept() calls across workers. Prevents thundering herd. Keep 'on' unless ben
3.3 HTTP Context Directives
Directive / Option Default Description & When to Use
include [Link] — Load MIME type mappings (text/html, image/png, etc.). Always include this!
default_type text/plain Default MIME type for unknown file extensions. Set to application/octet-stream for down
sendfile off Use kernel sendfile() syscall to serve files. MUCH faster than read()+write(). Always 'on
tcp_nopush off Send HTTP headers and file start in one TCP packet. Use with sendfile. Reduces packe
tcp_nodelay on Disable Nagle algorithm for keepalive connections. Reduces latency for small packets. K
keepalive_timeout 75s Time to keep idle keepalive connection open. 65-75s is good. Reduces TCP handshake
keepalive_requests 1000 Max requests per keepalive connection before closing. Increase for high-volume APIs.
client_max_body_size 1m Max request body size. Increase for file uploads (e.g., 50m for 50MB). Returns 413 if ex
client_body_timeout 60s Timeout waiting for client to send request body. Protect against slow-body attacks.
client_header_timeout 60s Timeout waiting for client to send full request headers.
send_timeout 60s Timeout between two successive writes to client. Protects against slow client attacks.
server_tokens on Show Nginx version in error pages and Server header. Set 'off' in production for security
types_hash_max_size 1024 Hash table size for MIME types. Increase to 2048 if you get warnings.
32
server_names_hash_bucket_size Hash bucket for server names. Increase to 64 or 128 for long domain names.
3.4 Server Context Directives
Directive / Option Default Description & When to Use
listen 80 Port to listen on. Examples: listen 443 ssl; listen [::]:80; (IPv6); listen 80 default_server;
server_name "" Hostname(s) this block handles. Use _ for catch-all. Supports wildcards (*.[Link]
root — Document root directory. Nginx prepends this to the URI to find files.
index [Link] Default file(s) to serve for directory requests. Nginx tries them in order.
error_page — Custom error pages. Example: error_page 404 /[Link]; error_page 500 502 503 504
access_log — Access log path and format for this server. Set 'off' to disable logging for specific vhosts
Nginx Complete Tutorial — [Link] Developer Edition Page 7
Directive / Option Default Description & When to Use
return — Send redirect or response directly. return 301 [Link] is the go-to red
rewrite — Rewrite URI using regex. Avoid overuse — prefer try_files or return for simple cases.
3.5 Location Context & Matching Rules
Location blocks match incoming request URIs. Nginx applies the most specific match. Order of priority:
Priority Syntax Example Description
1 (highest) = /path location = /[Link] Exact match. Stops searching immediately. Use for single files.
2 ^~ /path location ^~ /static/ Prefix match, no regex. Use for directories. Stops regex checking.
3 ~ regex location ~ \.php$ Case-sensitive regex. Order matters — first match wins.
3 ~* regex location ~* \.jpg$ Case-insensitive regex. First match among all regex wins.
4 (lowest) /path location /api/ Prefix match. Longest prefix wins among all prefix locations.
server {
# Exact match — highest priority
location = / {
return 200 'Root page hit exactly';
# Prefix match with no-regex flag — beats all regex
location ^~ /static/ {
root /var/www;
expires 1y;
# Case-insensitive regex — matches images
location ~* \.(jpg|jpeg|png|gif|webp|svg)$ {
expires 30d;
add_header Cache-Control 'public, immutable';
# Prefix fallback — [Link] API
location /api/ {
proxy_pass [Link]
# Catch-all
location / {
try_files $uri $uri/ /[Link];
3.6 try_files — The Most Important Location Directive
Nginx Complete Tutorial — [Link] Developer Edition Page 8
try_files is essential for [Link] SPAs and APIs. It tells Nginx to try serving files in sequence, falling back
to a URI or status code.
# Pattern: try the file → try as directory → fallback to [Link] (SPA)
location / {
root /var/www/my-react-app/dist;
try_files $uri $uri/ /[Link];
# 1. Try exact file ($uri = /about → /var/www/my-react-app/dist/about)
# 2. Try as directory ($uri/ = /about/ → look for [Link] inside)
# 3. Serve /[Link] — React Router handles routing client-side
# Return 404 if nothing matches
location /api/ {
try_files $uri $uri/ =404;
■ Tip: For React/Vue/Angular SPAs, always use try_files $uri $uri/ /[Link] so client-side routing works.
Nginx Complete Tutorial — [Link] Developer Edition Page 9
4. Serving Static Files
Nginx serves static files orders of magnitude faster than [Link] because it uses the OS kernel's sendfile()
syscall and handles caching at the network layer. Always offload static file serving to Nginx.
server {
listen 80;
server_name [Link] [Link];
root /var/www/html;
# Serve static files with aggressive caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control 'public, max-age=31536000, immutable';
access_log off; # Don't log static file requests
log_not_found off; # Don't log 404s for missing assets
# HTML files — short cache (changes often)
location ~* \.html$ {
expires 1h;
add_header Cache-Control 'public, max-age=3600';
# SPA fallback
location / {
try_files $uri $uri/ /[Link];
Alias vs Root
Both root and alias set the filesystem path, but they work differently:
# root: APPENDS the location path to root
# Request: /static/[Link] → /var/www/html/static/[Link]
location /static/ {
root /var/www/html;
# alias: REPLACES the location path entirely
# Request: /static/[Link] → /var/www/assets/[Link]
location /static/ {
Nginx Complete Tutorial — [Link] Developer Edition Page 10
alias /var/www/assets/; # Note trailing slash!
■ Note: Always add a trailing slash to alias paths. Missing it causes subtle 404 bugs.
Nginx Complete Tutorial — [Link] Developer Edition Page 11
5. Nginx as Reverse Proxy for [Link]
This is the most common use case for [Link] developers. Nginx sits in front of your Express/Fastify/Koa
app, handling SSL, static files, and proxying API requests to Node.
Basic Reverse Proxy Setup
# /etc/nginx/conf.d/[Link]
server {
listen 80;
server_name [Link];
location / {
proxy_pass [Link]
# Essential proxy headers
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;
Complete Proxy Headers Reference
Directive / Option Default Description & When to Use
proxy_pass — Backend URL. Can be [Link] [Link] or unix socket ([Link]
proxy_http_version 1.0 HTTP version to use with backend. Set to 1.1 for keepalive and WebSockets. Required
proxy_set_header Host $proxy_host Passes the Host header. Use $host to forward original domain to [Link].
—
proxy_set_header X-Real-IP Passes real client IP. Read in [Link] via [Link]['x-real-ip'].
—
proxy_set_header X-Forwarded-For Appends client IP to chain. Standard way to forward IPs through proxies. Read via req.h
—
proxy_set_header X-Forwarded-Proto Passes original protocol (http/https). Lets Node know if request was HTTPS even thoug
proxy_connect_timeout 60s Timeout to establish connection to backend. 10-30s is reasonable.
proxy_send_timeout 60s Timeout to send request to backend. Increase for large uploads.
Nginx Complete Tutorial — [Link] Developer Edition Page 12
Directive / Option Default Description & When to Use
proxy_read_timeout 60s Timeout waiting for backend response. Increase for slow endpoints (reports, exports). V
proxy_buffering on Buffer backend response. 'on' = Nginx buffers whole response, then sends to client. 'off
proxy_buffer_size 4k Size of buffer for the first part of response (headers). Increase to 16k if headers are larg
proxy_buffers 8 4k Number and size of buffers for response body. Increase for large responses.
proxy_cache_bypass — Conditions to bypass cache. Common: proxy_cache_bypass $http_upgrade; (for WebS
proxy_next_upstream error timeout On which conditions to try next upstream server. Add non_idempotent with caution.
Trust X-Forwarded-For in [Link] (Express)
// In your Express app — trust Nginx proxy
const app = express();
[Link]('trust proxy', 1); // Trust first proxy (Nginx)
// Now [Link] will return real client IP
[Link]('/ip', (req, res) => {
[Link]({ ip: [Link] });
});
■ Tip: Always set 'trust proxy' in Express when behind Nginx. Otherwise [Link] returns [Link].
Nginx Complete Tutorial — [Link] Developer Edition Page 13
6. Load Balancing
Nginx can distribute traffic across multiple [Link] instances. This is how you scale horizontally — run
multiple Node processes and let Nginx distribute load.
Upstream Block & Load Balancing Methods
http {
# Define upstream group
upstream node_cluster {
# Default: round-robin (each request goes to next server in turn)
server [Link]:3001;
server [Link]:3002;
server [Link]:3003;
server [Link]:3004;
# Enable keepalive connections to backend
keepalive 32; # Keep 32 idle connections per worker
server {
listen 80;
location / {
proxy_pass [Link]
proxy_http_version 1.1;
proxy_set_header Connection ''; # Required for keepalive
Load Balancing Algorithms
Directive / Option Default Description & When to Use
(default — round-robin) — Requests distributed evenly in rotation. Good for stateless APIs with similar response tim
least_conn — Sends request to server with fewest active connections. Best when requests have varia
ip_hash — Same client IP always goes to same server (sticky sessions). Useful when session state
hash $request_uri — Hash based on URI. Same URL always hits same server. Useful for caching consistenc
random — Random server selection. Add 'two least_conn' to pick best of two random servers (pow
Nginx Complete Tutorial — [Link] Developer Edition Page 14
Directive / Option Default Description & When to Use
weight=N 1 Server weight. weight=3 means server gets 3x more requests. Use for servers with diffe
backup — Marks server as backup. Only used when primary servers are unavailable.
down — Marks server as permanently unavailable. Useful for maintenance.
max_fails=N 1 Number of failed attempts before marking server as unavailable. Combine with fail_time
fail_timeout=Ns 10s How long server is considered down after max_fails. And the time window in which failu
PM2 + Nginx — Production [Link] Setup
# Start 4 [Link] processes with PM2
pm2 start [Link] -i 4 --name 'api'
# PM2 assigns ports 3001–3004 automatically with cluster mode
# Or manually start on specific ports
pm2 start [Link] --name 'api-1' -- --port 3001
pm2 start [Link] --name 'api-2' -- --port 3002
pm2 start [Link] --name 'api-3' -- --port 3003
pm2 start [Link] --name 'api-4' -- --port 3004
# Nginx upstream config
upstream node_api {
least_conn;
server [Link]:3001 max_fails=3 fail_timeout=30s;
server [Link]:3002 max_fails=3 fail_timeout=30s;
server [Link]:3003 max_fails=3 fail_timeout=30s;
server [Link]:3004 max_fails=3 fail_timeout=30s;
keepalive 64;
Nginx Complete Tutorial — [Link] Developer Edition Page 15
7. SSL / TLS & HTTPS
Nginx handles SSL termination — it decrypts HTTPS traffic and forwards plain HTTP to your [Link] app.
This is the standard production pattern: your Node app never needs to deal with certificates.
Let's Encrypt with Certbot (Free SSL)
# Install Certbot
sudo apt install certbot python3-certbot-nginx -y
# Obtain and auto-configure certificate
sudo certbot --nginx -d [Link] -d [Link]
# Test auto-renewal
sudo certbot renew --dry-run
# Certbot adds a cron job to auto-renew. Check:
sudo systemctl status [Link]
Manual SSL Configuration (Full Config)
server {
listen 80;
server_name [Link] [Link];
# Redirect all HTTP to HTTPS
return 301 [Link]
server {
listen 443 ssl;
http2 on; # Enable HTTP/2 (Nginx 1.25.1+)
server_name [Link] [Link];
# Certificate files
ssl_certificate /etc/letsencrypt/live/[Link]/[Link];
ssl_certificate_key /etc/letsencrypt/live/[Link]/[Link];
# Modern SSL settings (TLS 1.2 + 1.3 only)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
Nginx Complete Tutorial — [Link] Developer Edition Page 16
:DHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# SSL session cache — avoids full handshake on reconnect
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling — speeds up certificate verification
ssl_stapling on;
ssl_stapling_verify on;
resolver [Link] [Link] valid=300s;
resolver_timeout 5s;
# HSTS — tell browsers to always use HTTPS
add_header Strict-Transport-Security 'max-age=63072000' always;
location / {
proxy_pass [Link]
proxy_set_header X-Forwarded-Proto https;
Directive / Option Default Description & When to Use
ssl_protocols TLSv1 TLSv1.1.. Only allow TLS 1.2 and 1.3. Never enable SSLv3 or TLS 1.0/1.1 — they're insecure.
ssl_ciphers DEFAULT Cipher suites in OpenSSL format. Mozilla SSL Generator gives current best-practice list
ssl_prefer_server_cipherson Server chooses cipher, not client. Set 'off' for TLS 1.3 (it handles this itself).
ssl_session_cache none Shared SSL session cache between workers. 'shared:SSL:10m' stores ~40,000 session
ssl_session_timeout 5m How long SSL session parameters are cached. 1d reduces handshakes for returning cli
ssl_session_tickets on TLS session tickets for resumption. Set 'off' for forward secrecy (requires session cache
ssl_stapling off OCSP Stapling — Nginx fetches cert revocation status and attaches to handshake. Spe
ssl_dhparam — DH parameters file for DHE ciphers. Generate: openssl dhparam -out /etc/nginx/dhpara
Nginx Complete Tutorial — [Link] Developer Edition Page 17
8. HTTP/2 & HTTP/3
HTTP/2
HTTP/2 dramatically improves performance over HTTP/1.1 by using multiplexing (multiple requests over
one TCP connection), header compression (HPACK), and server push. Enable it — it requires no changes
to your [Link] code.
# Nginx 1.25.1+ (recommended)
server {
listen 443 ssl;
http2 on;
# ... rest of config
# Older Nginx (before 1.25.1)
server {
listen 443 ssl http2;
# ...
■ Tip: HTTP/2 requires HTTPS. Always enable it on your HTTPS server block — it's free performance.
HTTP/3 / QUIC (Nginx 1.25+)
server {
listen 443 ssl;
listen 443 quic reuseport; # HTTP/3 over UDP
http2 on;
server_name [Link];
ssl_certificate /path/to/[Link];
ssl_certificate_key /path/to/[Link];
ssl_protocols TLSv1.3; # HTTP/3 requires TLS 1.3
# Tell browsers HTTP/3 is available
add_header Alt-Svc 'h3=":443"; ma=86400';
Nginx Complete Tutorial — [Link] Developer Edition Page 18
9. Caching
Nginx can cache responses from your [Link] backend, drastically reducing load and latency. A cached
response is served without even touching [Link].
Proxy Cache Setup
# In http context — define cache zone
http {
proxy_cache_path /var/cache/nginx
levels=1:2 # Directory structure for cache files
keys_zone=app_cache:10m # 10MB for cache keys (metadata)
max_size=1g # Max total cache size on disk
inactive=60m # Remove items not accessed for 60m
use_temp_path=off; # Write directly to cache path (faster)
server {
listen 80;
location /api/ {
proxy_pass [Link]
# Enable caching
proxy_cache app_cache;
proxy_cache_key '$scheme$request_method$host$request_uri';
# Cache successful responses for 10 minutes
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
# Serve stale cache while revalidating (background refresh)
proxy_cache_use_stale error timeout updating
http_500 http_502 http_503 http_504;
proxy_cache_lock on; # Only one request rebuilds cache at a time
# Add cache status header (HIT/MISS/BYPASS)
add_header X-Cache-Status $upstream_cache_status;
# Don't cache POST/PUT/DELETE
proxy_cache_methods GET HEAD;
# Bypass cache for logged-in users (has auth cookie)
proxy_cache_bypass $cookie_auth_token;
proxy_no_cache $cookie_auth_token;
Nginx Complete Tutorial — [Link] Developer Edition Page 19
}
Directive / Option Default Description & When to Use
proxy_cache_path — Define where and how to store cache. Set levels, keys_zone (required), max_size, inac
proxy_cache off Name of cache zone to use (from proxy_cache_path). Required to enable caching for a
proxy_cache_key $scheme$proxy_host$request_uri
Unique key per cache entry. Include method, scheme, host, URI. Add $http_authorizatio
proxy_cache_valid — How long to cache responses by status code. Can have multiple lines: proxy_cache_va
proxy_cache_use_stale off Serve stale cache on error/timeout. 'updating' serves stale while a fresh copy is being fe
proxy_cache_lock off Queue duplicate requests for same uncached resource. First request fetches, others wa
proxy_cache_bypass — Conditions to skip cache lookup. $http_pragma (Pragma: no-cache), auth cookies, etc.
proxy_no_cache — Conditions to not store response in cache. Use same conditions as proxy_cache_bypas
add_header X-Cache-Status— Add $upstream_cache_status to response: HIT, MISS, BYPASS, EXPIRED, STALE, UP
Nginx Complete Tutorial — [Link] Developer Edition Page 20
10. Security Hardening
Essential Security Headers
server {
# Hide Nginx version from error pages and Server header
server_tokens off;
# Security headers
add_header X-Frame-Options 'SAMEORIGIN' always;
add_header X-Content-Type-Options 'nosniff' always;
add_header X-XSS-Protection '1; mode=block' always;
add_header Referrer-Policy 'strict-origin-when-cross-origin' always;
add_header Permissions-Policy 'camera=(), microphone=(), geolocation=()' always;
# Content Security Policy (adjust to your needs)
add_header Content-Security-Policy
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe
-inline';" always;
# HSTS — force HTTPS for 2 years, include subdomains
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains; preload'
always;
Blocking Bad Requests
server {
# Block common exploit scanners by user agent
if ($http_user_agent ~* (Nikto|sqlmap|masscan|zgrab|nmap)) {
return 403;
# Block requests with no Host header
if ($host = '') {
return 400;
# Deny access to hidden files (.htaccess, .git, .env)
location ~ /\. {
deny all;
Nginx Complete Tutorial — [Link] Developer Edition Page 21
return 404;
# Block access to backup and config files
location ~* \.(bak|sql|conf|ini|env|log|sh|key)$ {
deny all;
return 404;
# Restrict HTTP methods
if ($request_method !~ ^(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)$) {
return 405;
# Limit request body size (default 1m — increase for file upload APIs)
client_max_body_size 10m;
Nginx Complete Tutorial — [Link] Developer Edition Page 22
11. Gzip & Brotli Compression
http {
# Gzip compression
gzip on;
gzip_vary on; # Add Vary: Accept-Encoding header
gzip_proxied any; # Compress for all proxied requests
gzip_comp_level 6; # 1-9: higher = smaller but slower. 6 is sweet spot
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256; # Don't compress tiny responses
gzip_types
text/plain text/css text/xml text/javascript
application/json application/javascript application/xml
application/rss+xml application/atom+xml
image/svg+xml font/woff font/woff2;
■ Tip: Never gzip images (JPEG, PNG, WebP) — they're already compressed. Only compress text-based
content.
Brotli (Better than Gzip — Nginx module)
# Requires ngx_brotli module (compile Nginx with it or use OpenResty)
http {
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/javascript
text/xml application/xml image/svg+xml;
Nginx Complete Tutorial — [Link] Developer Edition Page 23
12. Logging & Monitoring
Custom Log Format
http {
# Define custom log format with useful fields
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
# JSON format (great for log aggregation tools like Datadog, Loki)
log_format json_log escape=json
'{"time": "$time_iso8601", '
'"remote_addr": "$remote_addr", '
'"method": "$request_method", '
'"uri": "$uri", '
'"status": $status, '
'"bytes": $body_bytes_sent, '
'"request_time": $request_time, '
'"upstream_time": "$upstream_response_time", '
'"user_agent": "$http_user_agent"}';
access_log /var/log/nginx/[Link] json_log;
error_log /var/log/nginx/[Link] warn;
Important Log Variables
Variable Description
$remote_addr Client IP address
$time_local Local time of the request
$request Full request line (method + URI + protocol)
$status HTTP response status code
$body_bytes_sent Bytes sent in response body (excludes headers)
$request_time Total request processing time (in seconds, with milliseconds)
$upstream_response_time Time [Link] took to respond. KEY metric for performance monitoring.
$upstream_cache_status Cache HIT/MISS/BYPASS — measure cache effectiveness
Nginx Complete Tutorial — [Link] Developer Edition Page 24
$http_referer Referer header value
$http_user_agent User-Agent header value
$ssl_protocol SSL/TLS protocol used (TLSv1.2, TLSv1.3)
$ssl_cipher Cipher suite used for SSL connection
$gzip_ratio Compression ratio achieved
Nginx Complete Tutorial — [Link] Developer Edition Page 25
13. Rate Limiting & DDoS Protection
Nginx rate limiting protects your [Link] backend from brute force attacks, scraping, and DDoS. It's much
more efficient than doing rate limiting in Node.
http {
# Define rate limit zones
# $binary_remote_addr = client IP (binary = smaller than string)
# zone=api:10m = 10MB memory for ~160,000 IPs
# rate=10r/s = 10 requests per second limit
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_req_zone $binary_remote_addr zone=global:10m rate=100r/s;
# Connection limiting (concurrent connections per IP)
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
# Global rate limit
limit_req zone=global burst=200 nodelay;
limit_conn addr 20; # Max 20 concurrent connections per IP
location /api/ {
limit_req zone=api burst=20 nodelay;
# burst=20: allow spike up to 20 extra requests
# nodelay: process burst instantly (not queue with delay)
proxy_pass [Link]
location /api/auth/login {
# Strict limit on login endpoint
limit_req zone=login burst=5 nodelay;
limit_req_status 429; # Return 429 Too Many Requests
proxy_pass [Link]
Directive / Option Default Description & When to Use
limit_req_zone — Define a rate limit zone. Format: $key zone=name:size rate=N r/s|r/m. Define in http{} b
limit_req — Apply rate limit in server/location. zone= (required), burst= (queue size), nodelay (proce
limit_req_status 503 HTTP status returned when rate limited. Change to 429 (standard) or 444 (drop connec
limit_conn_zone — Define zone for counting concurrent connections per key.
Nginx Complete Tutorial — [Link] Developer Edition Page 26
Directive / Option Default Description & When to Use
limit_conn — Max simultaneous connections per key in the zone.
limit_conn_status 503 HTTP status returned when connection limit hit.
burst 0 Number of extra requests to queue/allow beyond the rate. Prevents rejecting legitimate
nodelay — Process burst requests immediately instead of spacing them out. Add when you have b
Nginx Complete Tutorial — [Link] Developer Edition Page 27
14. WebSockets with [Link]
WebSockets require a protocol upgrade from HTTP to WS. Nginx handles this transparently with the right
headers. Works with [Link], ws, and any WebSocket library.
# WebSocket proxy configuration
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
server {
listen 443 ssl;
server_name [Link];
location /[Link]/ {
proxy_pass [Link]
# WebSocket upgrade headers
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $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;
# Keep WebSocket connections alive
proxy_read_timeout 3600s; # 1 hour timeout for WS connections
proxy_send_timeout 3600s;
proxy_connect_timeout 7s;
# Disable buffering for real-time data
proxy_buffering off;
# REST API on same server
location /api/ {
proxy_pass [Link]
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Nginx Complete Tutorial — [Link] Developer Edition Page 28
■ Note: WebSocket connections are long-lived. Set proxy_read_timeout to at least 3600s (1 hour) to prevent
Nginx from closing idle connections.
Nginx Complete Tutorial — [Link] Developer Edition Page 29
15. Nginx with Docker & [Link]
[Link] — Full Stack Setup
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx/[Link]:/etc/nginx/[Link]:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./certs:/etc/nginx/certs:ro
- ./logs:/var/log/nginx
depends_on:
- api
restart: unless-stopped
networks:
- app-net
api:
build: ./api
environment:
- NODE_ENV=production
- PORT=3000
expose:
- '3000' # Only expose to Docker network, NOT to host
restart: unless-stopped
networks:
- app-net
networks:
app-net:
driver: bridge
Nginx Config for Docker (conf.d/[Link])
Nginx Complete Tutorial — [Link] Developer Edition Page 30
upstream node_api {
server api:3000; # 'api' = docker-compose service name
keepalive 32;
server {
listen 80;
server_name _;
location / {
proxy_pass [Link]
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;
■ Tip: In Docker, use the service name (e.g., 'api') as the hostname in proxy_pass. Docker DNS resolves
service names automatically.
Nginx Complete Tutorial — [Link] Developer Edition Page 31
16. Performance Tuning
# [Link] — Optimized production config
user www-data;
worker_processes auto; # Match CPU cores
worker_rlimit_nofile 65535; # Max open files per worker
error_log /var/log/nginx/[Link] warn;
pid /run/[Link];
events {
worker_connections 4096; # Per worker. Total = cores * 4096
use epoll; # Linux kernel epoll (fastest)
multi_accept on; # Accept all connections at once
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Keepalive
keepalive_timeout 65;
keepalive_requests 10000;
# Open file cache — cache frequently accessed file descriptors
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# Buffers
client_body_buffer_size 16k;
client_header_buffer_size 1k;
client_max_body_size 10m;
large_client_header_buffers 4 8k;
# Timeouts
client_body_timeout 12;
client_header_timeout 12;
send_timeout 10;
# Gzip
gzip on;
Nginx Complete Tutorial — [Link] Developer Edition Page 32
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript;
include /etc/nginx/conf.d/*.conf;
Directive / Option Default Description & When to Use
open_file_cache off Cache file descriptors, sizes, and modification times. Prevents repeated open() syscalls
open_file_cache_valid 60s How often to revalidate cached file info. 30s is good for frequently-changing content.
open_file_cache_min_uses 1 Min accesses in inactive window before caching. Set to 2 to avoid caching rarely-access
open_file_cache_errors off Cache errors (file not found). Reduces stat() calls for missing files. Set 'on' in production
client_body_buffer_size 8k|16k Buffer for reading client request body. If body > this, Nginx writes to temp file. Match to
4 8k
large_client_header_buffers Buffer for large request headers (cookies, JWTs). Increase if clients send large headers
Nginx Complete Tutorial — [Link] Developer Edition Page 33
17. Common Patterns & Best Practices
Pattern 1: SPA (React/Vue/Angular) + [Link] API
server {
listen 443 ssl;
http2 on;
server_name [Link];
# Serve SPA static files
root /var/www/myapp/dist;
index [Link];
# Static assets — aggressive caching
location ~* \.(js|css|png|jpg|svg|woff2?)$ {
expires 1y;
add_header Cache-Control 'public, immutable';
access_log off;
# API — proxy to [Link]
location /api/ {
proxy_pass [Link]
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SPA fallback — client-side routing
location / {
try_files $uri $uri/ /[Link];
Pattern 2: Multiple [Link] Apps on One Server
# [Link]
server {
listen 443 ssl;
Nginx Complete Tutorial — [Link] Developer Edition Page 34
server_name [Link];
location / { proxy_pass [Link] }
# [Link]
server {
listen 443 ssl;
server_name [Link];
location / { proxy_pass [Link] }
# [Link]
server {
listen 443 ssl;
server_name [Link];
location / { proxy_pass [Link] }
Pattern 3: Serve [Link] App
server {
listen 443 ssl;
http2 on;
server_name [Link];
# Proxy everything to [Link]
location / {
proxy_pass [Link]
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-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# Cache [Link] static assets from _next/static
location /_next/static/ {
proxy_pass [Link]
expires 1y;
add_header Cache-Control 'public, immutable';
}
Nginx Complete Tutorial — [Link] Developer Edition Page 35
}
Nginx Complete Tutorial — [Link] Developer Edition Page 36
18. Troubleshooting Guide
■ 502 Bad Gateway
Cause: [Link] is not running or crashed. Check: pm2 status, node [Link]. Verify port matches
proxy_pass.
# Fix:
curl [Link] # Test Node directly
■ 504 Gateway Timeout
Cause: [Link] is too slow to respond. Increase proxy_read_timeout or optimize slow endpoints.
# Fix:
proxy_read_timeout 120s;
■ 413 Request Entity Too Large
Cause: Request body exceeds client_max_body_size. Increase it for file upload endpoints.
# Fix:
client_max_body_size 50m;
■ 431 Request Header Fields Too Large
Cause: Headers too big (JWT tokens, cookies). Increase large_client_header_buffers.
# Fix:
large_client_header_buffers 4 16k;
■ ERR_CONTENT_LENGTH_MISMATCH
Cause: Proxy buffering issue with streaming responses. Disable proxy_buffering for SSE/streaming.
# Fix:
proxy_buffering off;
■ WebSocket 101 not received
Cause: Missing Upgrade/Connection headers. Add proxy_http_version 1.1 and the Upgrade/Connection
headers.
# Fix:
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade;
Nginx Complete Tutorial — [Link] Developer Edition Page 37
■ CORS errors
Cause: Add CORS headers in Nginx or let your [Link] app handle them (preferred).
# Fix:
add_header Access-Control-Allow-Origin '*' always;
■ Config syntax error
Cause: Run nginx -t to check syntax before reloading. Read the error line number carefully.
# Fix:
sudo nginx -t && sudo nginx -s reload
■ Permission denied on log/cache files
Cause: Nginx worker runs as www-data. Ensure directories are owned by www-data.
# Fix:
chown -R www-data:www-data /var/cache/nginx
■ Redirect loop (HTTP<->HTTPS)
Cause: X-Forwarded-Proto not set or [Link] re-redirecting to HTTP.
# Fix:
proxy_set_header X-Forwarded-Proto $scheme;
Nginx Complete Tutorial — [Link] Developer Edition Page 38
Quick Reference — Nginx Cheat Sheet
Task Command / Config
Test config sudo nginx -t
Reload gracefully sudo nginx -s reload
Check status sudo systemctl status nginx
View access logs tail -f /var/log/nginx/[Link]
View error logs tail -f /var/log/nginx/[Link]
Check compiled modules nginx -V 2>&1 | grep --color -o '\-\-with[^ ]*'
Follow reload in log journalctl -u nginx -f
Redirect HTTP to HTTPS return 301 [Link]
Proxy to [Link] proxy_pass [Link]
Enable HTTP/2 listen 443 ssl; http2 on;
Free SSL cert certbot --nginx -d [Link]
Rate limit 10 req/s limit_req zone=api burst=20 nodelay;
Enable Gzip gzip on; gzip_comp_level 6;
SPA fallback try_files $uri $uri/ /[Link];
Block dotfiles location ~/\. { deny all; }
Cache static 1 year expires 1y; add_header Cache-Control 'public, immutable';
Disable server version server_tokens off;
You now have everything needed to go from zero to production-grade Nginx mastery. Every
directive is documented with its purpose, default, and [Link] context. Start with the reverse
proxy chapter, layer in SSL, then add caching and rate limiting. Good luck! ■
Nginx Complete Tutorial — [Link] Developer Edition Page 39