RefPad

Nginx

Requires Nginx 1.26 (mainline branch)

Table of Contents

  1. Installation & Basic Operations
  2. Configuration File Structure
  3. Serving Static Files
  4. Reverse Proxy
  5. HTTPS (SSL/TLS)
  6. Redirects
  7. Load Balancer
  8. Security
  9. Logging
  10. Common Directives Reference

1. Installation & Basic Operations

Installation (Ubuntu)

sudo apt update
sudo apt install nginx
 
# Check version
nginx -v

Service 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 status

Configuration Test

sudo nginx -t                   # syntax check
sudo nginx -T                   # syntax check + print config

Always run nginx -t before reload.

Key File Paths

PathDescription
/etc/nginx/nginx.confMain configuration file
/etc/nginx/conf.d/*.confAdditional server block configs
/etc/nginx/sites-available/Virtual host configs (Debian-based)
/etc/nginx/sites-enabled/Enabled virtual hosts (symlinks)
/var/log/nginx/access.logAccess log
/var/log/nginx/error.logError 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;
  1. $uri → serve as a file if it exists
  2. $uri/ → serve the directory index if it exists
  3. /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_pass URL ends with /, the location prefix 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-run

Certbot 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  = 302

7. 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 -rn

10. Common Directives Reference

DirectiveDescription
listenPort and protocol to listen on
server_nameDomain name(s) for the virtual host
rootDocument root path
indexIndex filename
try_filesTry files/directories in order
proxy_passURL of the upstream server
returnReturn a status code and URL
rewriteRewrite URL with a regex
includeLoad another config file
add_headerAdd a response header
expiresSet Cache-Control / Expires header
gzip onEnable gzip compression
client_max_body_sizeMaximum request body size
keepalive_timeoutKeep-alive connection timeout
upstreamDefine a group of backend servers
locationRules 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)