Nginx is everywhere—reverse proxy, load balancer, static file server, SSL terminator. Here are the configuration patterns that work in production.

Base 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
35
36
37
38
39
40
41
42
43
44
45
46
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Logging
    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';

    access_log /var/log/nginx/access.log main;

    # Performance
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    # Compression
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json 
               application/javascript application/xml+rss 
               application/atom+xml image/svg+xml;

    # Security
    server_tokens off;

    include /etc/nginx/conf.d/*.conf;
}

Reverse Proxy

Basic Proxy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# /etc/nginx/conf.d/app.conf
upstream backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name 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;
        proxy_set_header Connection "";
        
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

Load Balancing

1
2
3
4
5
6
7
8
9
upstream backend {
    least_conn;  # or: round_robin, ip_hash, hash $request_uri
    
    server backend1.local:3000 weight=3;
    server backend2.local:3000 weight=2;
    server backend3.local:3000 backup;
    
    keepalive 32;
}

Health Checks (Commercial)

1
2
3
4
5
6
7
# nginx plus only
upstream backend {
    server backend1:3000;
    server backend2:3000;
    
    health_check interval=5s fails=3 passes=2;
}

SSL/TLS Configuration

Modern SSL Setup

 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
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    
    # Modern configuration
    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;
    
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/nginx/ssl/chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
    
    # Session caching
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;

    # ... rest of config
}

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

Let’s Encrypt with Certbot

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$server_name$request_uri;
    }
}

Security Headers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
server {
    # ... SSL config ...

    # 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'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
    
    # HSTS (be careful - hard to undo)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Hide server version
    server_tokens off;
}

Rate Limiting

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Define rate limit zones
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
    # General rate limit
    limit_req zone=general burst=20 nodelay;
    limit_conn addr 10;

    location /api/login {
        limit_req zone=login burst=5 nodelay;
        proxy_pass http://backend;
    }

    location /api/ {
        limit_req zone=general burst=50 nodelay;
        proxy_pass http://backend;
    }
}

Rate Limit Response

1
2
3
4
5
6
7
8
9
limit_req_status 429;
limit_conn_status 429;

error_page 429 /429.json;
location = /429.json {
    internal;
    default_type application/json;
    return 429 '{"error": "Too many requests"}';
}

Caching

Proxy Cache

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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 /api/ {
        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 http_504;
        proxy_cache_lock on;
        
        add_header X-Cache-Status $upstream_cache_status;
        
        proxy_pass http://backend;
    }
}

Static File Caching

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
location /static/ {
    alias /var/www/static/;
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
    expires 30d;
    add_header Cache-Control "public";
}

WebSocket Support

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {
    location /ws/ {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        
        proxy_read_timeout 86400;
    }
}

API Gateway Patterns

Path-Based Routing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
server {
    location /api/users {
        proxy_pass http://users-service;
    }

    location /api/orders {
        proxy_pass http://orders-service;
    }

    location /api/inventory {
        proxy_pass http://inventory-service;
    }
}

API Versioning

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
location /api/v1/ {
    proxy_pass http://api-v1/;
}

location /api/v2/ {
    proxy_pass http://api-v2/;
}

# Default to latest
location /api/ {
    proxy_pass http://api-v2/;
}

Request/Response Modification

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
location /api/ {
    # Add headers to upstream request
    proxy_set_header X-Request-ID $request_id;
    proxy_set_header X-Forwarded-Host $host;
    
    # Modify response headers
    proxy_hide_header X-Powered-By;
    add_header X-Request-ID $request_id;
    
    proxy_pass http://backend;
}

Static Site Hosting

 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
server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.html;

    # SPA routing - try file, then directory, then fall back to index
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Cache static assets
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Deny access to hidden files
    location ~ /\. {
        deny all;
    }

    # Custom error pages
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
}

Debugging

Debug Logging

1
2
3
4
5
6
7
error_log /var/log/nginx/error.log debug;

# Or per-location
location /api/ {
    error_log /var/log/nginx/api-debug.log debug;
    # ...
}

Request Inspection

1
2
3
4
location /debug {
    default_type text/plain;
    return 200 "Request: $request\nHost: $host\nURI: $uri\nArgs: $args\nRemote: $remote_addr\n";
}

Test Configuration

1
2
3
4
nginx -t                    # Test config syntax
nginx -T                    # Test and dump full config
nginx -s reload            # Reload configuration
nginx -s reopen            # Reopen log files

Complete Production Example

 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
upstream api {
    least_conn;
    server api1:3000 weight=2;
    server api2:3000 weight=2;
    server api3:3000 backup;
    keepalive 32;
}

limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
proxy_cache_path /var/cache/nginx/api levels=1:2 keys_zone=api_cache:10m max_size=1g;

server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;

    # Security
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Strict-Transport-Security "max-age=31536000" always;

    # Static files
    location /static/ {
        alias /var/www/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # API
    location /api/ {
        limit_req zone=api burst=50 nodelay;
        
        proxy_cache api_cache;
        proxy_cache_valid 200 1m;
        add_header X-Cache-Status $upstream_cache_status;

        proxy_pass http://api;
        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 "";
    }

    # SPA
    location / {
        root /var/www/html;
        try_files $uri $uri/ /index.html;
    }
}

Nginx configuration is declarative and predictable once you understand the patterns. Start with a solid base, add features incrementally, and always test with nginx -t before reloading. The patterns above cover 95% of production use cases—adapt them to your needs.