server { listen 80; listen [::]:80; server_name chat.lotusguild.org; # Brotli compression (better than gzip for modern browsers) brotli on; brotli_static on; brotli_comp_level 6; brotli_types text/plain text/css application/javascript application/json image/svg+xml application/wasm font/woff2; root /var/www/html; server_tokens off; client_max_body_size 50m; limit_req zone=chat_limit burst=60 nodelay; limit_conn chat_conn 25; index index.html; # Security headers 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; # Block all source map files and dotfiles from public access location ~* \.(js|css)\.map$ { deny all; return 404; } location ~ /\. { deny all; return 404; } location = /netlify.toml { deny all; return 404; } # Content Security Policy add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://matrix.lotusguild.org https://matrix.org https://*.matrix.org https://mozilla.org https://mozilla.modular.im https://drive.lotusguild.org https://media.giphy.com https://media0.giphy.com https://media1.giphy.com https://media2.giphy.com https://media3.giphy.com https://media4.giphy.com https://www.openstreetmap.org https://tile.openstreetmap.org https://api.qrserver.com; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://matrix.lotusguild.org wss://matrix.lotusguild.org https://matrix.org https://*.matrix.org https://mozilla.org https://mozilla.modular.im https://chat.mozilla.org https://vector.im https://api.giphy.com https://*.giphy.com wss:; media-src 'self' https: blob:; frame-src 'self' https://www.openstreetmap.org; worker-src 'self' blob:; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always; # Service worker must never be cached so updates are picked up immediately location = /sw.js { expires -1; add_header Cache-Control "no-cache, no-store, must-revalidate" always; } # Cache content-addressed static assets aggressively location ~* \.(?:js|css|woff2?|png|svg|ico|webp)$ { expires 1y; add_header Cache-Control "public, immutable" always; } # Never cache HTML or JSON (index.html, config.json, manifest.json) location ~* \.(json|html)$ { expires -1; add_header Cache-Control "no-cache, no-store, must-revalidate" always; } # Auto-deploy webhook — proxied to local webhook service location = /hooks/lotus-deploy { proxy_pass http://127.0.0.1:9001/hooks/lotus-deploy; proxy_set_header Host $host; proxy_read_timeout 300; proxy_connect_timeout 5; } location / { rewrite ^/config\.json$ /config.json break; rewrite ^/manifest\.json$ /manifest.json break; rewrite ^/sw\.js$ /sw.js break; rewrite ^/pdf\.worker\.min\.js$ /pdf.worker.min.js break; rewrite ^/public/(.*)$ /public/$1 break; rewrite ^/assets/(.*)$ /assets/$1 break; rewrite ^(.+)$ /index.html break; } }