# nginx β annotated reference for the main config file.
# Default path: /etc/nginx/nginx.conf (site files in /etc/nginx/sites-enabled/ or conf.d/)
#
# Commands:
# nginx -t test config syntax
# nginx -s reload reload config (no downtime)
# nginx -s stop|quit|reopen stop / graceful stop / reopen log files
# systemctl reload nginx preferred on systemd hosts
#
# Directives live in contexts: main β events β http β server β location.
# Inner contexts inherit from outer; matching directive in inner wins.
user www-data; # worker process owner
worker_processes auto; # auto = one per CPU core
worker_rlimit_nofile 65535; # max open files per worker
pid /run/nginx.pid;
events {
worker_connections 4096; # concurrent connections per worker
multi_accept on; # accept all queued connections at once
use epoll; # Linux event method (kqueue on BSD)
}
http {
# βββ Basics βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
include /etc/nginx/mime.types;
default_type application/octet-stream;
charset utf-8;
server_tokens off; # hide nginx version in headers/errors
sendfile on; # zero-copy file sends
tcp_nopush on; # send headers in one packet
tcp_nodelay on; # disable Nagle for keepalive
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 25m; # max request body (uploads)
# βββ Logging ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time ut=$upstream_response_time';
access_log /var/log/nginx/access.log main buffer=32k flush=5s;
error_log /var/log/nginx/error.log warn;
# βββ Compression ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
gzip on;
gzip_vary on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript
image/svg+xml;
# βββ TLS (defaults applied to every https server) βββββββββββββββββββββββ
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;
ssl_session_tickets off;
# βββ Rate limiting ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
# βββ Upstream (load-balanced backend) βββββββββββββββββββββββββββββββββββ
upstream app_backend {
least_conn; # also: ip_hash, hash $key, random
server 127.0.0.1:8001 weight=2 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8002;
server 127.0.0.1:8003 backup; # used only if others fail
keepalive 32;
}
# βββ HTTP -> HTTPS redirect βββββββββββββββββββββββββββββββββββββββββββββ
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
# βββ A virtual host serving a static SPA + reverse-proxied API ββββββββββ
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
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;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
root /var/www/example;
index index.html;
# SPA: serve files, fall back to index.html
location / {
try_files $uri $uri/ /index.html;
}
# Long-cache hashed assets
location ~* \.(?:css|js|woff2|jpg|png|svg|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Reverse proxy to backend cluster
location /api/ {
limit_req zone=api burst=40 nodelay;
proxy_pass http://app_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;
proxy_set_header Connection ""; # enable keepalive to upstream
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
proxy_buffering on;
}
# WebSocket upgrade
location /ws/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 1h;
}
# Deny hidden files (.env, .git, β¦)
location ~ /\. { deny all; access_log off; log_not_found off; }
}
# Include per-site files
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
# βββ Location-matching cheatsheet ββββββββββββββββββββββββββββββββββββββββββββ
# location /path/ prefix match (longest prefix wins among prefixes)
# location = /path exact match (highest priority)
# location ^~ /path/ prefix; stop searching regex if matched
# location ~ pattern case-sensitive regex
# location ~* pattern case-insensitive regex
# Order: = ^~ ~/~* (first match wins among regex) prefix