166 lines
8.4 KiB
Nginx Configuration File
166 lines
8.4 KiB
Nginx Configuration File
# ─────────────────────────────────────────────────────────────────────────────
|
|
# REBOUR — nginx.conf
|
|
# Rôle : reverse proxy devant Elysia/Bun
|
|
# SEO : pas de SSR → HTML statique pré-rendu, on optimise le delivery
|
|
# (gzip, cache, headers) et la crawlabilité
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
user nginx;
|
|
worker_processes auto;
|
|
error_log /var/log/nginx/error.log warn;
|
|
pid /var/run/nginx.pid;
|
|
|
|
events {
|
|
worker_connections 1024;
|
|
use epoll;
|
|
}
|
|
|
|
http {
|
|
include /etc/nginx/mime.types;
|
|
default_type application/octet-stream;
|
|
|
|
log_format main '$remote_addr "$request" $status $body_bytes_sent '
|
|
'"$http_referer" "$http_user_agent" ${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 : réduit poids HTML/CSS/JS → Core Web Vitals ───────────────────
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_proxied any;
|
|
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;
|
|
limit_req_zone $binary_remote_addr zone=general:10m rate=60r/s;
|
|
|
|
# ── Resolver DNS Docker ───────────────────────────────────────────────────
|
|
# 127.0.0.11 = resolver interne Docker. La résolution dynamique via variable
|
|
# $backend évite le crash "host not found in upstream" au démarrage nginx
|
|
# si le container "app" n'est pas encore prêt (upstream statique résout au
|
|
# boot, pas à la requête — d'où le crash).
|
|
resolver 127.0.0.11 valid=5s ipv6=off;
|
|
|
|
# Variable résolue dynamiquement à chaque requête (pas au démarrage nginx)
|
|
# Permet à nginx de démarrer même si "app" n'existe pas encore en DNS.
|
|
map $host $elysia_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 _;
|
|
|
|
# ── SSL / prod (décommenter avec Certbot) ────────────────────────────
|
|
# listen 443 ssl http2;
|
|
# ssl_certificate /etc/letsencrypt/live/rebour.studio/fullchain.pem;
|
|
# ssl_certificate_key /etc/letsencrypt/live/rebour.studio/privkey.pem;
|
|
# ssl_protocols TLSv1.2 TLSv1.3;
|
|
# ssl_session_cache shared:SSL:10m;
|
|
|
|
# ── 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;
|
|
# HSTS — décommenter en prod UNIQUEMENT après config SSL
|
|
# add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
|
|
|
# ── Images / fonts : cache 1 an immutable ───────────────────────────
|
|
# Les crawlers sociaux (og:image) et Google téléchargent ces fichiers
|
|
location ~* \.(jpg|jpeg|png|webp|svg|ico|woff2)$ {
|
|
proxy_pass $elysia_backend;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
add_header Vary "Accept-Encoding";
|
|
}
|
|
|
|
# ── CSS / JS : cache 1 an ────────────────────────────────────────────
|
|
location ~* \.(css|js)$ {
|
|
proxy_pass $elysia_backend;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
add_header Vary "Accept-Encoding";
|
|
}
|
|
|
|
# ── Checkout Stripe : rate-limit strict, no-cache ───────────────────
|
|
location = /api/checkout {
|
|
limit_req zone=api burst=3 nodelay;
|
|
proxy_pass $elysia_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 : body raw, pas de buffering ──────────────────────
|
|
location = /api/webhook {
|
|
proxy_pass $elysia_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";
|
|
}
|
|
|
|
# ── API (session verify, etc.) ───────────────────────────────────────
|
|
location /api/ {
|
|
limit_req zone=api burst=10 nodelay;
|
|
proxy_pass $elysia_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-Proto $scheme;
|
|
add_header Cache-Control "no-store";
|
|
}
|
|
|
|
# ── SEO : sitemap + robots ───────────────────────────────────────────
|
|
location ~* ^/(sitemap\.xml|robots\.txt)$ {
|
|
proxy_pass $elysia_backend;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
add_header Cache-Control "public, max-age=86400";
|
|
}
|
|
|
|
# ── Pages HTML ───────────────────────────────────────────────────────
|
|
# Cache 1h côté client, stale-while-revalidate pour UX fluide
|
|
# Google re-crawle dès que le cache expire → pas de contenu périmé indexé
|
|
location / {
|
|
limit_req zone=general burst=20 nodelay;
|
|
proxy_pass $elysia_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 "public, max-age=3600, stale-while-revalidate=86400";
|
|
}
|
|
}
|
|
}
|