# ───────────────────────────────────────────────────────────────────────────── # REBOUR — nginx.conf # nginx sert public/ directement + proxifie /api/ vers Bun # ───────────────────────────────────────────────────────────────────────────── user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr "$request" $status $body_bytes_sent "${request_time}s"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; server_tokens off; # ── Gzip ───────────────────────────────────────────────────────────────── gzip on; gzip_vary on; gzip_comp_level 5; gzip_min_length 256; gzip_types text/plain text/css text/javascript text/xml application/javascript application/json application/xml image/svg+xml font/woff2; # ── Rate limiting ───────────────────────────────────────────────────────── limit_req_zone $binary_remote_addr zone=api:10m rate=10r/m; # ── Resolver Docker (résolution dynamique → pas de crash au boot) ───────── resolver 127.0.0.11 valid=5s ipv6=off; map $host $api_backend { default "http://app:3000"; } # ── Redirection HTTP → HTTPS (décommenter en prod) ──────────────────────── # server { # listen 80; # server_name rebour.studio www.rebour.studio; # return 301 https://rebour.studio$request_uri; # } server { listen 80; server_name _; # Dossier public servi directement par nginx root /srv/public; index index.html; # ── Headers sécurité ───────────────────────────────────────────────── add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=()" always; # ── Assets statiques : cache 1 an immutable ─────────────────────────── location ~* \.(jpg|jpeg|png|webp|svg|ico|woff2|css|js)$ { expires 1y; add_header Cache-Control "public, max-age=31536000, immutable"; add_header Vary "Accept-Encoding"; } # ── API → proxy vers Bun ────────────────────────────────────────────── location /api/ { limit_req zone=api burst=10 nodelay; proxy_pass $api_backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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; add_header Cache-Control "no-store"; } # ── Webhook Stripe : pas de buffering ───────────────────────────────── location = /api/webhook { limit_req zone=api burst=5 nodelay; proxy_pass $api_backend; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header stripe-signature $http_stripe_signature; proxy_request_buffering off; add_header Cache-Control "no-store"; } # ── SEO dynamique (robots/sitemap générés par Bun) ──────────────────── location ~* ^/(robots\.txt|sitemap\.xml)$ { proxy_pass $api_backend; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; add_header Cache-Control "public, max-age=86400"; } # ── HTML : index.html pour toutes les routes (SPA-style) ───────────── location / { try_files $uri $uri/ $uri.html =404; add_header Cache-Control "public, max-age=3600, stale-while-revalidate=86400"; } } }