Nginx as a Secure Reverse Proxy

Nginx as a Secure Reverse Proxy

Configure Nginx as a security-focused reverse proxy:

# /etc/nginx/sites-available/secure-reverse-proxy
upstream backend_servers {
    # Define backend servers with health checks
    server backend1.internal:8080 max_fails=3 fail_timeout=30s;
    server backend2.internal:8080 max_fails=3 fail_timeout=30s;
    server backend3.internal:8080 max_fails=3 fail_timeout=30s backup;
    
    # Connection settings
    keepalive 32;
    keepalive_requests 100;
    keepalive_timeout 60s;
}

# Rate limiting zones
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_conn_zone $binary_remote_addr zone=addr:10m;

# GeoIP blocking (optional)
# geoip_country /usr/share/GeoIP/GeoIP.dat;
# map $geoip_country_code $allowed_country {
#     default yes;
#     CN no;
#     RU no;
# }

# Security headers map
map $upstream_http_content_type $security_headers {
    default "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';";
    ~*text/html "default-src 'self'; script-src 'self' 'nonce-$request_id'; style-src 'self' 'nonce-$request_id'; object-src 'none'; base-uri 'self';";
}

server {
    listen 443 ssl http2;
    server_name example.com;
    
    # SSL configuration
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    
    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;
    
    # Security settings
    client_max_body_size 10M;
    client_body_buffer_size 128k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;
    
    # Timeouts
    client_body_timeout 10s;
    client_header_timeout 10s;
    send_timeout 10s;
    proxy_connect_timeout 5s;
    proxy_send_timeout 10s;
    proxy_read_timeout 10s;
    
    # Hide backend details
    proxy_hide_header X-Powered-By;
    proxy_hide_header Server;
    more_clear_headers Server;
    
    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    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 $security_headers always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
    
    # Main location block
    location / {
        # Rate limiting
        limit_req zone=general burst=20 nodelay;
        limit_conn addr 10;
        
        # Country blocking (if GeoIP enabled)
        # if ($allowed_country = no) {
        #     return 403;
        # }
        
        # Security checks
        if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|OPTIONS)$) {
            return 405;
        }
        
        # Block suspicious user agents
        if ($http_user_agent ~* (bot|crawl|spider|scraper|scan)) {
            return 403;
        }
        
        # Proxy settings
        proxy_pass http://backend_servers;
        proxy_http_version 1.1;
        
        # Headers to backend
        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 X-Request-ID $request_id;
        
        # Connection reuse
        proxy_set_header Connection "";
        
        # Buffering settings
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        proxy_busy_buffers_size 8k;
        
        # Cache settings (if applicable)
        proxy_cache_bypass $http_upgrade;
        proxy_no_cache $http_pragma $http_authorization;
    }
    
    # API endpoint with stricter limits
    location /api/ {
        limit_req zone=api burst=10 nodelay;
        limit_conn addr 5;
        
        # Additional API security
        if ($http_content_type !~* "application/json") {
            return 415;
        }
        
        proxy_pass http://backend_servers;
        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-API-Key $http_x_api_key;
        
        # API-specific timeouts
        proxy_read_timeout 30s;
    }
    
    # Login endpoint with strict rate limiting
    location /login {
        limit_req zone=login burst=5 nodelay;
        limit_conn addr 2;
        
        # Additional login security
        if ($request_method != POST) {
            return 405;
        }
        
        proxy_pass http://backend_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # Health check endpoint (no rate limiting)
    location /health {
        access_log off;
        proxy_pass http://backend_servers;
        proxy_set_header Host $host;
    }
    
    # Block access to sensitive paths
    location ~ /\.(git|svn|env|config|htaccess) {
        deny all;
        return 404;
    }
    
    # Custom error pages
    error_page 403 /errors/403.html;
    error_page 404 /errors/404.html;
    error_page 500 502 503 504 /errors/50x.html;
    
    location ^~ /errors/ {
        internal;
        root /usr/share/nginx/html;
    }
}