Cybersecurity-Projects/PROJECTS/bug-bounty-platform/docs/research/NGINX.md

980 lines
27 KiB
Markdown

# Production-Grade Nginx Configuration: 2025 Best Practices
**Complete guide for modern full-stack applications (FastAPI + Frontend) with emphasis on WebSocket support, configuration organization, and dev/prod architectures.**
## Table of Contents
1. [Configuration File Organization](#configuration-file-organization)
2. [WebSocket Proxying](#websocket-proxying)
3. [Shared Configuration (http.conf)](#shared-configuration-httpconf)
4. [Development Configuration](#development-configuration-devnginx)
5. [Production Configuration](#production-configuration-prodnginx)
6. [Performance Optimization](#performance-optimization)
7. [Security Headers](#security-headers)
8. [Static File Serving](#static-file-serving-production)
9. [Vite Dev Server Integration](#vite-dev-server-integration-development)
10. [Load Balancing & Upstreams](#load-balancing--upstreams)
11. [Rate Limiting](#rate-limiting)
12. [Logging & Monitoring](#logging--monitoring)
13. [Complete Configuration Examples](#complete-configuration-examples)
---
## Configuration File Organization
### Recommended Structure
```
conf/nginx/
├── http.conf # Shared: upstreams, maps, global http settings
├── dev.nginx # Full nginx.conf for development
└── prod.nginx # Full nginx.conf for production
```
### What Goes Where
**http.conf (Shared Configurations)**:
- Upstream definitions (backend, frontend servers)
- Map directives for WebSocket connection upgrades
- Shared rate limit zones
- Common proxy settings
- MIME type definitions
- Log formats
**dev.nginx (Development-Specific)**:
- Full nginx.conf structure
- Includes http.conf
- Proxies to Vite dev server (port 5173)
- Proxies API/WebSocket to backend (port 8000)
- Verbose logging
- CORS permissive settings
- No caching
- No SSL (typically)
**prod.nginx (Production-Specific)**:
- Full nginx.conf structure
- Includes http.conf
- Serves static files from `/usr/share/nginx/html`
- Proxies API/WebSocket to Gunicorn workers
- SSL/TLS configuration
- Security headers
- Gzip/Brotli compression
- Static file caching
- Error logging only
---
## WebSocket Proxying
WebSocket connections require explicit handling because the "Upgrade" and "Connection" headers are hop-by-hop and not automatically passed to the proxied server. The modern approach uses a `map` directive to handle connections conditionally.
### Core WebSocket Configuration Pattern
```nginx
# In http.conf (shared configuration)
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
```
This sophisticated approach sets the Connection header to "close" when there's no Upgrade header, and to "upgrade" when WebSocket upgrade is requested.
### WebSocket Location Block
```nginx
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
# WebSocket-specific headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Standard proxy headers
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;
# Timeout settings for long-lived connections
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 75s;
# Disable buffering for real-time data
proxy_buffering off;
proxy_cache_bypass $http_upgrade;
}
```
### Critical WebSocket Settings Explained
For WebSocket session persistence with multiple backend servers, use `ip_hash` to ensure clients always connect to the same backend:
```nginx
upstream websocket_backend {
ip_hash; # Session persistence
server backend1:8000;
server backend2:8000;
server backend3:8000;
}
```
**Timeout Configuration**: By default, connections close if the proxied server doesn't transmit data within 60 seconds. For WebSockets:
- `proxy_read_timeout`: Set to 3600s (1 hour) or higher
- `proxy_send_timeout`: Set to 3600s (1 hour) or higher
- `proxy_connect_timeout`: Usually 75s is sufficient
---
## Shared Configuration (http.conf)
This file contains settings used by both dev and prod environments.
```nginx
# conf/nginx/http.conf
# WebSocket upgrade handling
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Upstream definitions
upstream backend {
# Development: single uvicorn instance
# Production: multiple gunicorn workers (override in prod.nginx)
server backend:8000 max_fails=3 fail_timeout=30s;
keepalive 32; # Connection pooling
}
upstream frontend_dev {
# Only used in development
server frontend:5173;
}
# Custom log format with timing information
log_format main_timed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
# Rate limit zones
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=1r/s;
# Connection limits
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# Common proxy settings
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;
```
---
## Development Configuration (dev.nginx)
Full nginx.conf optimized for local development with hot module replacement.
```nginx
# conf/nginx/dev.nginx
user nginx;
worker_processes 1; # Single worker sufficient for dev
error_log /var/log/nginx/error.log debug; # Verbose logging
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Include shared configuration
include /etc/nginx/http.conf;
# Development-specific settings
access_log /var/log/nginx/access.log main_timed;
sendfile off; # Disable for file system changes
tcp_nopush off;
tcp_nodelay on;
keepalive_timeout 65;
# Disable caching in development
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
# CORS permissive for development
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
server {
listen 80;
server_name localhost;
# Handle preflight requests
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
# API routes to backend
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 10;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
# Proxy buffering settings
proxy_buffering off;
proxy_request_buffering off;
}
# WebSocket route
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_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_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
# Vite dev server (with HMR WebSocket support)
location / {
proxy_pass http://frontend_dev;
proxy_http_version 1.1;
# Required for Vite HMR
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
# Vite-specific timeout
proxy_read_timeout 60s;
proxy_buffering off;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}
```
### Key Development Features:
1. **Verbose Logging**: `error_log debug` for troubleshooting
2. **No Caching**: Ensures fresh content on every request
3. **Permissive CORS**: Allows frontend to call backend freely
4. **HMR Support**: WebSocket headers (Upgrade and Connection) are required for Vite's Hot Module Replacement to function properly
5. **Disabled Optimizations**: `sendfile off` to catch file changes immediately
---
## Production Configuration (prod.nginx)
Optimized for performance, security, and reliability.
```nginx
# conf/nginx/prod.nginx
user nginx;
worker_processes auto; # One per CPU core
worker_rlimit_nofile 100000;
error_log /var/log/nginx/error.log warn; # Only warnings and errors
pid /var/run/nginx.pid;
events {
worker_connections 4096; # High concurrency support
use epoll; # Efficient connection handling on Linux
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Include shared configuration
include /etc/nginx/http.conf;
# Logging with buffering
access_log /var/log/nginx/access.log main_timed buffer=32k flush=5s;
# Performance optimizations
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 100;
types_hash_max_size 2048;
server_tokens off; # Hide nginx version
# File cache
open_file_cache max=10000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# Buffer sizes
client_body_buffer_size 128k;
client_header_buffer_size 16k;
client_max_body_size 10m;
large_client_header_buffers 4 16k;
# Timeouts
client_body_timeout 12s;
client_header_timeout 12s;
send_timeout 10s;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
gzip_disable "msie6";
gzip_min_length 256;
# Redirect HTTP to HTTPS
server {
listen 80;
server_name yourdomain.com;
return 301 https://$host$request_uri;
}
# Main HTTPS server
server {
listen 443 ssl http2;
server_name yourdomain.com;
# SSL configuration
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.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;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
# 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 Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Content Security Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss://yourdomain.com" always;
# Root directory for static files
root /usr/share/nginx/html;
index index.html;
# API routes with rate limiting
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 20;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
# Proxy buffering optimized for API
proxy_buffering on;
proxy_buffers 8 24k;
proxy_buffer_size 2k;
# Timeouts
proxy_connect_timeout 75s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
# Auth endpoints with stricter rate limiting
location /api/auth/ {
limit_req zone=auth_limit burst=5 nodelay;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# WebSocket route
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_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_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 75s;
proxy_buffering off;
}
# Static files with aggressive caching
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
location ~* \.(css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
location ~* \.(woff|woff2|ttf|eot|otf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# SPA fallback - all non-matching routes to index.html
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, must-revalidate";
}
# Health check
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
}
```
---
## Performance Optimization
### Worker Configuration
Running one worker process per CPU core works well in most cases, and setting worker_processes to auto achieves this:
```nginx
worker_processes auto;
worker_rlimit_nofile 100000; # File descriptor limit
events {
worker_connections 4096;
use epoll; # Most efficient on Linux
multi_accept on; # Accept multiple connections at once
}
```
### Connection and Request Handling
The default worker_connections is 512, but most systems can support higher values. The optimal setting depends on server resources and traffic patterns.
**Formula**: `max_clients = worker_processes * worker_connections`
### Buffer Optimization
If buffer sizes are too low, Nginx will write to temporary files, causing excessive disk I/O:
```nginx
client_body_buffer_size 128k;
client_header_buffer_size 16k;
client_max_body_size 10m;
large_client_header_buffers 4 16k;
```
### Keepalive Connections
Keepalive connections reduce CPU and network overhead by keeping connections open longer:
```nginx
keepalive_timeout 65;
keepalive_requests 100;
upstream backend {
server backend:8000;
keepalive 32; # Idle connections to upstream
}
```
### Sendfile and TCP Optimizations
The sendfile() system call enables zero-copy data transfer, speeding up TCP transmissions without consuming CPU cycles:
```nginx
sendfile on;
tcp_nopush on; # Send headers in one packet
tcp_nodelay on; # Disable Nagle's algorithm
```
### File Caching
```nginx
open_file_cache max=10000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
```
### Compression
```nginx
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6; # Balance between CPU and compression
gzip_min_length 256;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
```
**Important**: Don't increase compression level too high, as it costs CPU effort without proportional throughput gains.
---
## Security Headers
### Essential Security Headers (2025)
Security headers are levers that slash XSS risk, lock browsers to HTTPS, tame third-party scripts, and protect users without touching app code.
```nginx
# HTTP Strict Transport Security (HSTS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Prevent MIME sniffing
add_header X-Content-Type-Options "nosniff" always;
# XSS Protection (legacy but still useful)
add_header X-XSS-Protection "1; mode=block" always;
# Referrer Policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Disable dangerous browser features
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
```
### Content Security Policy (CSP)
CSP controls where scripts, styles, images, frames, and connections can load from. Start with a restrictive policy:
```nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss://yourdomain.com" always;
```
**Best Practice**: Roll out CSP in Report-Only mode first, sending violation reports to an endpoint you control:
```nginx
# Test mode
add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report" always;
```
### HSTS Explained
HSTS forces browsers to only use HTTPS by caching this policy for the max-age period. The `includeSubDomains` directive applies the policy to all subdomains.
**Preloading**: To add your domain to the browser preload list, include "preload" in the header and submit to hstspreload.org. This is a one-way decision—removal is difficult.
```nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
```
### SSL/TLS Best Practices
```nginx
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:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
```
---
## Static File Serving (Production)
### Cache Headers Strategy
Use Cache-Control on static files so that CDNs and browsers can cache them effectively. The modern approach favors `Cache-Control` over `Expires`.
```nginx
# Images, fonts, media - long cache
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|avif|woff|woff2|ttf|eot|otf|mp4|mp3|ogg|webm)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# CSS and JavaScript - long cache with versioning
location ~* \.(css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# HTML files - no caching
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
}
```
### Cache Busting
When files are updated, use cache-busting by appending version numbers to filenames (e.g., `style.css?v=2` or `style.v2.css`).
### SPA Routing
For Single Page Applications, route all non-file requests to index.html:
```nginx
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, must-revalidate";
}
```
---
## Vite Dev Server Integration (Development)
### HMR WebSocket Support
Vite's HMR requires WebSocket support with Upgrade and Connection headers set for the WebSocket connection to function:
```nginx
location / {
proxy_pass http://frontend:5173;
proxy_http_version 1.1;
# Critical for Vite HMR
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
# Timeouts
proxy_read_timeout 60s;
proxy_buffering off;
}
```
### Vite HMR Path Handling
If Vite uses a custom HMR path:
```nginx
location ~* /__vite_hmr {
proxy_pass http://frontend:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
```
### Common Vite + Nginx Issues
1. **HMR not working**: Ensure WebSocket headers are set correctly
2. **Too many redirects**: Use `proxy_pass http://host;` without trailing slash to avoid path manipulation
3. **Connection timeout**: Default proxy timeouts may be too short; increase to 30-60 seconds for WebSocket connections
---
## Load Balancing & Upstreams
### Upstream Configuration
```nginx
upstream backend {
least_conn; # Route to server with fewest connections
server backend1:8000 max_fails=3 fail_timeout=30s;
server backend2:8000 max_fails=3 fail_timeout=30s;
server backend3:8000 backup; # Fallback server
keepalive 32; # Connection pooling
keepalive_requests 100;
keepalive_timeout 60s;
}
```
### Load Balancing Methods
- `round_robin` (default): Distribute requests evenly
- `least_conn`: Route to server with fewest active connections
- `ip_hash`: Consistent routing based on client IP (session persistence)
- `hash $request_uri consistent`: Route based on URI
### Health Checks (Nginx Plus)
```nginx
upstream backend {
zone backend 64k;
server backend1:8000;
server backend2:8000;
health_check interval=5s fails=3 passes=2;
}
```
---
## Rate Limiting
### Zone Definitions
```nginx
# In http block
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=1r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
```
### Application in Locations
```nginx
# General API rate limiting
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 20;
proxy_pass http://backend;
}
# Stricter for authentication
location /api/auth/ {
limit_req zone=auth_limit burst=5 nodelay;
proxy_pass http://backend;
}
```
**Parameters**:
- `rate`: Requests per second (or `r/m` for per minute)
- `burst`: Allow temporary burst above rate
- `nodelay`: Process burst requests immediately
- `limit_conn`: Max simultaneous connections
---
## Logging & Monitoring
### Custom Log Format with Timing
```nginx
log_format main_timed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
```
### Buffered Logging for Performance
Logging every request directly to disk is expensive; buffering reduces write operations:
```nginx
access_log /var/log/nginx/access.log main_timed buffer=32k flush=5s;
```
### Conditional Logging
```nginx
# Don't log health checks
location /health {
access_log off;
return 200 "healthy\n";
}
# Log only errors for static files
location ~* \.(jpg|png|css|js)$ {
access_log off;
error_log /var/log/nginx/static_error.log;
}
```
---
## Complete Configuration Examples
### Docker Compose Integration
```yaml
# docker-compose.prod.yml
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./conf/nginx/http.conf:/etc/nginx/http.conf:ro
- ./conf/nginx/prod.nginx:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
- nginx_cache:/var/cache/nginx
depends_on:
- backend
networks:
- app_network
backend:
build:
context: ./backend
dockerfile: Dockerfile.prod
expose:
- "8000"
networks:
- app_network
volumes:
nginx_cache:
networks:
app_network:
```
### Minimal Dev Configuration
```nginx
# Absolute minimum for development
http {
include mime.types;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream backend {
server backend:8000;
}
server {
listen 80;
location /api/ {
proxy_pass http://backend;
}
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
}
location / {
proxy_pass http://frontend:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
}
```
---
## Key Takeaways
### WebSocket Requirements
1. Map `$http_upgrade` to `$connection_upgrade`
2. Set `proxy_http_version 1.1`
3. Set headers: `Upgrade $http_upgrade` and `Connection $connection_upgrade`
4. Increase timeouts: `proxy_read_timeout 3600s`
5. Disable buffering: `proxy_buffering off`
### Dev vs Prod Differences
- **Dev**: Verbose logging, no caching, permissive CORS, proxy to Vite
- **Prod**: Error logging only, aggressive caching, strict security headers, serve static files
### File Organization
- **http.conf**: Upstreams, maps, shared settings
- **dev.nginx**: Full config optimized for development
- **prod.nginx**: Full config optimized for production
### Performance Priorities
1. Worker processes = CPU cores (`worker_processes auto`)
2. High worker connections (4096+)
3. Enable `sendfile`, `tcp_nopush`, `tcp_nodelay`
4. Buffer optimization to avoid disk I/O
5. Keepalive connections for upstreams
### Security Essentials
1. HSTS with preload for HTTPS enforcement
2. Comprehensive CSP to prevent XSS
3. X-Frame-Options to prevent clickjacking
4. Rate limiting on API endpoints
5. Hide server version (`server_tokens off`)
### Static File Serving
1. Long cache for assets (1 year with `immutable`)
2. No cache for HTML files
3. Use cache busting with versioned filenames
4. SPA fallback with `try_files $uri /index.html`