Nginx
Requires Nginx 1.26 (mainline branch)
Table of Contents
- Installation & Basic Operations
- Configuration File Structure
- Serving Static Files
- Reverse Proxy
- HTTPS (SSL/TLS)
- Redirects
- Load Balancer
- Security
- Logging
- Common Directives Reference
1. Installation & Basic Operations
Installation (Ubuntu)
sudo apt update
sudo apt install nginx
# Check version
nginx -vService Control
sudo systemctl start nginx # start
sudo systemctl stop nginx # stop
sudo systemctl restart nginx # restart (after config changes)
sudo systemctl reload nginx # graceful reload (recommended)
sudo systemctl enable nginx # enable on boot
sudo systemctl status nginx # check statusConfiguration Test
sudo nginx -t # syntax check
sudo nginx -T # syntax check + print configAlways run
nginx -tbeforereload.
Key File Paths
| Path | Description |
|---|---|
/etc/nginx/nginx.conf | Main configuration file |
/etc/nginx/conf.d/*.conf | Additional server block configs |
/etc/nginx/sites-available/ | Virtual host configs (Debian-based) |
/etc/nginx/sites-enabled/ | Enabled virtual hosts (symlinks) |
/var/log/nginx/access.log | Access log |
/var/log/nginx/error.log | Error log |
/var/www/html/ | Default document root |
2. Configuration File Structure
# /etc/nginx/nginx.conf
# Number of worker processes (auto = number of CPU cores)
worker_processes auto;
events {
worker_connections 1024; # max connections per worker
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# Load individual server configs
include /etc/nginx/conf.d/*.conf;
}Context Hierarchy
main
└── events
└── http
└── server ← virtual host
└── location ← URL path rules
3. Serving Static Files
# /etc/nginx/conf.d/static.conf
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Cache images, CSS, JS
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}How try_files Works
try_files $uri $uri/ /index.html;$uri→ serve as a file if it exists$uri/→ serve the directory index if it exists/index.html→ fallback (required for SPAs)
4. Reverse Proxy
Proxy requests to an app server (Node.js, Python, Ruby, etc.).
# /etc/nginx/conf.d/app.conf
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000; # app port
# Proxy headers (let the app know the real IP and protocol)
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;
# Timeouts
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}Rewrite Path on Proxy
# /api/v1/users → http://backend:8080/users (strip /api/v1/)
location /api/v1/ {
proxy_pass http://backend:8080/; # trailing slash strips the location prefix
}When
proxy_passURL ends with/, thelocationprefix is stripped from the path.
WebSocket Support
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}5. HTTPS (SSL/TLS)
Obtain a Certificate with Let's Encrypt (Certbot)
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
# Test auto-renewal
sudo certbot renew --dry-runCertbot automatically rewrites nginx.conf. For manual configuration:
HTTPS Config Example
server {
listen 80;
server_name example.com www.example.com;
# HTTP → HTTPS redirect
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS versions (allow only TLS 1.2 / 1.3)
ssl_protocols TLSv1.2 TLSv1.3;
# Recommended cipher suites
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;
# HSTS (tell browsers to refuse plain HTTP for this domain)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}6. Redirects
# 301 (permanent) — redirect www to non-www
server {
listen 80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
# 302 (temporary)
location /old-path {
return 302 /new-path;
}
# Redirect a path to an external URL
location /docs {
return 301 https://docs.example.com;
}
# Rewrite path with regex
rewrite ^/blog/(.*)$ /posts/$1 permanent; # permanent = 301
rewrite ^/old$ /new redirect; # redirect = 3027. Load Balancer
http {
upstream backend {
# Default: round-robin
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}Load Balancing Algorithms
upstream backend {
least_conn; # route to server with fewest connections (recommended)
# ip_hash; # same IP always goes to same server (session persistence)
# random; # random
server 10.0.0.1:3000 weight=3; # weighted round-robin
server 10.0.0.2:3000 weight=1;
server 10.0.0.3:3000 backup; # used only when all others are down
}Health Checks
upstream backend {
server 10.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 10.0.0.2:3000 max_fails=3 fail_timeout=30s;
}8. Security
server {
# Hide Nginx version from response headers
server_tokens off;
# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# XSS filter
add_header X-XSS-Protection "1; mode=block" always;
# Prevent MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;
# Referrer policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Restrict access to specific IPs
location /admin {
allow 203.0.113.0/24;
deny all;
}
# Limit upload size
client_max_body_size 10m;
# Allow only specific HTTP methods
if ($request_method !~ ^(GET|POST|PUT|DELETE|OPTIONS)$) {
return 405;
}
}Rate Limiting
http {
# Allow max 10 requests/second per IP
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
# burst: allow up to 20 requests in excess temporarily
}
}
}9. Logging
Custom Log Format
http {
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time'; # include response time
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn; # warn / error / crit
}Disable Logging for a Path
location /health {
access_log off;
return 200 "OK";
}View Logs in Real Time
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
# Count by status code
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn10. Common Directives Reference
| Directive | Description |
|---|---|
listen | Port and protocol to listen on |
server_name | Domain name(s) for the virtual host |
root | Document root path |
index | Index filename |
try_files | Try files/directories in order |
proxy_pass | URL of the upstream server |
return | Return a status code and URL |
rewrite | Rewrite URL with a regex |
include | Load another config file |
add_header | Add a response header |
expires | Set Cache-Control / Expires header |
gzip on | Enable gzip compression |
client_max_body_size | Maximum request body size |
keepalive_timeout | Keep-alive connection timeout |
upstream | Define a group of backend servers |
location | Rules for URL paths (prefix / exact / regex) |
location Matching Priority
location = /exact { ... } # 1. Exact match (highest priority)
location ^~ /prefix/ { ... } # 2. Prefix match (beats regex)
location ~* \.(php)$ { ... } # 3. Regex match (case-insensitive)
location ~ \.php$ { ... } # 3. Regex match (case-sensitive)
location / { ... } # 4. Prefix match (last resort)