# ============================================================================= # nginx.conf.example — Configuration serveur pour courcellematthi.eu # ----------------------------------------------------------------------------- # À adapter puis inclure dans la configuration Nginx. # Couvre : redirections canoniques, en-têtes de sécurité, cache, compression. # # Sans ces réglages, un site statique parfaitement écrit reste noté médiocre # par les outils d'audit (Lighthouse, Mozilla Observatory, securityheaders.com) # et sert des fichiers non compressés. # ============================================================================= # ----------------------------------------------------------------------------- # REDIRECTION HTTP -> HTTPS # Le certificat est supposé géré par Certbot ou acme.sh. # ----------------------------------------------------------------------------- server { listen 80; listen [::]:80; server_name courcellematthi.eu www.courcellematthi.eu; # Laisse passer la validation ACME avant de rediriger location /.well-known/acme-challenge/ { root /var/www/certbot; } location / { return 301 https://www.courcellematthi.eu$request_uri; } } # ----------------------------------------------------------------------------- # REDIRECTION DU DOMAINE NU VERS WWW # Une seule URL canonique évite la dilution du référencement et le contenu # dupliqué. Le choix www / sans-www est indifférent, la constance ne l'est pas. # La valeur retenue ici doit correspondre à dans le HTML. # ----------------------------------------------------------------------------- server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name courcellematthi.eu; ssl_certificate /etc/letsencrypt/live/courcellematthi.eu/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/courcellematthi.eu/privkey.pem; return 301 https://www.courcellematthi.eu$request_uri; } # ----------------------------------------------------------------------------- # SERVEUR PRINCIPAL # ----------------------------------------------------------------------------- server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name www.courcellematthi.eu; root /var/www/courcellematthi.eu; index index.html; charset utf-8; # ------------------------------------------------------------------------- # TLS # TLS 1.2 minimum : les versions antérieures sont obsolètes et vulnérables. # ------------------------------------------------------------------------- ssl_certificate /etc/letsencrypt/live/courcellematthi.eu/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/courcellematthi.eu/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; # Laisse le client choisir : recommandé # depuis TLS 1.3 ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; # Agrafage OCSP : le serveur fournit lui-même la preuve de non-révocation # du certificat, ce qui évite au navigateur une requête vers l'autorité. ssl_stapling on; ssl_stapling_verify on; resolver 1.1.1.1 8.8.8.8 valid=300s; # ========================================================================= # EN-TÊTES DE SÉCURITÉ # ========================================================================= # HSTS — force HTTPS pendant deux ans, y compris sur les sous-domaines. # ATTENTION : ne l'activer qu'une fois le HTTPS confirmé fonctionnel # partout. Un HSTS émis par erreur rend le site inaccessible jusqu'à # expiration, sans possibilité d'annulation côté serveur. add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; # CSP — liste blanche des sources autorisées. Défense principale contre # l'injection de scripts. # default-src 'self' : tout provient du domaine par défaut # style-src ... 'unsafe-inline' : requis par le CSS injecté de Google Fonts # font-src fonts.gstatic.com : fichiers de police # script-src 'self' 'unsafe-inline' : 'unsafe-inline' n'est nécessaire # que pour l'attribut onclick du bouton d'impression. Le retirer si # ce bouton est déplacé dans un module. # frame-ancestors 'none' : interdit l'inclusion en iframe (clickjacking) # base-uri 'self' : empêche la réécriture des URL relatives # form-action 'self' : limite les cibles de soumission 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:; connect-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; object-src 'none'; upgrade-insecure-requests" always; # Empêche le navigateur de deviner le type MIME d'un fichier, technique # utilisée pour faire exécuter une image comme du script. add_header X-Content-Type-Options "nosniff" always; # Ne transmet le référent complet qu'en navigation interne ; vers # l'extérieur, seul le domaine est envoyé. Compromis usuel entre mesure # d'audience et confidentialité. add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Désactive des API sensibles jamais utilisées par ce site. add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), interest-cohort=()" always; # Redondant avec frame-ancestors, conservé pour les navigateurs anciens. add_header X-Frame-Options "DENY" always; # ========================================================================= # COMPRESSION # Gzip partout ; Brotli en plus si le module est disponible (taux # supérieur de 15 à 20 % sur du texte). # ========================================================================= gzip on; gzip_vary on; # Ajoute « Vary: Accept-Encoding » : sans lui, # un proxy peut servir la version compressée # à un client qui ne la supporte pas. gzip_comp_level 6; # Au-delà, le gain marginal ne compense plus # le coût CPU. gzip_min_length 256; # En dessous, l'en-tête coûte plus que le gain. gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/manifest+json application/xml image/svg+xml; # Décommenter si le module ngx_brotli est compilé # brotli on; # brotli_comp_level 6; # brotli_types text/plain text/css application/javascript application/json image/svg+xml; # ========================================================================= # CACHE # Stratégie en deux niveaux : # - Les ressources d'assets changent rarement : cache long. # - Le HTML change à chaque mise à jour de contenu : jamais mis en cache # durablement, sinon les visiteurs voient une version périmée. # # NOTE : « immutable » suppose des noms de fichiers versionnés # (style.a1b2c3.css). Sans versionnement, garder une durée modérée # pour pouvoir publier un correctif. # ========================================================================= location ~* \.(css|js|woff2?|ttf|otf|eot)$ { expires 30d; add_header Cache-Control "public, max-age=2592000"; access_log off; } location ~* \.(png|jpe?g|gif|svg|webp|avif|ico)$ { expires 90d; add_header Cache-Control "public, max-age=7776000"; access_log off; } location = /index.html { add_header Cache-Control "public, max-age=0, must-revalidate"; } location ~* \.(txt|xml|webmanifest)$ { expires 1d; add_header Cache-Control "public, max-age=86400"; } # ========================================================================= # TYPES MIME PARTICULIERS # ========================================================================= location = /site.webmanifest { types { } default_type application/manifest+json; } location = /llms.txt { types { } default_type text/plain; charset utf-8; } location = /.well-known/security.txt { types { } default_type text/plain; charset utf-8; } # ========================================================================= # ROUTAGE # ========================================================================= location / { try_files $uri $uri/ /index.html; } # Refuse l'accès aux fichiers cachés, sauf .well-known (RFC 8615) location ~ /\.(?!well-known) { deny all; return 404; } # Page d'erreur : renvoyer vers l'accueil plutôt qu'une page vide error_page 404 /index.html; }