server {
listen 80;
root /usr/share/nginx/html;
index index.html;
server_tokens off;
# En-têtes de sécurité : le helmet de l'API ne couvre que les réponses JSON,
# pas les pages HTML/JS servies ici. Admin = actions destructrices → strict.
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Tout est self-hosted, à une police Google près ( dans index.html).
# script-src 'self' est tenable : app.js n'utilise aucun handler inline.
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
# Résolveur DNS interne Docker : force nginx à re-résoudre "api" à chaque
# requête au lieu de mettre l'IP en cache au démarrage (sinon un redeploy
# du service api laisse nginx pointer vers un conteneur mort -> 502/404).
resolver 127.0.0.11 valid=10s;
location /admin/api/ {
set $upstream_api http://api:3000;
proxy_pass $upstream_api;
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;
}
location /admin/auth/ {
set $upstream_api http://api:3000;
proxy_pass $upstream_api;
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;
}
location / {
try_files $uri $uri/ /index.html;
}
}