# nginx server config for the static site, baked into the image. # Base image: nginxinc/nginx-unprivileged (runs as uid 101, listens on 8080). # Read-only rootfs in k8s: /tmp and /var/cache/nginx are emptyDir mounts. server { listen 8080; server_name _; root /usr/share/nginx/html; index index.html; # Don't leak the nginx version. server_tokens off; # ---- Security headers (origin) ------------------------------------------- # These travel with the artifact. HSTS + HTTPS redirect are set at the # Cloudflare edge (the tunnel terminates TLS), so they are NOT set here. add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always; # ---- Content-Security-Policy --------------------------------------------- # FILLED IN M4: Astro computes the inline script/style hashes at build; the # final policy is emitted here as a header (a CSP can't set # frame-ancestors). Until then, a conservative baseline: add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always; # ---- Caching ------------------------------------------------------------- # Astro emits content-hashed assets under /_astro/ — cache them hard. location /_astro/ { expires 1y; add_header Cache-Control "public, immutable" always; } # HTML is revalidated so deploys show up immediately. location ~* \.html$ { add_header Cache-Control "no-cache" always; } # ---- Routing ------------------------------------------------------------- location / { try_files $uri $uri/ $uri.html =404; } error_page 404 /404.html; location = /404.html { internal; } # Compression gzip on; gzip_comp_level 6; gzip_min_length 1024; gzip_types text/plain text/css application/javascript application/json image/svg+xml application/xml application/rss+xml; gzip_vary on; }