Nginx — Web Server, Reverse Proxy & Load Balancer
Before you start: basic Linux/CLI comfort and a rough idea of what HTTP/HTTPS and DNS do are assumed. No prior web-server administration experience is needed.
Nginx (pronounced "engine-x") is the most widely deployed web server in the world. It powers over 34% of all websites and is the default reverse proxy in Kubernetes ingress, Docker environments, and cloud architectures.
What Nginx Does
Internet → Nginx → Application Servers
│
├── Reverse proxy (forward requests to backend)
├── Load balancer (distribute across multiple backends)
├── SSL/TLS termination (handle HTTPS, pass HTTP internally)
├── Static file serving (images, CSS, JS — extremely fast)
├── Rate limiting (protect APIs from abuse)
├── Caching (cache backend responses)
└── HTTP/2 and gRPC support (gRPC — a fast RPC framework often used for service-to-service calls)
Installation
bash
# Ubuntu/Debian
sudo apt install nginx
sudo systemctl enable --now nginx
# Check status and version
sudo systemctl status nginx
nginx -v # nginx/1.24.0
nginx -t # Test config syntax
# Key file locations
# /etc/nginx/nginx.conf Main config
# /etc/nginx/sites-available/ Virtual host configs
# /etc/nginx/sites-enabled/ Symlinks to active configs
# /var/log/nginx/access.log Access log
# /var/log/nginx/error.log Error log
# /var/www/html Default web root
Core Configuration Concepts
nginx
# nginx.conf structure
events {
worker_connections 1024; # Max connections per worker
}
http {
# Global settings apply to all servers
gzip on;
gzip_types text/plain application/json application/javascript text/css;
# Include all virtual host configs
include /etc/nginx/sites-enabled/*;
}
Virtual Hosts (Server Blocks)
nginx
# /etc/nginx/sites-available/myapp.conf
server {
listen 80;
listen [::]:80;
server_name myapp.com www.myapp.com;
# Redirect all HTTP to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name myapp.com www.myapp.com;
# SSL certificates
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
# SSL hardening
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# Security headers
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy no-referrer-when-downgrade;
# Logging
access_log /var/log/nginx/myapp_access.log;
error_log /var/log/nginx/myapp_error.log;
# Serve static files directly (fast!)
location /static/ {
root /var/www/myapp;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Proxy to Node.js/Python/whatever backend
location / {
proxy_pass http://127.0.0.1:3000;
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 60s;
proxy_connect_timeout 10s;
}
}
Load Balancing
nginx
http {
# Define upstream group
upstream api_backend {
# Load balancing algorithms:
# (default = round-robin)
# least_conn; # Least connections
# ip_hash; # Sticky sessions by client IP
# hash $request_uri; # Hash-based (consistent)
server 10.0.0.1:3000 weight=3; # Gets 3x traffic
server 10.0.0.2:3000 weight=1;
server 10.0.0.3:3000 backup; # Only used when others fail
# Health checks
keepalive 32; # Persistent connections
}
upstream api_backend_ha {
least_conn;
server 10.0.1.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.2:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.3:8080 max_fails=3 fail_timeout=30s;
}
server {
location /api/ {
proxy_pass http://api_backend;
}
}
}
Rate Limiting
nginx
http {
# Define rate limit zones
# $binary_remote_addr = client IP (4 bytes, efficient)
# zone=api:10m = 10MB shared memory zone named "api"
# rate=10r/s = max 10 requests per second per IP
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=2r/m;
server {
# Apply rate limit to API
location /api/ {
limit_req zone=api burst=20 nodelay;
# burst=20: allow burst of 20 requests
# nodelay: serve burst immediately, don't queue
proxy_pass http://api_backend;
}
# Stricter limit for login endpoint
location /auth/login {
limit_req zone=login burst=5 nodelay;
limit_req_status 429;
proxy_pass http://api_backend;
}
}
}
Caching
nginx
http {
# Define cache zone
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=api_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;
server {
location /api/public/ {
proxy_cache api_cache;
proxy_cache_valid 200 5m; # Cache 200 responses for 5 min
proxy_cache_valid 404 1m; # Cache 404s for 1 min
proxy_cache_use_stale error timeout updating;
proxy_cache_background_update on;
proxy_cache_lock on;
# Cache key includes URI and query string
proxy_cache_key "$scheme$request_method$host$request_uri";
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://api_backend;
}
}
}
Nginx as Kubernetes Ingress
yaml
# ingress-nginx controller handles all K8s Ingress resources
# Install via Helm:
# helm install ingress-nginx ingress-nginx/ingress-nginx
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
# Rate limiting
nginx.ingress.kubernetes.io/limit-rps: "10"
# Redirect HTTP to HTTPS
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# Custom timeout
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
# Enable CORS
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://myapp.com"
# Basic auth
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
spec:
ingressClassName: nginx
tls:
- hosts:
- api.myapp.com
secretName: myapp-tls
rules:
- host: api.myapp.com
http:
paths:
- path: /v1/
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- path: /auth/
pathType: Prefix
backend:
service:
name: auth-service
port:
number: 80
Performance Tuning
nginx
# nginx.conf - production tuning
worker_processes auto; # One worker per CPU core
worker_rlimit_nofile 65535; # Max open files per worker
events {
worker_connections 65535; # Max connections per worker
use epoll; # Linux's efficient I/O event-notification mechanism (fastest option)
multi_accept on; # Accept multiple connections at once
}
http {
# Disable nginx version in error pages (security)
server_tokens off;
# Sendfile — kernel handles file transfer (much faster for static files)
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Timeouts
keepalive_timeout 65;
keepalive_requests 1000;
client_header_timeout 10s;
client_body_timeout 10s;
send_timeout 10s;
# Buffer sizes
client_body_buffer_size 128k;
client_max_body_size 50m; # Max upload size
proxy_buffer_size 4k;
proxy_buffers 8 4k;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6; # 1-9, 6 is good balance
gzip_min_length 256;
gzip_types
text/plain text/css text/xml text/javascript
application/json application/javascript application/xml
image/svg+xml;
}
Useful Commands
bash
# Test config syntax (always before reload!)
sudo nginx -t
# Reload without dropping connections
sudo nginx -s reload
# Full restart
sudo systemctl restart nginx
# View access log in real-time
sudo tail -f /var/log/nginx/access.log
# View error log
sudo tail -f /var/log/nginx/error.log
# Check which config files are loaded
sudo nginx -T | head -50
# Enable a site
sudo ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo nginx -s reload
# Disable a site
sudo rm /etc/nginx/sites-enabled/myapp.conf
sudo nginx -s reload
Interview Questions
What is the difference between a reverse proxy and a load balancer?
A reverse proxy sits in front of one or more servers and forwards client requests to them, hiding the backend from clients. A load balancer distributes incoming requests across multiple backend servers for scalability and high availability. Nginx does both — as a reverse proxy it handles SSL termination, caching, and header manipulation; as a load balancer it distributes traffic using algorithms like round-robin, least_conn, or ip_hash. In practice, the distinction is blurry because every production load balancer is also a reverse proxy.
What is the difference between proxy_pass http://backend/ and proxy_pass http://backend?
The trailing slash matters. With location /api/ and proxy_pass http://backend/, nginx strips /api/ from the URI — /api/users becomes /users on the backend. Without the slash, proxy_pass http://backend, nginx passes the full URI — /api/users remains /api/users. Most APIs want the trailing slash version to strip the path prefix.