Nginx powers a significant portion of the internet. These configurations will help you move from default installs to production-ready setups.

Basic Structure

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # Logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    
    # Performance
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    
    # Gzip
    gzip on;
    gzip_types text/plain text/css application/json application/javascript;
    
    # Virtual hosts
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Basic Server Block

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# /etc/nginx/sites-available/example.com
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;
    }
}

SSL/TLS Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com;
    
    # Certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    # Modern SSL configuration
    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 caching
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
    
    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    
    root /var/www/example.com;
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://$server_name$request_uri;
}

Reverse Proxy

Basic Proxy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
server {
    listen 80;
    server_name api.example.com;
    
    location / {
        proxy_pass http://localhost:3000;
        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

1
2
3
4
5
6
7
8
location /ws {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 86400;
}

Proxy with Buffering Control

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
location /api {
    proxy_pass http://backend;
    
    # Disable buffering for streaming
    proxy_buffering off;
    
    # Or tune buffer sizes
    proxy_buffer_size 4k;
    proxy_buffers 8 4k;
    proxy_busy_buffers_size 8k;
    
    # Timeouts
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
}

Load Balancing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
upstream backend {
    # Round robin (default)
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000;
    
    # Weighted
    server 10.0.0.1:3000 weight=3;
    server 10.0.0.2:3000 weight=1;
    
    # Backup server
    server 10.0.0.4:3000 backup;
    
    # Health checks
    server 10.0.0.1:3000 max_fails=3 fail_timeout=30s;
}

# Alternative algorithms
upstream backend_least_conn {
    least_conn;
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

upstream backend_ip_hash {
    ip_hash;  # Session persistence
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

Caching

Proxy Cache

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
http {
    # Define cache zone
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m 
                     max_size=1g inactive=60m use_temp_path=off;
    
    server {
        location / {
            proxy_pass http://backend;
            proxy_cache my_cache;
            proxy_cache_valid 200 60m;
            proxy_cache_valid 404 1m;
            proxy_cache_use_stale error timeout updating;
            
            # Cache key
            proxy_cache_key "$scheme$request_method$host$request_uri";
            
            # Add header to show cache status
            add_header X-Cache-Status $upstream_cache_status;
        }
        
        # Bypass cache for specific requests
        location /api {
            proxy_pass http://backend;
            proxy_cache my_cache;
            proxy_cache_bypass $http_authorization;
            proxy_no_cache $http_authorization;
        }
    }
}

Static File Caching

1
2
3
4
5
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
    access_log off;
}

Rate Limiting

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
http {
    # Define rate limit zones
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;
    
    server {
        # Apply to API endpoints
        location /api/ {
            limit_req zone=api_limit burst=20 nodelay;
            proxy_pass http://backend;
        }
        
        # Stricter limit for login
        location /login {
            limit_req zone=login_limit burst=5;
            proxy_pass http://backend;
        }
    }
}

Connection Limiting

1
2
3
4
5
6
7
http {
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
    
    server {
        limit_conn conn_limit 10;  # Max 10 connections per IP
    }
}

Security Headers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
server {
    # Security headers
    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 Content-Security-Policy "default-src 'self';" always;
    
    # HSTS (only enable after confirming SSL works)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # Hide nginx version
    server_tokens off;
    
    # Prevent access to hidden files
    location ~ /\. {
        deny all;
    }
}

Access Control

IP-based

1
2
3
4
5
6
7
location /admin {
    allow 10.0.0.0/8;
    allow 192.168.1.0/24;
    deny all;
    
    proxy_pass http://backend;
}

Basic Authentication

1
2
3
4
5
6
location /admin {
    auth_basic "Admin Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
    
    proxy_pass http://backend;
}

Create password file:

1
htpasswd -c /etc/nginx/.htpasswd admin

URL Rewriting

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
server {
    # Redirect old URLs
    location /old-page {
        return 301 /new-page;
    }
    
    # Rewrite internally
    rewrite ^/blog/(\d+)/(.*)$ /posts/$1-$2 last;
    
    # Remove trailing slash
    rewrite ^/(.*)/$ /$1 permanent;
    
    # Force trailing slash
    rewrite ^([^.]*[^/])$ $1/ permanent;
}

Serving Static Files

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
server {
    root /var/www/myapp;
    
    # Try static file, then proxy to app
    location / {
        try_files $uri $uri/ @backend;
    }
    
    location @backend {
        proxy_pass http://localhost:3000;
    }
    
    # Serve static assets directly
    location /static/ {
        alias /var/www/myapp/static/;
        expires 30d;
    }
}

Logging

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
http {
    # Custom log format
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    'rt=$request_time uct=$upstream_connect_time '
                    'uht=$upstream_header_time urt=$upstream_response_time';
    
    # JSON format for log aggregation
    log_format json escape=json '{'
        '"time":"$time_iso8601",'
        '"remote_addr":"$remote_addr",'
        '"method":"$request_method",'
        '"uri":"$uri",'
        '"status":$status,'
        '"body_bytes_sent":$body_bytes_sent,'
        '"request_time":$request_time,'
        '"upstream_response_time":"$upstream_response_time"'
    '}';
    
    access_log /var/log/nginx/access.log main;
    
    server {
        # Per-server logging
        access_log /var/log/nginx/example.access.log json;
        error_log /var/log/nginx/example.error.log warn;
        
        # Disable logging for health checks
        location /health {
            access_log off;
            return 200 "OK";
        }
    }
}

Common Patterns

Single Page Application

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
server {
    root /var/www/spa;
    index index.html;
    
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    location /api {
        proxy_pass http://backend:3000;
    }
}

PHP with FastCGI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
server {
    root /var/www/php-app;
    index index.php;
    
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Testing and Debugging

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Test configuration
nginx -t

# Reload configuration
nginx -s reload

# Check which config is loaded
nginx -T

# Debug with verbose logging
error_log /var/log/nginx/error.log debug;

Nginx configuration is declarative—you describe what you want, not how to get there. Start with a basic proxy, add SSL, then layer on caching, rate limiting, and security headers as needed.

Test every change with nginx -t before reloading. A syntax error in production is a bad day for everyone.