# Team Tryouts - Production Nginx Configuration (Windows) # # This configuration provides: # - HTTP to HTTPS redirect # - TLS 1.2/1.3 with strong ciphers # - HSTS enforcement # - Request size limits # - gzip compression # - Proxy to Waitress (Flask) # - Security headers (reinforced at reverse proxy level) worker_processes auto; events { worker_connections 1024; multi_accept on; } http { # ========================================================================= # Basic Settings # ========================================================================= server_tokens off; # Hide Nginx version sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; client_max_body_size 16M; # Max upload size (matches Flask MAX_CONTENT_LENGTH) client_body_buffer_size 128k; client_header_buffer_size 1k; large_client_header_buffers 4 8k; include mime.types; default_type application/octet-stream; # ========================================================================= # Logging # ========================================================================= access_log logs/access.log; error_log logs/error.log warn; # ========================================================================= # Gzip Compression # ========================================================================= gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_min_length 256; gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml application/rss+xml image/svg+xml font/ttf font/otf; # ========================================================================= # HTTP → HTTPS Redirect # ========================================================================= server { listen 80; server_name _; # Redirect all HTTP traffic to HTTPS return 301 https://$host$request_uri; } # ========================================================================= # HTTPS Server # ========================================================================= server { listen 443 ssl http2; server_name _; # --------------------------------------------------------------------- # SSL/TLS Configuration # --------------------------------------------------------------------- # Paths to SSL certificate and key (update these for your deployment) ssl_certificate C:/nginx/certs/fullchain.pem; ssl_certificate_key C:/nginx/certs/privkey.pem; # Strong TLS configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; # SSL session settings ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_session_tickets off; # OCSP Stapling (uncomment when running on a proper domain) # ssl_stapling on; # ssl_stapling_verify on; # ssl_trusted_certificate C:/nginx/certs/chain.pem; # Diffie-Hellman parameters (generate with: openssl dhparam -out dhparam.pem 2048) # ssl_dhparam C:/nginx/certs/dhparam.pem; # --------------------------------------------------------------------- # Security Headers (defense-in-depth with Flask's own headers) # --------------------------------------------------------------------- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; # X-XSS-Protection intentionally omitted: deprecated, removed from # current browsers, and harmful in its last implementations. add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always; add_header Cross-Origin-Opener-Policy "same-origin" always; # --------------------------------------------------------------------- # Proxy to Waitress (Flask) # --------------------------------------------------------------------- location / { proxy_pass http://127.0.0.1:5000; # 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; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; # Timeouts proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; # Buffer settings proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 8k; } # --------------------------------------------------------------------- # Static Files (PERF-006) # # 59 KB of CSS and JS on every page load, previously proxied through # Waitress. Nginx serves them from disk instead. # # ADJUST THIS ONE PATH to the deployment's checkout, absolute, forward # slashes even on Windows. Nginx resolves a relative path against its # own install prefix, not against this file. The trailing slash on both # the location and the alias is required: without it /static/css/x.css # resolves one directory too high. # # `immutable` is safe here and only here: url_for('static', …) appends # ?v= (see version_static_urls in app/app.py), so a deployed file # is requested under a new URL and the cached copy of the old one is # never asked for again. Removing that stamp and leaving this block # gives every visitor a month-old stylesheet. # --------------------------------------------------------------------- location /static/ { alias C:/team-tryouts/app/static/; expires 30d; access_log off; # These three are repeated on purpose. In nginx, add_header is # inherited from the enclosing block ONLY when the current block # declares none of its own — one add_header here silently drops # every security header set at server level. Dropping nosniff on # the JavaScript is the one that matters. add_header Cache-Control "public, immutable"; add_header X-Content-Type-Options "nosniff" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # A missing static file must 404, not fall through to Flask: the # fallthrough would hide a broken deploy behind a working page. try_files $uri =404; } # --------------------------------------------------------------------- # Rate Limiting # --------------------------------------------------------------------- # Define rate limit zones (uncomment when rate limiting at Nginx level) # limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; # limit_req_zone $binary_remote_addr zone=global:10m rate=100r/m; # location /auth/login { # limit_req zone=login burst=5 nodelay; # proxy_pass http://127.0.0.1:5000; # } } }