# ============================================================================= # Configuration NGINX PRODUCTION pour pra-geo (IN4) # Date: 2025-10-07 # Environnement: PRODUCTION # Server: IN4 (51.159.7.190) # ============================================================================= # Site principal (redirection vers www ou app) server { listen 80; server_name geosector.fr; # Redirection permanente vers HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name geosector.fr; # Certificats SSL ssl_certificate /etc/letsencrypt/live/geosector.fr/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/geosector.fr/privkey.pem; # Configuration SSL optimisée ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305'; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_stapling on; ssl_stapling_verify on; root /var/www/geosector/web; index index.html; # Logs PRODUCTION access_log /var/log/nginx/geosector-web_access.log combined; error_log /var/log/nginx/geosector-web_error.log warn; # Headers de sécurité add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; location / { try_files $uri $uri/ /index.html; } # Assets statiques avec cache agressif location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|css|js|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } # Protection des fichiers sensibles location ~ /\.(?!well-known) { deny all; access_log off; log_not_found off; } } # ============================================================================= # APPLICATION FLUTTER + API PHP # ============================================================================= # Redirection HTTP → HTTPS server { listen 80; server_name app3.geosector.fr; # Permettre Let's Encrypt validation location ^~ /.well-known/acme-challenge/ { root /var/www/letsencrypt; allow all; } # Redirection permanente vers HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name app3.geosector.fr; # Certificats SSL ssl_certificate /etc/letsencrypt/live/app3.geosector.fr/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/app3.geosector.fr/privkey.pem; # Configuration SSL optimisée (même que ci-dessus) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305'; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_stapling on; ssl_stapling_verify on; # Logs PRODUCTION access_log /var/log/nginx/pra-app_access.log combined; error_log /var/log/nginx/pra-app_error.log warn; # Headers de sécurité globaux add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Taille maximale des uploads (pour les logos, exports, etc.) client_max_body_size 10M; client_body_buffer_size 128k; # Timeouts optimisés pour PRODUCTION client_body_timeout 30s; client_header_timeout 30s; send_timeout 60s; # ============================================================================= # APPLICATION FLUTTER (contenu statique) # ============================================================================= location / { root /var/www/geosector/app; index index.html; try_files $uri $uri/ /index.html; # Cache intelligent pour PRODUCTION # HTML : pas de cache (pour déploiements) location ~* \.html$ { expires -1; add_header Cache-Control "no-cache, no-store, must-revalidate"; } # Assets Flutter (JS, CSS, fonts) avec hash : cache agressif location ~* \.(js|css|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } # Images : cache longue durée location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ { expires 30d; add_header Cache-Control "public"; access_log off; } } # ============================================================================= # API PHP (RESTful) # ============================================================================= location /api/ { root /var/www/geosector; # CORS - Liste blanche des origines autorisées en PRODUCTION set $cors_origin ""; # Autoriser uniquement les domaines de production if ($http_origin ~* ^https://(app\.geosector\.fr|geosector\.fr)$) { set $cors_origin $http_origin; } # Gestion des preflight requests (OPTIONS) if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' $cors_origin always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Max-Age' 86400; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } # Headers CORS pour les requêtes normales add_header 'Access-Control-Allow-Origin' $cors_origin always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always; add_header 'Access-Control-Allow-Credentials' 'true' always; # Cache API : pas de cache (données dynamiques) add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate" always; add_header Pragma "no-cache" always; add_header Expires "0" always; # Headers de sécurité spécifiques API add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; # Rewrite vers index.php try_files $uri $uri/ /api/index.php$is_args$args; # Traitement PHP location ~ ^/api/(.+\.php)$ { root /var/www/geosector; # FastCGI PHP-FPM fastcgi_pass unix:/run/php-fpm83/php-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $request_filename; # Variable d'environnement PRODUCTION fastcgi_param APP_ENV "production"; fastcgi_param SERVER_NAME "app3.geosector.fr"; # Headers transmis à PHP fastcgi_param HTTP_X_REAL_IP $remote_addr; fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for; fastcgi_param HTTP_X_FORWARDED_PROTO $scheme; # Timeouts pour opérations longues (sync, exports) fastcgi_read_timeout 300; fastcgi_send_timeout 300; fastcgi_connect_timeout 60; # Buffers optimisés fastcgi_buffer_size 128k; fastcgi_buffers 256 16k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; # Headers CORS pour réponses PHP add_header 'Access-Control-Allow-Origin' $cors_origin always; add_header 'Access-Control-Allow-Credentials' 'true' always; } } # ============================================================================= # UPLOADS ET MÉDIAS # ============================================================================= location /api/uploads/ { alias /var/www/geosector/api/uploads/; # Cache pour les médias uploadés expires 7d; add_header Cache-Control "public"; # Sécurité : empêcher l'exécution de scripts location ~ \.(php|phtml|php3|php4|php5|phps)$ { deny all; } } # ============================================================================= # SÉCURITÉ # ============================================================================= # Bloquer l'accès aux fichiers sensibles location ~ /\.(?!well-known) { deny all; access_log off; log_not_found off; } # Bloquer l'accès aux fichiers de configuration location ~* \.(env|sql|bak|backup|swp|config|conf|ini|log)$ { deny all; access_log off; log_not_found off; } # Bloquer les user-agents malveillants if ($http_user_agent ~* (bot|crawler|spider|scraper|wget|curl)) { return 403; } # Protection contre les requêtes invalides if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|PATCH|OPTIONS)$) { return 405; } # ============================================================================= # MONITORING # ============================================================================= # Endpoint de health check (accessible uniquement en local) location = /nginx-health { access_log off; allow 127.0.0.1; allow 13.23.34.0/24; # Réseau interne Incus deny all; return 200 "healthy\n"; add_header Content-Type text/plain; } }