server { listen 80; listen [::]:80; server_name missioflow.fr www.missioflow.fr localhost; root /usr/share/nginx/html; index index.html; # --- OWASP Security Headers --- # Prevent MIME type sniffing add_header X-Content-Type-Options "nosniff" always; # Clickjacking protection add_header X-Frame-Options "DENY" always; # XSS protection (legacy browsers) add_header X-XSS-Protection "1; mode=block" always; # Referrer policy add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Permissions policy - restrict browser features add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()" always; # Content Security Policy - strict, no inline scripts add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.gstatic.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self' mailto:;" always; # HSTS - force HTTPS (handled by Traefik, but reinforced here) add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; # Prevent content from being cached when it contains sensitive data add_header X-Permitted-Cross-Domain-Policies "none" always; # --- Performance --- # Gzip compression gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_min_length 256; gzip_types text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml application/xhtml+xml; # Static assets caching location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; add_header X-Content-Type-Options "nosniff" always; access_log off; } # HTML - no cache (allow revalidation) location ~* \.html$ { add_header Cache-Control "no-cache, must-revalidate"; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.gstatic.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self' mailto:;" always; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()" always; } # SPA-style routing - serve index.html for all routes location / { try_files $uri $uri/ $uri.html /index.html; } # Block dotfiles (security) location ~ /\. { deny all; access_log off; log_not_found off; } # Block access to sensitive files location ~* \.(env|git|gitignore|dockerignore|md|yml|yaml|toml|lock|log)$ { deny all; access_log off; log_not_found off; } # Custom error pages error_page 404 /404.html; error_page 500 502 503 504 /index.html; # Disable server tokens server_tokens off; }