# Secure External Load Balancer Configuration # Acts as the only externally exposed component # Rate limiting 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; # Security headers map map $scheme $hsts_header { https "max-age=31536000; includeSubDomains; preload"; } # Upstream to Traefik (internal only) upstream traefik_backend { server traefik:80 max_fails=3 fail_timeout=30s; server traefik:443 max_fails=3 fail_timeout=30s; keepalive 32; } # HTTP to HTTPS redirect server { listen 80 default_server; listen [::]:80 default_server; server_name _; # Security headers for HTTP add_header X-Frame-Options "DENY" 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; # Block common attack patterns location ~* \.(git|svn|htaccess|htpasswd)$ { deny all; return 444; } # Let's Encrypt ACME challenge location /.well-known/acme-challenge/ { proxy_pass http://traefik_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_connect_timeout 5s; proxy_send_timeout 5s; proxy_read_timeout 5s; } # Redirect everything else to HTTPS location / { return 301 https://$host$request_uri; } } # Main HTTPS server server { listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; server_name _; # SSL Configuration ssl_certificate /ssl/tls.crt; ssl_certificate_key /ssl/tls.key; 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:10m; ssl_session_timeout 1d; ssl_stapling on; ssl_stapling_verify on; # Security headers add_header Strict-Transport-Security $hsts_header always; add_header X-Frame-Options "DENY" 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' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' wss:; frame-ancestors 'none';" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), vr=(), accelerometer=(), gyroscope=(), magnetometer=(), ambient-light-sensor=(), encrypted-media=()" always; # Rate limiting limit_req zone=general burst=20 nodelay; # Block common attack patterns location ~* \.(git|svn|htaccess|htpasswd)$ { deny all; return 444; } # Block access to sensitive paths location ~ ^/(\.env|config\.yaml|secrets|admin) { deny all; return 444; } # Additional rate limiting for auth endpoints location ~ ^.*/auth { limit_req zone=login burst=5 nodelay; proxy_pass http://traefik_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 https; proxy_set_header X-Forwarded-Port 443; proxy_buffering off; proxy_connect_timeout 5s; proxy_send_timeout 5s; proxy_read_timeout 5s; } # Main proxy to Traefik location / { proxy_pass http://traefik_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 https; proxy_set_header X-Forwarded-Port 443; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Timeouts proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; # Buffering proxy_buffering off; proxy_request_buffering off; # Handle large uploads client_max_body_size 10G; proxy_max_temp_file_size 0; # Error handling for when Traefik is not available proxy_intercept_errors on; error_page 502 503 504 = @maintenance; } # Maintenance page when Traefik is down location @maintenance { return 503 '{"error": "Service temporarily unavailable", "message": "Traefik is starting up, please try again in a moment"}'; add_header Content-Type application/json; add_header Retry-After 30; } # Health check endpoint location /nginx-health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } } # Monitoring and logging log_format detailed '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' '$request_time $upstream_response_time ' '"$http_x_forwarded_for"'; access_log /var/log/nginx/access.log detailed; error_log /var/log/nginx/error.log warn;