# ============================================================================= # AI Software Factory — hardened nginx reverse proxy # # Purpose: single entry point in front of the docker-compose app stack # (web :3000, api :4000, realtime :4001). See README.md in this directory. # # Layout: # :80 → redirect everything to https # :443 → TLS termination, then: # / → web (Next.js) # /api/ → api (NestJS, rate-limited, 512m upload cap) # /saml/ → api (SAML SP: metadata + start + ACS — TASK-0131) # /sso/ → api (OIDC RP: authorize + callback + start dispatcher) # /ws → realtime (WebSocket upgrade, 1h read timeout) # /health → 200 (LB health probe, does not touch the apps) # ============================================================================= user nginx; worker_processes auto; # Failures logged with a pid so they can be correlated. error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 4096; multi_accept on; } http { include /etc/nginx/mime.types; default_type application/octet-stream; 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 buffer=32k flush=5s; # --- Performance --------------------------------------------------------- sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; keepalive_requests 10000; types_hash_max_size 2048; server_tokens off; # do not advertise nginx version client_body_timeout 120s; send_timeout 120s; client_max_body_size 512m; # file uploads via /api/ (see location) client_body_buffer_size 128k; # --- Gzip ------------------------------------------------------------------ # Note: for the web location we proxy gzip on to Next.js (which already # compresses); the rules below cover static responses and /api JSON. gzip on; gzip_vary on; gzip_min_length 1024; gzip_proxied any; gzip_comp_level 5; gzip_types text/plain text/css text/xml text/javascript application/javascript application/x-javascript application/json application/xml application/wasm image/svg+xml font/ttf font/otf font/woff font/woff2; # --- Proxy defaults (inherited by all locations) --------------------------- 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 X-Forwarded-Host $host; proxy_set_header Connection ""; proxy_connect_timeout 5s; proxy_send_timeout 60s; proxy_read_timeout 60s; proxy_buffering on; proxy_buffers 16 32k; proxy_busy_buffers_size 64k; # --- Rate limit zones -------------------------------------------------------- # api: 30 req/s per client IP (burst 60, nodelay) — normal browsing + # editor autosave stays well under this; abuse does not. # auth: 10 req/min per client IP for /api/v1/auth/* — registration, # login, password reset, refresh. Complements (does not replace) # the API's own rate limiting; this is the edge tripwire. limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s; limit_req_zone $binary_remote_addr zone=auth:10m rate=10r/m; limit_req_status 429; # Concurrent connection cap per IP (hammer protection for /ws). limit_conn_zone $binary_remote_addr zone=connperip:10m; limit_conn_status 429; # --- Upstreams ---------------------------------------------------------------- # docker-compose service names resolve on the compose network when nginx # runs as a compose service; for a host-level nginx in front of # localhost-published ports, swap the names for 127.0.0.1 (the README # shows the exact diff). upstream web_upstream { server web:3000; keepalive 32; } upstream api_upstream { server api:4000; keepalive 32; } upstream realtime_upstream { # Long-lived WS connections: generous timeouts. server realtime:4001; keepalive 16; keepalive_requests 0; } # --- Security headers (applied to every response) ----------------------------- # # Why these (and nothing more exotic): # CSP default-src 'self' — no third-party scripts/styles/fonts by # default; every factory surface is same-origin. # script-src 'self' — Next.js bundles are same-origin. (Inline # scripts are NOT allowed: Next.js inlines # nothing by default in production.) # style-src 'self' 'unsafe-inline' — Next.js inline