Installation
# Ubuntu/Debian
sudo apt update && sudo apt install nginx
# Start and enable
sudo systemctl start nginx
sudo systemctl enable nginx
# Verify
nginx -v
curl -I http://localhost
| Command | Description |
nginx -t | Test configuration syntax |
nginx -T | Test and dump full config |
nginx -s reload | Reload config without downtime |
nginx -s stop | Fast shutdown |
nginx -s quit | Graceful shutdown |
systemctl reload nginx | Reload via systemd |
Config Structure
# /etc/nginx/nginx.conf — main config
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 50M;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Gzip
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml;
# Virtual hosts
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
| Path | Purpose |
/etc/nginx/nginx.conf | Main configuration file |
/etc/nginx/sites-available/ | Virtual host configs (inactive) |
/etc/nginx/sites-enabled/ | Symlinks to active configs |
/etc/nginx/conf.d/ | Additional config files (auto-included) |
/var/log/nginx/ | Access and error logs |
/var/www/html/ | Default web root |
Server Blocks
Static site
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Cache static assets
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
Enable a site
# Create config
sudo nano /etc/nginx/sites-available/example.com
# Enable via symlink
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
# Test and reload
sudo nginx -t && sudo systemctl reload nginx
Location Matching
| Modifier | Type | Priority |
= | Exact match | 1 (highest) |
^~ | Prefix match (no regex check) | 2 |
~ | Case-sensitive regex | 3 |
~* | Case-insensitive regex | 3 |
| (none) | Prefix match | 4 (lowest) |
# Exact match — only /health
location = /health {
return 200 "OK";
}
# Prefix — starts with /api/
location /api/ {
proxy_pass http://backend;
}
# Regex — image files
location ~* \.(png|jpg|gif|svg)$ {
expires 7d;
}
# Prefix, skip regex — /static/ always wins over regex
location ^~ /static/ {
root /var/www;
}
Reverse Proxy
upstream backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
# Balancing methods: round-robin (default), least_conn, ip_hash
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend;
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
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}
SSL/TLS
Let's Encrypt with Certbot
# Install Certbot
sudo apt install certbot python3-certbot-nginx
# Obtain and auto-configure
sudo certbot --nginx -d example.com -d www.example.com
# Auto-renewal (cron is set up automatically)
sudo certbot renew --dry-run
Production SSL config
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Modern TLS settings
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;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
}
# HTTP to HTTPS redirect
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
Rate Limiting
# Define rate limit zone in http {} block
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
server {
# API — allow burst of 20 requests, no delay for first 10
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
# Login — strict rate limit
location /login {
limit_req zone=login burst=5;
limit_req_status 429;
proxy_pass http://backend;
}
}
Caching
Proxy cache
# In http {} block
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:10m
max_size=1g inactive=60m use_temp_path=off;
server {
location / {
proxy_cache app_cache;
proxy_cache_valid 200 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
# Bypass cache for authenticated users
location /api/ {
proxy_cache app_cache;
proxy_cache_bypass $http_authorization;
proxy_no_cache $http_authorization;
proxy_pass http://backend;
}
}
Static file caching
location ~* \.(css|js|woff2|png|jpg|svg|ico)$ {
expires 365d;
add_header Cache-Control "public, immutable";
access_log off;
}
Common Patterns
SPA (Single Page Application)
server {
listen 80;
server_name app.example.com;
root /var/www/app/dist;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
}
}
Redirect www to non-www
server {
listen 80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
Custom error pages
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /404.html {
root /var/www/errors;
internal;
}
Block bad bots and scanners
location ~* /(wp-admin|wp-login|xmlrpc\.php|\.env|\.git) {
return 444;
}
Basic auth
# Generate password file
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd admin
# In location block
location /admin/ {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}