Nginx Quick Reference

Config structure, server blocks, reverse proxy, SSL/TLS, caching and security headers.

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
CommandDescription
nginx -tTest configuration syntax
nginx -TTest and dump full config
nginx -s reloadReload config without downtime
nginx -s stopFast shutdown
nginx -s quitGraceful shutdown
systemctl reload nginxReload 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/*;
}
PathPurpose
/etc/nginx/nginx.confMain 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

ModifierTypePriority
=Exact match1 (highest)
^~Prefix match (no regex check)2
~Case-sensitive regex3
~*Case-insensitive regex3
(none)Prefix match4 (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;
    }
}

Security Headers

# Add to server {} or http {} block
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;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" always;
Always test CSP in report-only mode first: Content-Security-Policy-Report-Only

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;
}