# nginx server block for bchanot.fr static site. # Container listens on port 80; host port is configured via docker-compose # (PORT env var). A host-level reverse proxy (nginx, Traefik, Caddy) should # terminate TLS and proxy_pass to http://127.0.0.1:${PORT}. server { listen 80; listen [::]:80; server_name _; root /usr/share/nginx/html; index index.html; # Security headers. HSTS is intentionally NOT set here — leave it to the # outer reverse proxy that terminates TLS, otherwise it may be sent over # plain HTTP between proxy and container. add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always; # CSP: inline CSS + JS are allowed (project convention), fonts from Google. add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self' 'unsafe-inline'; img-src 'self' data:; base-uri 'self'; form-action 'self'; frame-ancestors 'self'" always; # Forwarded headers — trust the upstream reverse proxy. real_ip_header X-Forwarded-For; set_real_ip_from 0.0.0.0/0; # Compression. gzip on; gzip_vary on; gzip_min_length 1024; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/html text/javascript application/javascript application/json application/xml application/pdf image/svg+xml; # Long cache for the PDF (regenerated rarely, content-hash not used). location ~* \.pdf$ { expires 7d; add_header Cache-Control "public, max-age=604800"; } # Short cache for HTML so content updates land fast. location ~* \.html$ { expires 1h; add_header Cache-Control "public, max-age=3600, must-revalidate"; } # Long cache for favicon + image assets (rarely change). location ~* \.(?:ico|svg|png|jpg|jpeg|gif|webp)$ { expires 30d; add_header Cache-Control "public, max-age=2592000, immutable"; access_log off; } # Logs to stdout/stderr (default in nginx:alpine). access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log warn; # Block access to dotfiles (defense-in-depth — none are shipped anyway). location ~ /\. { deny all; return 404; } # Default: serve files, fall back to 404. location / { try_files $uri $uri/ =404; } }