# Nginx Configuration for Multi-Tenant Subdomain Routing # # Architecture: # - Each subdomain routes to a tenant-specific app instance # - Dynamic upstream selection based on subdomain # - Shared Redis caching for performance # - SSL termination with wildcard certificate # # DNS Setup Required: # *.example.com IN A # example.com IN A # # Certificate Setup: # - Wildcard SSL cert for *.example.com (required) # - Store as: certs/inventarsystem.crt & certs/inventarsystem.key # - Or use Let's Encrypt with DNS challenge for dynamic tenants # Redirect HTTP to HTTPS server { listen 80; server_name _; return 301 https://$host$request_uri; } # HTTPS - Multi-Tenant Upstream upstream inventar_backend { # Load balance across app instances # Docker-compose will auto-scale these server app:8000 max_fails=3 fail_timeout=30s; # Additional instances for scaling: # server app_2:8000 max_fails=3 fail_timeout=30s; # server app_3:8000 max_fails=3 fail_timeout=30s; # ... up to app_N keepalive 32; } # Main HTTPS server block server { listen 443 ssl; http2 on; server_name ~^(?[a-z0-9-]+)?\.?example\.com$; # SSL Configuration (wildcard certificate) ssl_certificate /etc/nginx/certs/inventarsystem.crt; ssl_certificate_key /etc/nginx/certs/inventarsystem.key; ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; ssl_session_tickets off; # Modern SSL protocol and ciphers 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; # HSTS add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # Security headers add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Performance optimizations client_max_body_size 50M; client_body_timeout 30s; client_header_timeout 30s; send_timeout 30s; # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json; gzip_comp_level 5; # Access/Error logs access_log /var/log/nginx/inventar_access.log combined buffer=32k; error_log /var/log/nginx/inventar_error.log warn; # Root location - proxy to app with tenant context location / { # Set tenant header for app context proxy_set_header X-Tenant-ID $tenant; 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 $server_name; proxy_set_header X-Forwarded-Port $server_port; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Timeouts for long-running requests proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 300s; proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 8k; # Caching for static responses proxy_cache_bypass $cookie_nocache $arg_nocache; proxy_no_cache $http_pragma $http_authorization; # Pass through to backend proxy_pass http://inventar_backend; } # Static asset caching (CSS, JS, images) location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { proxy_pass http://inventar_backend; expires 30d; add_header Cache-Control "public, immutable"; proxy_cache_valid 200 30d; } # QR Code caching location /QRCodes/ { proxy_pass http://inventar_backend; expires 7d; add_header Cache-Control "public, must-revalidate"; } # Upload folder (no caching, direct pass) location /uploads/ { proxy_pass http://inventar_backend; add_header Cache-Control "private, no-cache"; } # Health check endpoint (internal only) location /health { proxy_pass http://inventar_backend; access_log off; } # Deny access to admin panel on non-admin subdomains (optional security) location ~ ^/admin(.*)$ { # Only allow from admin subdomain if ($tenant != "admin") { return 403; } proxy_pass http://inventar_backend; } # Error pages error_page 500 502 503 504 /50x.html; location = /50x.html { default_type text/html; return 200 'Service Error

500 Internal Server Error

The service is temporarily unavailable. Please try again later.

'; } error_page 503 /503.html; location = /503.html { default_type text/html; return 503 'Service Unavailable

503 Service Unavailable

The service is under maintenance.

'; } # Rate limiting per tenant (optional) # Requires: limit_req_zone $tenant zone=tenant_limit:10m rate=10r/s; # location / { # limit_req zone=tenant_limit burst=20 nodelay; # proxy_pass http://inventar_backend; # } } # Management endpoint (localhost only, no tenant isolation) server { listen 8080; server_name localhost 127.0.0.1; location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; } location /health { access_log off; default_type text/plain; return 200 "OK"; } }