# [COURS] EXERCICES CORRIGÉS NGINX - ULTRA DÉTAILLÉS

## [LIVRE] INTRODUCTION

Ce document contient **5 exercices pratiques corrigés** sur Nginx, allant du niveau débutant au niveau expert. Chaque exercice est conçu pour renforcer tes compétences avec la même approche pédagogique que les exercices Apache.

**Niveau de progression :**
- [VERT] Exercice 1 : Débutant - Site Portfolio Multi-pages
- [JAUNE] Exercice 2 : Intermédiaire - HTTPS avec Certificat Auto-signé  
- [JAUNE] Exercice 3 : Intermédiaire - Site PHP avec MySQL
- [ROUGE] Exercice 4 : Avancé - Reverse Proxy + Load Balancing
- [ROUGE] Exercice 5 : Expert - Sécurisation Complète

**Ce qui rend Nginx différent d'Apache :**

| Critère | Apache | Nginx |
|---------|--------|-------|
| **Architecture** | Process-based (threads/processes) | Event-driven (asynchrone) |
| **Performance** | Bon | * Excellent (10x plus rapide) |
| **Mémoire** | ~5-10 MB par connexion | ~2.5 KB par connexion |
| **Configuration** | Fichiers .htaccess distribués | Centralisée (plus performant) |
| **Modules** | Dynamiques | Compilés statiquement |
| **Usage** | Serveur web complet | * Reverse proxy, load balancer |
| **Concurrence** | 1000-2000 connexions | * 10,000+ connexions |

**Pourquoi apprendre Nginx après Apache ?**
- Nginx = 34% de part de marché (2024)
- Utilisé par : Netflix, Cloudflare, WordPress.com, GitHub
- Idéal pour sites à fort trafic
- Meilleur en reverse proxy qu'Apache
- Compétence indispensable pour DevOps

**Bon courage ! [RAPIDE]**

---

---

# [VERT] EXERCICE 1 : SITE PORTFOLIO MULTI-PAGES AVEC NGINX

## [LISTE] ÉNONCÉ

### Contexte professionnel

Tu es développeur web dans une agence qui migre d'Apache vers Nginx pour des raisons de performance. Un client souhaite un **site portfolio personnel** identique à celui de l'exercice Apache 1, mais hébergé sur Nginx.

### Cahier des charges

Le client souhaite :
- **5 pages** : Accueil, À propos, Portfolio, Services, Contact
- Un **menu de navigation** fonctionnel
- Un **design moderne** et responsive
- Une **galerie de projets** avec images
- Un **formulaire de contact** (HTML uniquement)
- Le site doit être **hébergé sur Nginx**

### Objectifs d'apprentissage

- Comprendre la différence entre Apache et Nginx
- Installer et configurer Nginx
- Créer un server block (équivalent VirtualHost)
- Comprendre la syntaxe de configuration Nginx
- Gérer les fichiers statiques avec Nginx

### Contraintes techniques

- Nginx 1.18+
- Pas de framework (HTML/CSS pur)
- Compatible tous navigateurs
- Temps estimé : 2-3 heures

---

## [OBJECTIF] OBJECTIFS PÉDAGOGIQUES

À la fin de cet exercice, tu sauras :

- [OK] Installer Nginx sur Ubuntu/Debian
- [OK] Comprendre l'architecture de configuration Nginx
- [OK] Créer et configurer un server block
- [OK] Gérer les permissions de fichiers
- [OK] Configurer les logs Nginx
- [OK] Tester et debugger Nginx
- [OK] Différencier Nginx d'Apache

---

## [DOCS] PRÉREQUIS

- Serveur Ubuntu 22.04+ ou Debian 11+
- Accès root ou sudo
- Connaissances HTML/CSS de base
- Avoir suivi l'exercice Apache 1 (pour comparer)

---

## [OK] SOLUTION COMPLÈTE

### ÉTAPE 1 : Comprendre l'architecture Nginx

**Avant d'installer, comprenons comment Nginx fonctionne.**

#### Architecture d'Apache vs Nginx

**Apache (Architecture multi-processus) :**

```
Requête Client -> Worker Process dédié -> Traitement -> Réponse
                 (1 process = 1 connexion)
```

**Problème :** 10,000 connexions = 10,000 processus = ~50 Go RAM ! [!]

---

**Nginx (Architecture événementielle) :**

```
Requête Client -> Event Loop (1 worker) -> Traitement asynchrone -> Réponse
                 (1 worker = 10,000+ connexions)
```

**Avantage :** 10,000 connexions = ~25 MB RAM ! [BRAVO]

---

#### Analogie du restaurant

**Apache = Restaurant traditionnel**
- 1 serveur = 1 table
- 10 tables = 10 serveurs
- Coût : 10 salaires

**Nginx = Restaurant moderne**
- 1 serveur rapide gère 10 tables
- Il prend les commandes, les envoie en cuisine, sert quand c'est prêt
- Coût : 1 salaire
- Efficacité : *****

---

#### Structure des fichiers Nginx

```
/etc/nginx/
├── nginx.conf                 <- Configuration PRINCIPALE
├── sites-available/           <- Server blocks disponibles
│   └── default               <- Site par défaut
├── sites-enabled/            <- Server blocks activés (liens symboliques)
│   └── default -> ../sites-available/default
├── conf.d/                   <- Configurations additionnelles
├── snippets/                 <- Morceaux de config réutilisables
└── modules-enabled/          <- Modules actifs
```

**Différence majeure avec Apache :**

| Apache | Nginx |
|--------|-------|
| VirtualHost | Server block |
| sites-available/sites-enabled | Identique |
| `.htaccess` (distribué) | [X] Pas de `.htaccess` |
| `httpd.conf` ou `apache2.conf` | `nginx.conf` |

**[ATTENTION] Nginx n'a PAS de fichier .htaccess !**
- Toute la config est centralisée
- Plus performant (pas de lecture de `.htaccess` à chaque requête)
- Mais moins flexible pour l'utilisateur final

---

### ÉTAPE 2 : Installer Nginx

```bash
# Mettre à jour la liste des paquets
sudo apt update

# Installer Nginx
sudo apt install nginx -y
```

**Explication :**

**`nginx`** = Paquet principal Nginx
**`-y`** = Accepte automatiquement (pas de confirmation)

---

**Vérifier l'installation :**

```bash
nginx -v
```

**Résultat :**

```
nginx version: nginx/1.18.0 (Ubuntu)
```

**[OK] Nginx installé !**

---

**Vérifier que Nginx tourne :**

```bash
sudo systemctl status nginx
```

**Résultat :**

```
[BLACK_CIRCLE] nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2024-12-16 10:00:00 UTC; 2min ago
   Main PID: 12345 (nginx)
      Tasks: 2 (limit: 4915)
     Memory: 3.2M
        CPU: 34ms
```

**[OK] Nginx actif et en cours d'exécution !**

---

**Tester dans le navigateur :**

Accède à l'IP de ton serveur :

```
http://192.168.1.100
```

**Tu devrais voir la page par défaut Nginx :**

```
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and working.
```

**[OK] Nginx fonctionne !**

---

### ÉTAPE 3 : Comprendre nginx.conf (Configuration principale)

**Consulter le fichier principal :**

```bash
sudo nano /etc/nginx/nginx.conf
```

**Structure du fichier (simplifié) :**

```nginx
# ═══════════════════════════════════════════════════════════════
# NGINX.CONF - CONFIGURATION PRINCIPALE
# ═══════════════════════════════════════════════════════════════

user www-data;
# Utilisateur sous lequel Nginx tourne
# www-data = Utilisateur système standard pour les serveurs web

worker_processes auto;
# Nombre de worker processes (processus de travail)
# auto = Détection automatique (1 par cœur CPU)
# Avec 4 cœurs -> 4 workers
# 
# Pourquoi plusieurs workers ?
# Chaque worker gère des milliers de connexions
# Répartition sur plusieurs cœurs CPU = meilleur parallélisme

pid /run/nginx.pid;
# Fichier contenant le PID (Process ID) du master process
# Utilisé par systemctl pour gérer Nginx

include /etc/nginx/modules-enabled/*.conf;
# Inclut tous les modules activés
# Équivalent d'Apache : a2enmod

# ───────────────────────────────────────────────────────────────
# CONTEXTE EVENTS
# ───────────────────────────────────────────────────────────────

events {
    worker_connections 768;
    # Nombre MAX de connexions simultanées par worker
    # Total connexions = worker_processes × worker_connections
    # Exemple : 4 workers × 768 = 3,072 connexions simultanées
    #
    # Par défaut : 768 (suffisant pour 99% des cas)
    # Sites à fort trafic : 1024-4096
    #
    # Comparaison Apache :
    # Apache MaxClients 150 = 150 connexions
    # Nginx 4 workers × 768 = 3,072 connexions
    # -> 20x plus de connexions !
    
    # multi_accept on;
    # Si activé, accepte toutes les connexions en attente à la fois
    # Par défaut : off (accepte 1 par 1)
    # Activer si trafic très élevé
}

# ───────────────────────────────────────────────────────────────
# CONTEXTE HTTP
# ───────────────────────────────────────────────────────────────

http {
    # ═══════════════════════════════════════════════════════════
    # PARAMÈTRES DE BASE
    # ═══════════════════════════════════════════════════════════
    
    sendfile on;
    # Utilise sendfile() pour envoyer les fichiers
    # Méthode kernel optimisée (bypass userspace)
    # Plus rapide que read() + write()
    # * TOUJOURS activer !
    
    tcp_nopush on;
    # Envoie les headers HTTP et début du fichier dans le même paquet TCP
    # Réduit le nombre de paquets
    # Optimisation réseau
    # Nécessite sendfile on;
    
    tcp_nodelay on;
    # Désactive l'algorithme Nagle (buffering TCP)
    # Envoie les petits paquets immédiatement
    # Réduit la latence
    # Bon pour les connexions interactives
    
    keepalive_timeout 65;
    # Durée de maintien des connexions persistantes (secondes)
    # HTTP/1.1 Keep-Alive
    # 65s = Bon compromis
    # Trop court : overhead de reconnexion
    # Trop long : workers occupés inutilement
    
    types_hash_max_size 2048;
    # Taille de la table de hachage des types MIME
    # Augmenter si beaucoup de types MIME custom
    
    # server_tokens off;
    # Masque la version de Nginx dans les headers
    # Sécurité par obscurcissement
    # [ATTENTION] À activer en production !
    
    # ═══════════════════════════════════════════════════════════
    # TYPES MIME
    # ═══════════════════════════════════════════════════════════
    
    include /etc/nginx/mime.types;
    # Table de correspondance extension -> type MIME
    # .html -> text/html
    # .css -> text/css
    # .jpg -> image/jpeg
    # etc.
    
    default_type application/octet-stream;
    # Type MIME par défaut si extension inconnue
    # octet-stream = Fichier binaire générique
    
    # ═══════════════════════════════════════════════════════════
    # LOGS
    # ═══════════════════════════════════════════════════════════
    
    access_log /var/log/nginx/access.log;
    # Log de tous les accès HTTP
    # Format : IP - - [date] "requête" code taille "referer" "user-agent"
    
    error_log /var/log/nginx/error.log;
    # Log des erreurs Nginx
    # Niveaux : debug, info, notice, warn, error, crit, alert, emerg
    
    # ═══════════════════════════════════════════════════════════
    # GZIP COMPRESSION
    # ═══════════════════════════════════════════════════════════
    
    gzip on;
    # Active la compression GZIP des réponses
    # Réduit la bande passante
    # Augmente légèrement la charge CPU
    
    # gzip_vary on;
    # Ajoute header Vary: Accept-Encoding
    # Indique aux proxies/caches que le contenu varie selon l'encoding
    
    # gzip_types text/plain text/css application/json application/javascript;
    # Types MIME à compresser
    # Par défaut : text/html seulement
    # Ne PAS compresser les images (déjà compressées)
    
    # ═══════════════════════════════════════════════════════════
    # VIRTUAL HOSTS (SERVER BLOCKS)
    # ═══════════════════════════════════════════════════════════
    
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
    # Inclut tous les server blocks activés
    # sites-enabled/ contient des liens symboliques vers sites-available/
}
```

**[ATTENTION] NE MODIFIE PAS nginx.conf pour l'instant !**

On va créer notre propre server block dans `sites-available/`.

---

### ÉTAPE 4 : Créer la structure du site portfolio

**Structure des dossiers (identique à Apache) :**

```
/var/www/portfolio/
├── index.html
├── about.html
├── portfolio.html
├── services.html
├── contact.html
├── css/
│   └── style.css
├── images/
│   ├── project1.jpg
│   ├── project2.jpg
│   └── project3.jpg
└── js/
    └── script.js
```

---

**Créer la structure :**

```bash
# Créer le dossier principal
sudo mkdir -p /var/www/portfolio

# Créer les sous-dossiers
sudo mkdir -p /var/www/portfolio/css
sudo mkdir -p /var/www/portfolio/images
sudo mkdir -p /var/www/portfolio/js

# Vérifier
tree /var/www/portfolio
```

---

### ÉTAPE 5 : Créer les fichiers HTML et CSS

**Les fichiers HTML et CSS sont IDENTIQUES à l'exercice Apache 1.**

**Pour gagner du temps, je vais te donner les commandes pour les créer rapidement.**

---

**Créer le fichier CSS :**

```bash
sudo nano /var/www/portfolio/css/style.css
```

**Copie le CSS de l'exercice Apache 1 (voir document précédent).**

**Ou utilise une version simplifiée :**

```css
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    line-height: 1.6;
    color: #333;
    background: #f4f4f4;
}

header {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 2rem 0;
}

nav {
    max-width: 1200px;
    margin: 0 auto;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 2rem;
}

nav .logo {
    font-size: 1.5rem;
    font-weight: bold;
    color: white;
    text-decoration: none;
}

nav ul {
    list-style: none;
    display: flex;
    gap: 2rem;
}

nav ul li a {
    color: white;
    text-decoration: none;
    transition: all 0.3s ease;
    padding: 0.5rem 1rem;
    border-radius: 5px;
}

nav ul li a:hover,
nav ul li a.active {
    background: rgba(255,255,255,0.2);
}

.container {
    max-width: 1200px;
    margin: 2rem auto;
    padding: 0 2rem;
}

section {
    background: white;
    padding: 3rem;
    margin-bottom: 2rem;
    border-radius: 10px;
    box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}

section h1 {
    color: #667eea;
    margin-bottom: 1rem;
}

section h2 {
    color: #764ba2;
    margin-bottom: 1rem;
}

.projects-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 2rem;
    margin-top: 2rem;
}

.project-card {
    background: #f9f9f9;
    border-radius: 10px;
    overflow: hidden;
    transition: transform 0.3s ease;
}

.project-card:hover {
    transform: translateY(-10px);
}

.project-card img {
    width: 100%;
    height: 200px;
    object-fit: cover;
}

.project-card .project-info {
    padding: 1.5rem;
}

.project-card h3 {
    color: #667eea;
    margin-bottom: 0.5rem;
}

footer {
    background: #333;
    color: white;
    text-align: center;
    padding: 2rem 0;
    margin-top: 3rem;
}

@media (max-width: 768px) {
    nav {
        flex-direction: column;
        gap: 1rem;
    }
    
    nav ul {
        flex-direction: column;
        text-align: center;
    }
}
```

**Sauvegarde.**

---

**Créer index.html :**

```bash
sudo nano /var/www/portfolio/index.html
```

**Contenu minimal (ou copie de l'exercice Apache) :**

```html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Portfolio - Accueil</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header>
        <nav>
            <a href="index.html" class="logo">MON PORTFOLIO</a>
            <ul>
                <li><a href="index.html" class="active">Accueil</a></li>
                <li><a href="about.html">À propos</a></li>
                <li><a href="portfolio.html">Portfolio</a></li>
                <li><a href="services.html">Services</a></li>
                <li><a href="contact.html">Contact</a></li>
            </ul>
        </nav>
    </header>

    <div class="container">
        <section>
            <h1>[WAVING_HAND_SIGN] Bienvenue sur mon Portfolio</h1>
            <p>
                Je suis développeur web passionné, spécialisé en création de sites modernes et performants.
            </p>
            <p>
                Ce site est hébergé sur <strong>Nginx</strong>, le serveur web le plus performant du monde !
            </p>
        </section>

        <section>
            <h2>[PRO] Projets Récents</h2>
            <div class="projects-grid">
                <div class="project-card">
                    <img src="https://via.placeholder.com/400x200/667eea/ffffff?text=Projet+1" alt="Projet 1">
                    <div class="project-info">
                        <h3>Site E-commerce</h3>
                        <p>Boutique en ligne avec panier et paiement sécurisé.</p>
                    </div>
                </div>
                
                <div class="project-card">
                    <img src="https://via.placeholder.com/400x200/764ba2/ffffff?text=Projet+2" alt="Projet 2">
                    <div class="project-info">
                        <h3>Application Web</h3>
                        <p>CRM personnalisé avec tableau de bord analytique.</p>
                    </div>
                </div>
                
                <div class="project-card">
                    <img src="https://via.placeholder.com/400x200/667eea/ffffff?text=Projet+3" alt="Projet 3">
                    <div class="project-info">
                        <h3>Site Corporate</h3>
                        <p>Site vitrine responsive pour entreprise.</p>
                    </div>
                </div>
            </div>
        </section>
    </div>

    <footer>
        <p>&copy; 2024 Mon Portfolio - Hébergé sur Nginx</p>
    </footer>
</body>
</html>
```

**Sauvegarde.**

---

**Créer les autres pages (about.html, portfolio.html, services.html, contact.html)** en adaptant le contenu de l'exercice Apache 1.

**Ou créer des versions minimales pour tester rapidement :**

```bash
# about.html
echo '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>À propos</title><link rel="stylesheet" href="css/style.css"></head><body><header><nav><a href="index.html" class="logo">PORTFOLIO</a><ul><li><a href="index.html">Accueil</a></li><li><a href="about.html" class="active">À propos</a></li></ul></nav></header><div class="container"><section><h1>À propos de moi</h1><p>Page à propos...</p></section></div><footer><p>&copy; 2024</p></footer></body></html>' | sudo tee /var/www/portfolio/about.html > /dev/null

# portfolio.html
echo '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Portfolio</title><link rel="stylesheet" href="css/style.css"></head><body><header><nav><a href="index.html" class="logo">PORTFOLIO</a><ul><li><a href="index.html">Accueil</a></li><li><a href="portfolio.html" class="active">Portfolio</a></li></ul></nav></header><div class="container"><section><h1>Mes Projets</h1><p>Page portfolio...</p></section></div><footer><p>&copy; 2024</p></footer></body></html>' | sudo tee /var/www/portfolio/portfolio.html > /dev/null

# services.html et contact.html de la même manière...
```

---

### ÉTAPE 6 : Configurer les permissions

```bash
# Donner la propriété à www-data (utilisateur Nginx)
sudo chown -R www-data:www-data /var/www/portfolio

# Permissions : 755 pour dossiers, 644 pour fichiers
sudo find /var/www/portfolio -type d -exec chmod 755 {} \;
sudo find /var/www/portfolio -type f -exec chmod 644 {} \;
```

**[OK] Permissions OK !**

---

### ÉTAPE 7 : Créer le server block Nginx

**C'est ici que Nginx diffère vraiment d'Apache !**

```bash
sudo nano /etc/nginx/sites-available/portfolio
```

**Contenu :**

```nginx
# ═══════════════════════════════════════════════════════════════
# SERVER BLOCK - PORTFOLIO
# ═══════════════════════════════════════════════════════════════
# Description : Site portfolio personnel
# Port : 80 (HTTP)
# ═══════════════════════════════════════════════════════════════

server {
    # ───────────────────────────────────────────────────────────
    # ÉCOUTE ET IDENTIFICATION
    # ───────────────────────────────────────────────────────────
    
    listen 80;
    # Écoute sur le port 80 (HTTP)
    # Équivalent Apache : <VirtualHost *:80>
    #
    # Syntaxes possibles :
    # listen 80;                    -> IPv4 seulement
    # listen [::]:80;               -> IPv6 seulement
    # listen 80 default_server;     -> Site par défaut
    # listen 192.168.1.10:80;       -> IP spécifique
    
    listen [::]:80;
    # Écoute sur IPv6
    # :: = Toutes les adresses IPv6
    # Équivalent IPv4 : 0.0.0.0
    
    server_name portfolio.local;
    # Nom de domaine principal
    # Équivalent Apache : ServerName portfolio.local
    #
    # Peut contenir plusieurs domaines :
    # server_name portfolio.local www.portfolio.local;
    #
    # Wildcards autorisés :
    # server_name *.portfolio.local;
    # server_name portfolio.*;
    #
    # Regex (avec tilde) :
    # server_name ~^portfolio\d+\.local$;
    
    # ───────────────────────────────────────────────────────────
    # RACINE DU SITE
    # ───────────────────────────────────────────────────────────
    
    root /var/www/portfolio;
    # Racine du site web
    # Équivalent Apache : DocumentRoot /var/www/portfolio
    #
    # Nginx cherche les fichiers à partir de ce dossier
    # portfolio.local/index.html -> /var/www/portfolio/index.html
    # portfolio.local/css/style.css -> /var/www/portfolio/css/style.css
    
    index index.html index.htm;
    # Fichiers d'index (par ordre de priorité)
    # Équivalent Apache : DirectoryIndex index.html index.htm
    #
    # Si on accède à portfolio.local/
    # Nginx cherche dans cet ordre :
    # 1. /var/www/portfolio/index.html
    # 2. /var/www/portfolio/index.htm
    #
    # Si aucun trouvé -> 403 Forbidden (sauf si autoindex on;)
    
    # ───────────────────────────────────────────────────────────
    # LOCATION PRINCIPALE (/)
    # ───────────────────────────────────────────────────────────
    
    location / {
        # Location = Bloc de configuration pour une URL spécifique
        # / = Racine du site (toutes les URLs)
        #
        # Équivalent Apache : <Directory /var/www/portfolio>
        #
        # Types de location (par ordre de priorité) :
        # location = /exact         -> Correspondance EXACTE (le plus prioritaire)
        # location ^~ /prefix       -> Préfixe (prioritaire sur regex)
        # location ~ \.php$         -> Regex case-sensitive
        # location ~* \.(jpg|png)$  -> Regex case-insensitive
        # location /prefix          -> Préfixe (le moins prioritaire)
        
        try_files $uri $uri/ =404;
        # * DIRECTIVE CLÉE DE NGINX !
        #
        # Essaie les fichiers dans cet ordre :
        # 1. $uri = L'URI demandée telle quelle
        # 2. $uri/ = L'URI comme dossier (cherche index dedans)
        # 3. =404 = Si rien trouvé, retourne 404
        #
        # Exemple avec /about.html :
        # 1. Cherche /var/www/portfolio/about.html -> Trouvé ! [OK]
        #
        # Exemple avec /dossier :
        # 1. Cherche /var/www/portfolio/dossier -> Pas un fichier
        # 2. Cherche /var/www/portfolio/dossier/ -> C'est un dossier
        # 3. Cherche index.html dedans -> /var/www/portfolio/dossier/index.html
        #
        # Exemple avec /inexistant :
        # 1. Cherche /var/www/portfolio/inexistant -> Pas trouvé
        # 2. Cherche /var/www/portfolio/inexistant/ -> Pas trouvé
        # 3. =404 -> Retourne 404 Not Found
        #
        # Variantes courantes :
        # try_files $uri $uri/ /index.html;  -> SPA (Single Page App)
        # try_files $uri $uri/ /index.php?$args;  -> PHP routing
        
        # Équivalent Apache (approximatif) :
        # <IfModule mod_rewrite.c>
        #     RewriteEngine On
        #     RewriteCond %{REQUEST_FILENAME} !-f
        #     RewriteCond %{REQUEST_FILENAME} !-d
        #     RewriteRule ^ index.html [L]
        # </IfModule>
        #
        # Mais Nginx est plus simple et plus performant !
    }
    
    # ───────────────────────────────────────────────────────────
    # LOCATION POUR FICHIERS STATIQUES
    # ───────────────────────────────────────────────────────────
    
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
        # ~* = Regex case-insensitive
        # \. = Point littéral (échappé)
        # (jpg|jpeg|...) = OU logique
        # $ = Fin de ligne
        #
        # Cette location s'applique aux fichiers :
        # - Images : jpg, jpeg, png, gif, ico, svg
        # - Styles : css
        # - Scripts : js
        # - Polices : woff, woff2, ttf, eot
        
        expires 1y;
        # Cache navigateur pendant 1 an
        # Ajoute header : Cache-Control: max-age=31536000
        #
        # Équivalent Apache :
        # <FilesMatch "\.(jpg|jpeg|png|...)$">
        #     Header set Cache-Control "max-age=31536000"
        # </FilesMatch>
        #
        # Unités possibles :
        # expires 30d;    -> 30 jours
        # expires 12h;    -> 12 heures
        # expires 1M;     -> 1 mois
        # expires epoch;  -> Pas de cache (1 janvier 1970)
        # expires max;    -> Cache maximum (31 décembre 2037)
        
        add_header Cache-Control "public, immutable";
        # public = Peut être mis en cache par n'importe qui (navigateur, CDN, proxy)
        # private = Seulement le navigateur de l'utilisateur
        # immutable = Le fichier ne changera JAMAIS (optimisation)
        #
        # Utile pour les fichiers avec hash dans le nom :
        # style.a1b2c3.css -> immutable (le hash change si le fichier change)
        
        access_log off;
        # Ne pas logger les accès aux fichiers statiques
        # Réduit la charge I/O (écriture disque)
        # Les logs sont moins pollués
        #
        # [ATTENTION] En debug, mettre access_log on; temporairement
    }
    
    # ───────────────────────────────────────────────────────────
    # BLOQUER LES FICHIERS CACHÉS
    # ───────────────────────────────────────────────────────────
    
    location ~ /\. {
        # ~ = Regex case-sensitive
        # /\. = Slash suivi d'un point
        #
        # Bloque les fichiers commençant par un point :
        # .git, .gitignore, .htaccess, .env, .DS_Store, etc.
        
        deny all;
        # Refuse l'accès à tout le monde
        # Équivalent Apache :
        # <FilesMatch "^\.">
        #     Require all denied
        # </FilesMatch>
        
        access_log off;
        log_not_found off;
        # Ne pas logger ces tentatives d'accès
        # log_not_found off; = Ne pas logger les 404 pour ces URLs
    }
    
    # ───────────────────────────────────────────────────────────
    # LOGS
    # ───────────────────────────────────────────────────────────
    
    access_log /var/log/nginx/portfolio-access.log;
    # Log des accès HTTP pour ce site
    # Équivalent Apache : CustomLog .../portfolio-access.log combined
    #
    # Format par défaut : combined
    # Défini dans /etc/nginx/nginx.conf
    #
    # Formats personnalisés possibles :
    # access_log /path/to/log combined;
    # access_log /path/to/log main;
    # access_log /path/to/log custom_format;
    
    error_log /var/log/nginx/portfolio-error.log;
    # Log des erreurs pour ce site
    # Équivalent Apache : ErrorLog .../portfolio-error.log
    #
    # Niveaux de log (du + au - verbeux) :
    # debug, info, notice, warn, error (défaut), crit, alert, emerg
    #
    # Spécifier le niveau :
    # error_log /path/to/log warn;
}

# ═══════════════════════════════════════════════════════════════
# FIN DU SERVER BLOCK
# ═══════════════════════════════════════════════════════════════
```

**Sauvegarde : `Ctrl + O`, `Entrée`, `Ctrl + X`**

---

**Points clés Nginx vs Apache :**

| Directive | Apache | Nginx |
|-----------|--------|-------|
| **Host virtuel** | `<VirtualHost>` | `server {}` |
| **Écoute port** | `<VirtualHost *:80>` | `listen 80;` |
| **Nom domaine** | `ServerName` | `server_name` |
| **Racine site** | `DocumentRoot` | `root` |
| **Fichiers index** | `DirectoryIndex` | `index` |
| **Répertoire** | `<Directory>` | `location {}` |
| **Réécriture** | `mod_rewrite` (lourd) | `try_files` (léger) |
| **Cache** | `mod_expires` | `expires` |
| **Logs** | `CustomLog`, `ErrorLog` | `access_log`, `error_log` |

---

### ÉTAPE 8 : Activer le server block

**Créer un lien symbolique (comme Apache) :**

```bash
sudo ln -s /etc/nginx/sites-available/portfolio /etc/nginx/sites-enabled/
```

**Explication :**

**`ln -s source destination`**
- `ln` = Link (créer un lien)
- `-s` = Symbolic (lien symbolique, pas un lien dur)
- Source = Fichier original dans `sites-available/`
- Destination = Lien dans `sites-enabled/`

**Pourquoi des liens symboliques ?**
- Permet d'activer/désactiver facilement
- Fichier original reste intact
- Même système qu'Apache (a2ensite/a2dissite)

---

**Vérifier le lien :**

```bash
ls -la /etc/nginx/sites-enabled/
```

**Résultat :**

```
lrwxrwxrwx 1 root root 34 Dec 16 12:00 portfolio -> /etc/nginx/sites-available/portfolio
```

**[OK] Lien symbolique créé !**

---

**Désactiver le site par défaut (optionnel) :**

```bash
sudo rm /etc/nginx/sites-enabled/default
```

**Ou le renommer :**

```bash
sudo mv /etc/nginx/sites-enabled/default /etc/nginx/sites-enabled/default.disabled
```

---

### ÉTAPE 9 : Tester la configuration Nginx

**[ATTENTION] TOUJOURS tester avant de recharger !**

```bash
sudo nginx -t
```

**Résultat si OK :**

```
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```

**[OK] Configuration valide !**

---

**Si erreur, exemple :**

```
nginx: [emerg] unknown directive "listenn" in /etc/nginx/sites-enabled/portfolio:10
nginx: configuration file /etc/nginx/nginx.conf test failed
```

**L'erreur indique :**
- Le fichier : `/etc/nginx/sites-enabled/portfolio`
- La ligne : `10`
- Le problème : `unknown directive "listenn"` (faute de frappe)

**Corriger et retester jusqu'à "syntax is ok".**

---

### ÉTAPE 10 : Recharger Nginx

```bash
sudo systemctl reload nginx
```

**Différence reload vs restart :**

| Commande | Action | Connexions | Downtime |
|----------|--------|------------|----------|
| **reload** | Recharge config | Maintenues | [X] Aucun |
| **restart** | Arrête/Redémarre | Fermées | [ATTENTION] Quelques secondes |

**Toujours utiliser `reload` en production !**

---

**Vérifier que Nginx tourne toujours :**

```bash
sudo systemctl status nginx
```

**Doit être `active (running)`.**

---

### ÉTAPE 11 : Configurer le fichier hosts

**Sur ton PC (pas le serveur), éditer `/etc/hosts` :**

**Linux/macOS :**

```bash
sudo nano /etc/hosts
```

**Windows :**

```
1. Ouvre Notepad en administrateur
2. Fichier -> Ouvrir -> C:\Windows\System32\drivers\etc\hosts
3. Type : Tous les fichiers
```

**Ajouter :**

```
192.168.1.100    portfolio.local
```

**Remplace `192.168.1.100` par l'IP de ton serveur.**

**Sauvegarde.**

---

### ÉTAPE 12 : Tester le site

**Ouvre ton navigateur :**

```
http://portfolio.local
```

**[BRAVO] TU DEVRAIS VOIR TON SITE PORTFOLIO ! [BRAVO]**

---

**Tester la navigation :**

[OK] Cliquer sur "À propos" -> about.html s'affiche
[OK] Cliquer sur "Portfolio" -> portfolio.html s'affiche
[OK] Le CSS est chargé (style appliqué)
[OK] Les images placeholder s'affichent

---

### [OK] TESTS DE VALIDATION

**1. Server block fonctionne**

```bash
curl http://portfolio.local
```

**Doit retourner le HTML de index.html.**

---

**2. Fichiers statiques**

```bash
curl -I http://portfolio.local/css/style.css
```

**Vérifier :**

```
HTTP/1.1 200 OK
Content-Type: text/css
Cache-Control: public, immutable
Expires: ...
```

**[OK] Cache configuré correctement !**

---

**3. Fichiers cachés bloqués**

```bash
curl http://portfolio.local/.git
```

**Doit retourner : `403 Forbidden`**

---

**4. Logs Nginx**

```bash
sudo tail -20 /var/log/nginx/portfolio-access.log
```

**Tu devrais voir tes requêtes :**

```
192.168.1.50 - - [16/Dec/2024:12:00:00 +0000] "GET / HTTP/1.1" 200 1234 "-" "Mozilla/5.0..."
192.168.1.50 - - [16/Dec/2024:12:00:01 +0000] "GET /css/style.css HTTP/1.1" 200 5678 "http://portfolio.local/" "Mozilla/5.0..."
```

---

**5. Performance Nginx**

```bash
ab -n 1000 -c 100 http://portfolio.local/
```

**ApacheBench : 1000 requêtes, 100 concurrentes**

**Résultat attendu :**

```
Requests per second:    5000+ [#/sec] (mean)
Time per request:       0.020 [ms] (mean, across all concurrent requests)
```

**[RAPIDE] Nginx est ULTRA rapide pour les fichiers statiques !**

---

### [ROUGE] ERREURS COURANTES ET SOLUTIONS

#### Erreur 1 : "Connection refused"

**Symptôme :**

```
curl: (7) Failed to connect to portfolio.local port 80: Connection refused
```

**Cause : Nginx ne tourne pas**

**Solution :**

```bash
sudo systemctl status nginx
# Si inactif :
sudo systemctl start nginx
```

---

#### Erreur 2 : "403 Forbidden"

**Symptôme :**

```
403 Forbidden
nginx/1.18.0 (Ubuntu)
```

**Causes possibles :**

**1. Permissions incorrectes**

```bash
# Vérifier
ls -la /var/www/portfolio/

# Corriger
sudo chown -R www-data:www-data /var/www/portfolio
sudo chmod 755 /var/www/portfolio
sudo chmod 644 /var/www/portfolio/index.html
```

---

**2. Pas de fichier index**

```bash
# Vérifier que index.html existe
ls -l /var/www/portfolio/index.html

# Si absent, le créer
```

---

**3. Directive index manquante**

**Dans le server block, vérifier :**

```nginx
index index.html index.htm;
```

---

#### Erreur 3 : "404 Not Found" sur CSS/images

**Symptôme :**

HTML s'affiche mais sans style.

**Cause : Chemins incorrects**

**Solution :**

**Dans index.html, vérifier :**

```html
<!-- [OK] BON (chemin relatif) -->
<link rel="stylesheet" href="css/style.css">

<!-- [X] MAUVAIS (chemin absolu local) -->
<link rel="stylesheet" href="/var/www/portfolio/css/style.css">
```

---

**Tester avec curl :**

```bash
curl -I http://portfolio.local/css/style.css
```

**Doit retourner `200 OK`.**

---

#### Erreur 4 : "nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)"

**Symptôme :**

Nginx ne démarre pas, port 80 occupé.

**Cause : Autre serveur web (Apache ?) sur le port 80**

**Solution :**

**Voir quel processus utilise le port 80 :**

```bash
sudo lsof -i :80
```

**Résultat (exemple) :**

```
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
apache2  1234     root    4u  IPv6  12345      0t0  TCP *:http (LISTEN)
```

**Arrêter Apache :**

```bash
sudo systemctl stop apache2
sudo systemctl disable apache2
```

**Démarrer Nginx :**

```bash
sudo systemctl start nginx
```

---

#### Erreur 5 : Changements dans server block pas appliqués

**Symptôme :**

Tu modifies le server block, recharges Nginx, mais rien ne change.

**Causes :**

**1. Oubli de recharger**

```bash
sudo systemctl reload nginx
```

---

**2. Erreur de syntaxe (non détectée)**

```bash
# TOUJOURS tester avant
sudo nginx -t
```

---

**3. Cache navigateur**

Vider le cache : `Ctrl + Shift + R` (Chrome/Firefox)

---

**4. Mauvais server block sélectionné**

**Nginx utilise le premier server block qui match.**

**Vérifier les `server_name` :**

```bash
grep -r "server_name" /etc/nginx/sites-enabled/
```

**Si plusieurs sites ont le même `server_name`, c'est le premier chargé qui gagne.**

---

### [IMPORTANT] POINTS CLÉS À RETENIR

**1. Architecture Nginx**
- Event-driven (asynchrone)
- 1 worker = 10,000+ connexions
- Plus rapide qu'Apache pour fichiers statiques

**2. Configuration Nginx**
- Centralisée (pas de .htaccess)
- Server blocks au lieu de VirtualHosts
- Syntaxe différente mais plus simple

**3. Directives principales**
- `listen` = Port d'écoute
- `server_name` = Nom de domaine
- `root` = Racine du site
- `location` = Bloc de configuration par URL
- `try_files` = Gestion des fichiers (très puissant)

**4. Gestion des fichiers statiques**
- Nginx excelle dans ce domaine
- Cache navigateur avec `expires`
- Logs désactivés pour performance

**5. Sécurité de base**
- Bloquer fichiers cachés (`location ~ /\.`)
- Permissions correctes (www-data, 755/644)
- Logs séparés par site

---

### [RAPIDE] POUR ALLER PLUS LOIN

**1. Ajouter plusieurs domaines (server_name)**

```nginx
server {
    listen 80;
    server_name portfolio.local www.portfolio.local dev.portfolio.local;
    # ...
}
```

---

**2. Redirection www -> non-www**

```nginx
server {
    listen 80;
    server_name www.portfolio.local;
    return 301 http://portfolio.local$request_uri;
}
```

---

**3. Activer la compression Gzip**

**Dans nginx.conf (contexte http) :**

```nginx
gzip on;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
```

---

**4. Ajouter des headers de sécurité**

**Dans le server block :**

```nginx
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
```

---

**5. Créer un fichier snippet réutilisable**

```bash
sudo nano /etc/nginx/snippets/security-headers.conf
```

**Contenu :**

```nginx
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
```

**Dans ton server block :**

```nginx
include snippets/security-headers.conf;
```

---

## [COURS] CONCLUSION DE L'EXERCICE 1

**[OK] Félicitations ! Tu as créé et déployé ton premier site avec Nginx !**

**Ce que tu as appris :**
- Installer et configurer Nginx
- Créer un server block
- Comprendre la syntaxe Nginx
- Gérer les fichiers statiques efficacement
- Différences entre Apache et Nginx
- Tester et debugger Nginx

**Compétences acquises :**
- [OK] Installation Nginx (niveau débutant)
- [OK] Configuration server blocks
- [OK] Directive `try_files`
- [OK] Gestion des logs
- [OK] Cache navigateur

**Temps moyen de réalisation :** 2-3 heures

**Prochaine étape :** Exercice 2 - HTTPS avec certificat SSL ! [VERROUILLE]

---

---

*(Continuer avec les exercices 2, 3, 4 et 5 dans le même style ultra-détaillé...)*

**Note:** Ceci est un extrait détaillé du premier exercice. Veux-tu que je continue avec les exercices 2, 3, 4 et 5 avec le même niveau de détail ?


# [JAUNE] EXERCICE 2 : HTTPS AVEC CERTIFICAT AUTO-SIGNÉ SUR NGINX

## [LISTE] ÉNONCÉ

### Contexte professionnel

Tu travailles pour une startup qui développe une application web interne. L'équipe te demande de sécuriser le site portfolio avec HTTPS pour l'environnement de développement, avant de déployer en production avec un vrai certificat.

### Objectifs

- Créer un certificat SSL auto-signé
- Configurer Nginx pour HTTPS (port 443)
- Rediriger automatiquement HTTP -> HTTPS
- Implémenter tous les headers de sécurité modernes
- Optimiser la configuration SSL/TLS

### Différences majeures avec Apache

| Aspect | Apache | Nginx |
|--------|--------|-------|
| **Modules SSL** | mod_ssl (à activer) | Compilé nativement |
| **Config SSL** | Verbeux | Plus concis |
| **Performance SSL** | Bon | * Excellent |
| **Session cache** | socache_shmcb | ssl_session_cache |
| **OCSP Stapling** | SSLUseStapling | ssl_stapling |

### Contraintes

- Nginx 1.18+
- OpenSSL 1.1.1+
- Protocoles TLS 1.2 et 1.3 uniquement
- Temps estimé : 1-2 heures

---

## [OBJECTIF] OBJECTIFS PÉDAGOGIQUES

À la fin de cet exercice, tu sauras :

- [OK] Créer un certificat SSL avec OpenSSL
- [OK] Configurer SSL/TLS sur Nginx
- [OK] Optimiser les performances SSL
- [OK] Configurer le session cache SSL
- [OK] Implémenter OCSP Stapling
- [OK] Ajouter tous les headers de sécurité
- [OK] Forcer la redirection HTTPS
- [OK] Comprendre les différences SSL Apache/Nginx

---

## [DOCS] PRÉREQUIS

- Exercice 1 terminé (site portfolio fonctionnel)
- OpenSSL installé
- Compréhension de SSL/TLS

---

## [OK] SOLUTION COMPLÈTE

### ÉTAPE 1 : Comprendre SSL/TLS sur Nginx

**Nginx a SSL/TLS compilé nativement, contrairement à Apache qui nécessite mod_ssl.**

#### Avantages de Nginx pour SSL/TLS

**1. Performance**

```
Apache + mod_ssl : ~500-1000 connexions SSL/s
Nginx : ~2000-5000 connexions SSL/s
```

**2. Mémoire**

```
Apache : ~10-15 MB par worker avec SSL
Nginx : ~2-3 MB par worker avec SSL
```

**3. Configuration**

```
Apache : ~50 lignes de config SSL
Nginx : ~20 lignes de config SSL
```

---

#### Architecture SSL sur Nginx

**Nginx utilise un modèle événementiel pour SSL :**

```
Client -> SSL Handshake (asynchrone) -> Worker Nginx
         v
    Session Cache (partagé entre workers)
         v
    Traitement requête (sans bloquer)
         v
    Réponse chiffrée
```

**Avantage :** Un worker peut gérer des milliers de handshakes SSL simultanément !

---

### ÉTAPE 2 : Créer le certificat SSL auto-signé

**Créer le dossier pour les certificats :**

```bash
sudo mkdir -p /etc/nginx/ssl
```

---

**Générer le certificat et la clé privée en une commande :**

```bash
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/ssl/portfolio.key \
  -out /etc/nginx/ssl/portfolio.crt \
  -subj "/C=SN/ST=Dakar/L=Dakar/O=Portfolio/CN=portfolio.local"
```

**Décomposition de la commande :**

**`openssl req`** : Outil de gestion de certificats

**`-x509`** : Crée un certificat X.509 auto-signé
- Sans cette option, créerait une CSR (Certificate Signing Request)
- Avec cette option, signe directement le certificat

**`-nodes`** : NO DES (pas de chiffrement de la clé)
- La clé privée ne sera PAS protégée par mot de passe
- Nécessaire pour que Nginx démarre automatiquement
- En production avec certificat important, on pourrait chiffrer

**`-days 365`** : Validité 1 an
- Après 365 jours, le certificat expire
- Pour tests : 365 jours suffisant
- En prod avec Let's Encrypt : 90 jours (renouvellement auto)

**`-newkey rsa:2048`** : Nouvelle clé RSA 2048 bits
- RSA = Algorithme de chiffrement asymétrique
- 2048 bits = Sécurité suffisante (standard actuel)
- 4096 bits = Plus sécurisé mais plus lent

**`-keyout /etc/nginx/ssl/portfolio.key`** : Fichier clé privée

**`-out /etc/nginx/ssl/portfolio.crt`** : Fichier certificat

**`-subj`** : Informations du certificat (évite les questions interactives)
- C = Country (SN = Sénégal)
- ST = State (Dakar)
- L = Locality (Dakar)
- O = Organization (Portfolio)
- CN = Common Name (**DOIT correspondre au domaine !**)

---

**Vérifier les fichiers créés :**

```bash
ls -lh /etc/nginx/ssl/
```

**Résultat :**

```
-rw-r--r-- 1 root root 1.3K Dec 16 14:00 portfolio.crt
-rw------- 1 root root 1.7K Dec 16 14:00 portfolio.key
```

**Permissions importantes :**

**portfolio.crt (644)** : Certificat public, peut être lu par tous
**portfolio.key (600)** : Clé privée, lecture seule par root !

---

**Examiner le certificat :**

```bash
sudo openssl x509 -in /etc/nginx/ssl/portfolio.crt -text -noout
```

**Résultat (extrait) :**

```
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            xx:xx:xx:xx:xx:xx:xx:xx
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = SN, ST = Dakar, L = Dakar, O = Portfolio, CN = portfolio.local
        Validity
            Not Before: Dec 16 14:00:00 2024 GMT
            Not After : Dec 16 14:00:00 2025 GMT
        Subject: C = SN, ST = Dakar, L = Dakar, O = Portfolio, CN = portfolio.local
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
```

**Points à vérifier :**

[OK] Issuer = Subject (auto-signé)
[OK] CN = portfolio.local (correspond au domaine)
[OK] Validité : 1 an
[OK] RSA 2048 bits

---

### ÉTAPE 3 : Configurer Nginx pour HTTPS

**Créer le server block HTTPS :**

```bash
sudo nano /etc/nginx/sites-available/portfolio-ssl
```

**Contenu :**

```nginx
# ═══════════════════════════════════════════════════════════════
# SERVER BLOCK - PORTFOLIO (HTTPS)
# ═══════════════════════════════════════════════════════════════
# Description : Site portfolio sécurisé avec SSL/TLS
# Port : 443 (HTTPS)
# ═══════════════════════════════════════════════════════════════

server {
    # ───────────────────────────────────────────────────────────
    # ÉCOUTE HTTPS
    # ───────────────────────────────────────────────────────────
    
    listen 443 ssl http2;
    # Port 443 avec SSL et HTTP/2
    #
    # Décomposition :
    # 443 = Port standard HTTPS
    # ssl = Active SSL/TLS pour cette connexion
    # http2 = Active HTTP/2 (plus rapide que HTTP/1.1)
    #
    # HTTP/2 avantages :
    # - Multiplexage (plusieurs requêtes sur 1 connexion)
    # - Compression des headers
    # - Server Push
    # - Prioritisation des ressources
    #
    # [ATTENTION] HTTP/2 nécessite HTTPS (pas HTTP)
    #
    # Équivalent Apache :
    # <VirtualHost *:443>
    #     SSLEngine on
    #     Protocols h2 http/1.1
    # </VirtualHost>
    
    listen [::]:443 ssl http2;
    # IPv6 avec SSL et HTTP/2
    
    server_name portfolio.local;
    
    # ───────────────────────────────────────────────────────────
    # CERTIFICAT SSL/TLS
    # ───────────────────────────────────────────────────────────
    
    ssl_certificate /etc/nginx/ssl/portfolio.crt;
    # Chemin vers le certificat (clé publique)
    # Équivalent Apache : SSLCertificateFile
    
    ssl_certificate_key /etc/nginx/ssl/portfolio.key;
    # Chemin vers la clé privée
    # Équivalent Apache : SSLCertificateKeyFile
    
    # [ATTENTION] Ces deux fichiers DOIVENT correspondre !
    # La clé privée a généré la clé publique du certificat
    
    # ───────────────────────────────────────────────────────────
    # PROTOCOLES SSL/TLS
    # ───────────────────────────────────────────────────────────
    
    ssl_protocols TLSv1.2 TLSv1.3;
    # Protocoles autorisés : TLS 1.2 et TLS 1.3
    #
    # Équivalent Apache :
    # SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
    #
    # Nginx : Plus simple et direct !
    #
    # Historique des protocoles :
    # SSLv2 (1995) : [X] Obsolète, vulnérable (DROWN)
    # SSLv3 (1996) : [X] Obsolète, vulnérable (POODLE)
    # TLS 1.0 (1999) : [X] Obsolète, faible
    # TLS 1.1 (2006) : [X] Obsolète, déprécié
    # TLS 1.2 (2008) : [OK] Sécurisé, standard actuel
    # TLS 1.3 (2018) : [OK] Le plus récent et sécurisé
    #
    # Compatibilité navigateurs :
    # TLS 1.2 : Chrome 30+, Firefox 27+, Safari 7+ (2013+)
    # TLS 1.3 : Chrome 70+, Firefox 63+, Safari 12.1+ (2018+)
    #
    # [ATTENTION] Si support IE11/vieux Android : garder TLSv1.2
    # Sinon : TLSv1.3 uniquement pour max sécurité
    
    # ───────────────────────────────────────────────────────────
    # SUITES DE CHIFFREMENT (CIPHER SUITES)
    # ───────────────────────────────────────────────────────────
    
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
    # Liste des algorithmes de chiffrement autorisés
    #
    # Décomposition d'une suite (ex: ECDHE-RSA-AES128-GCM-SHA256) :
    # - ECDHE : Échange de clés (Elliptic Curve Diffie-Hellman Ephemeral)
    # - RSA : Authentification/signature
    # - AES128-GCM : Chiffrement symétrique (128 bits, mode GCM)
    # - SHA256 : Fonction de hachage pour intégrité
    #
    # Ordre de préférence (du + au - sécurisé) :
    # 1. ECDHE-ECDSA (courbe elliptique + signature ECDSA)
    # 2. ECDHE-RSA (courbe elliptique + signature RSA)
    # 3. CHACHA20-POLY1305 (chiffrement moderne, rapide sur mobile)
    # 4. DHE-RSA (Diffie-Hellman + RSA, compatible ancien)
    #
    # On EXCLUT :
    # - RC4 : Cassé
    # - 3DES : Lent et faible
    # - MD5 : Fonction de hachage cassée
    # - Export : Chiffrement faible (lois US années 90)
    # - aNULL : Pas d'authentification (MITM possible)
    #
    # Configuration Mozilla moderne (recommandée) :
    # https://ssl-config.mozilla.org/
    
    ssl_prefer_server_ciphers on;
    # Le serveur impose l'ordre des cipher suites
    # Équivalent Apache : SSLHonorCipherOrder on
    #
    # Sans ça, le CLIENT choisit (peut choisir une suite faible)
    # Avec ça, le SERVEUR choisit (on force les suites fortes)
    
    # ───────────────────────────────────────────────────────────
    # OPTIMISATION SSL : SESSION CACHE
    # ───────────────────────────────────────────────────────────
    
    ssl_session_cache shared:SSL:10m;
    # Cache partagé des sessions SSL
    #
    # Décomposition :
    # shared:SSL:10m = Cache partagé nommé "SSL" de 10 MB
    #
    # Pourquoi un cache de sessions ?
    # Le handshake SSL est COÛTEUX (CPU, latence) :
    # 1. Client Hello
    # 2. Server Hello + Certificat
    # 3. Échange de clés
    # 4. Changement vers chiffrement
    # 5. Verification
    # -> ~5-7 allers-retours réseau = ~200-500ms !
    #
    # Avec session cache :
    # 1. Client envoie Session ID
    # 2. Serveur reconnaît la session
    # 3. Reprise immédiate (session resumption)
    # -> ~1 aller-retour = ~50ms !
    #
    # Taille du cache :
    # 1 MB ≈ 4000 sessions
    # 10 MB ≈ 40,000 sessions
    #
    # Types de cache :
    # none : Pas de cache (très mauvais pour performance)
    # builtin : Cache par worker (pas partagé, inutile)
    # shared:NAME:SIZE : Cache partagé entre tous les workers *
    #
    # Équivalent Apache :
    # SSLSessionCache shmcb:/var/cache/apache2/ssl_scache(512000)
    
    ssl_session_timeout 10m;
    # Durée de validité d'une session en cache
    # Après 10 minutes, le client doit refaire un full handshake
    #
    # Compromis :
    # Court (5m) : Plus sécurisé, plus de handshakes
    # Long (1h) : Moins sécurisé, moins de handshakes
    # 10m : Bon équilibre
    
    ssl_session_tickets off;
    # Désactive les tickets de session
    #
    # Session tickets = Alternative au session cache
    # Permet au CLIENT de stocker la session (chiffrée par le serveur)
    #
    # Problème de sécurité :
    # Si la clé de chiffrement des tickets fuite :
    # -> Toutes les anciennes sessions peuvent être déchiffrées
    # -> Pas de Perfect Forward Secrecy
    #
    # Meilleure pratique : off (utiliser session cache à la place)
    #
    # Équivalent Apache :
    # SSLSessionTickets off
    
    # ───────────────────────────────────────────────────────────
    # OPTIMISATION SSL : DHPARAM (pour DHE cipher suites)
    # ───────────────────────────────────────────────────────────
    
    # ssl_dhparam /etc/nginx/ssl/dhparam.pem;
    # Paramètres Diffie-Hellman personnalisés
    #
    # Nécessaire SEULEMENT si on utilise DHE cipher suites
    # Avec ECDHE (courbes elliptiques), pas nécessaire
    #
    # Génération (TRÈS LENT, 5-10 minutes) :
    # sudo openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
    #
    # Pour cet exercice, on utilise seulement ECDHE, donc commenté
    
    # ───────────────────────────────────────────────────────────
    # OCSP STAPLING (pour éviter requêtes OCSP du client)
    # ───────────────────────────────────────────────────────────
    
    ssl_stapling on;
    # Active OCSP Stapling
    #
    # OCSP = Online Certificate Status Protocol
    # Vérifie si un certificat est révoqué
    #
    # Sans stapling :
    # Client -> Serveur : Hello
    # Client -> CA : Ce certificat est-il révoqué ?
    # CA -> Client : Non, il est valide
    # Client -> Serveur : OK, continuons
    # -> Ralentissement + fuite de privacy (CA voit où tu vas)
    #
    # Avec stapling :
    # Serveur récupère périodiquement le statut OCSP auprès de la CA
    # Serveur envoie ce statut signé au client pendant le handshake
    # -> Pas de requête OCSP du client = plus rapide + plus privé
    #
    # Équivalent Apache :
    # SSLUseStapling on
    
    ssl_stapling_verify on;
    # Vérifie la réponse OCSP reçue de la CA
    # Sécurité supplémentaire
    
    # ssl_trusted_certificate /path/to/ca-bundle.crt;
    # Chaîne de certificats CA pour vérifier le stapling
    # Nécessaire SEULEMENT avec un vrai certificat (pas auto-signé)
    # Pour Let's Encrypt : /etc/letsencrypt/live/domain/chain.pem
    
    # ───────────────────────────────────────────────────────────
    # HEADERS DE SÉCURITÉ
    # ───────────────────────────────────────────────────────────
    
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    # HSTS : HTTP Strict Transport Security
    #
    # Force HTTPS pendant 1 an (31536000 secondes)
    # includeSubDomains : S'applique aux sous-domaines
    # preload : Eligible pour la liste de préchargement des navigateurs
    #
    # Fonctionnement :
    # 1ère visite (HTTP) : Redirigé vers HTTPS
    # Serveur envoie header HSTS
    # Navigateur mémorise : "Ce site = TOUJOURS HTTPS"
    #
    # Visites suivantes :
    # User tape http://portfolio.local
    # Navigateur INTERCEPTE et convertit en https:// AVANT d'envoyer
    # -> Aucune requête HTTP n'est jamais envoyée !
    #
    # Sécurité :
    # Protège contre SSL stripping (attaque MITM)
    # Empêche les erreurs de saisie (http au lieu de https)
    #
    # [ATTENTION] Attention en dev :
    # Une fois HSTS activé, impossible de revenir à HTTP
    # pendant la durée du max-age
    # Solution : Vider le cache HSTS du navigateur
    # Chrome : chrome://net-internals/#hsts
    
    add_header X-Frame-Options "DENY" always;
    # Protection contre Clickjacking
    #
    # Valeurs possibles :
    # DENY : Interdit toutes les iframes
    # SAMEORIGIN : Autorise les iframes du même domaine
    # ALLOW-FROM https://example.com : Domaine spécifique (déprécié)
    #
    # Attaque Clickjacking :
    # Site malveillant affiche ton site dans une iframe invisible
    # User clique sur un bouton visible
    # En réalité, clique sur un bouton de ton site (caché)
    # -> Action non désirée (ex: suppression compte, achat, etc.)
    #
    # DENY = Meilleure protection
    
    add_header X-Content-Type-Options "nosniff" always;
    # Empêche le MIME sniffing
    #
    # MIME sniffing = Le navigateur "devine" le type de fichier
    # Même si le serveur dit Content-Type: text/plain
    # Si le contenu ressemble à du JavaScript, le navigateur l'exécute
    #
    # Attaque :
    # Upload d'un fichier .txt contenant du JavaScript
    # Le navigateur l'exécute quand même (XSS)
    #
    # nosniff = Le navigateur respecte strictement le Content-Type
    # -> Si text/plain, jamais exécuté comme JavaScript
    
    add_header X-XSS-Protection "1; mode=block" always;
    # Filtre XSS du navigateur
    #
    # 1 = Activé
    # mode=block = Bloque toute la page si XSS détecté
    #
    # Note : Déprécié au profit de CSP
    # Mais utile pour vieux navigateurs
    
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    # Contrôle l'envoi du header Referer
    #
    # strict-origin-when-cross-origin :
    # - Même origine : URL complète
    # - Cross-origin HTTPS->HTTPS : Origine seulement
    # - Cross-origin HTTPS->HTTP : Rien
    #
    # Protège la vie privée (ne révèle pas l'URL complète)
    
    # always : Ajoute le header même en cas d'erreur (404, 500, etc.)
    
    # ───────────────────────────────────────────────────────────
    # RACINE DU SITE ET CONFIGURATION
    # ───────────────────────────────────────────────────────────
    
    root /var/www/portfolio;
    index index.html index.htm;
    
    location / {
        try_files $uri $uri/ =404;
    }
    
    # Location pour fichiers statiques (identique exercice 1)
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
    
    # Bloquer fichiers cachés
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
    
    # ───────────────────────────────────────────────────────────
    # LOGS
    # ───────────────────────────────────────────────────────────
    
    access_log /var/log/nginx/portfolio-ssl-access.log;
    error_log /var/log/nginx/portfolio-ssl-error.log;
}

# ═══════════════════════════════════════════════════════════════
# REDIRECTION HTTP -> HTTPS
# ═══════════════════════════════════════════════════════════════

server {
    listen 80;
    listen [::]:80;
    server_name portfolio.local;
    
    # Redirection permanente vers HTTPS
    return 301 https://$server_name$request_uri;
    
    # Explication de la redirection :
    #
    # return 301 = Code HTTP 301 (Moved Permanently)
    # Code 302 (Found) = Temporaire
    # Code 301 = Permanent (navigateurs/moteurs mémorisent)
    #
    # $server_name = portfolio.local
    # $request_uri = /about.html?param=value
    #
    # Exemple :
    # http://portfolio.local/about.html?page=2
    # -> https://portfolio.local/about.html?page=2
    #
    # Équivalent Apache :
    # Redirect permanent / https://portfolio.local/
    #
    # Avantage Nginx :
    # Plus simple et plus performant
    # Pas besoin de mod_rewrite
}
```

**Sauvegarde : `Ctrl + O`, `Entrée`, `Ctrl + X`**

---

### ÉTAPE 4 : Activer le site HTTPS et désactiver HTTP

```bash
# Activer le site HTTPS
sudo ln -s /etc/nginx/sites-available/portfolio-ssl /etc/nginx/sites-enabled/

# Désactiver l'ancien site HTTP (pour éviter conflits)
sudo rm /etc/nginx/sites-enabled/portfolio
```

---

**Tester la configuration :**

```bash
sudo nginx -t
```

**Résultat attendu :**

```
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```

**[OK] Configuration valide !**

---

**Recharger Nginx :**

```bash
sudo systemctl reload nginx
```

---

### ÉTAPE 5 : Vérifier que Nginx écoute sur le port 443

```bash
sudo ss -tlnp | grep :443
```

**Ou :**

```bash
sudo netstat -tlnp | grep :443
```

**Résultat :**

```
LISTEN 0      511          0.0.0.0:443       0.0.0.0:*    users:(("nginx",pid=12345,fd=6))
```

**[OK] Nginx écoute sur le port 443 !**

---

### ÉTAPE 6 : Tester HTTPS dans le navigateur

**Accède à :**

```
https://portfolio.local
```

**[ATTENTION] AVERTISSEMENT DE SÉCURITÉ (normal avec certificat auto-signé)**

**Chrome :**
```
Votre connexion n'est pas privée
NET::ERR_CERT_AUTHORITY_INVALID
```

**Firefox :**
```
Avertissement : risque probable de sécurité
```

**C'EST NORMAL !** Le certificat est auto-signé (pas de CA reconnue).

---

**Continuer malgré l'avertissement :**

**Chrome :**
1. Clic sur "Paramètres avancés"
2. Clic sur "Continuer vers portfolio.local (dangereux)"

**Firefox :**
1. Clic sur "Avancé"
2. Clic sur "Accepter le risque et poursuivre"

---

**[BRAVO] Le site devrait s'afficher en HTTPS ! [BRAVO]**

**Dans la barre d'adresse :**

```
[VERROUILLE] Pas sécurisé | https://portfolio.local
```

**Clique sur le cadenas pour voir les détails :**

```
Certificat (invalide)
Émis à : portfolio.local
Émis par : portfolio.local (auto-signé)
Valide du : 16/12/2024
Valide jusqu'au : 16/12/2025
```

---

**Tester la redirection HTTP -> HTTPS :**

```
http://portfolio.local
```

**Tu devrais être automatiquement redirigé vers `https://`**

**[OK] Redirection fonctionnelle !**

---

### ÉTAPE 7 : Vérifier la configuration SSL avec OpenSSL

**Tester le handshake SSL :**

```bash
openssl s_client -connect portfolio.local:443 -servername portfolio.local
```

**Ou (plus simple) :**

```bash
openssl s_client -connect portfolio.local:443 -servername portfolio.local < /dev/null
```

**Résultat (extrait) :**

```
CONNECTED(00000003)
depth=0 C = SN, ST = Dakar, L = Dakar, O = Portfolio, CN = portfolio.local
verify error:num=18:self signed certificate
verify return:1
---
Certificate chain
 0 s:C = SN, ST = Dakar, L = Dakar, O = Portfolio, CN = portfolio.local
   i:C = SN, ST = Dakar, L = Dakar, O = Portfolio, CN = portfolio.local
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAK...
-----END CERTIFICATE-----
subject=C = SN, ST = Dakar, L = Dakar, O = Portfolio, CN = portfolio.local
issuer=C = SN, ST = Dakar, L = Dakar, O = Portfolio, CN = portfolio.local
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 1339 bytes and written 373 bytes
Verification error: self signed certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 18 (self signed certificate)
---
```

**Points à vérifier :**

[OK] `TLSv1.3` (ou TLSv1.2) = Protocole moderne
[OK] `Cipher is TLS_AES_256_GCM_SHA384` = Chiffrement fort
[OK] `Server public key is 2048 bit` = Taille de clé OK
[OK] `Verify return code: 18` = Auto-signé (normal pour cet exercice)

---

**Tester les protocoles supportés :**

**TLS 1.2 :**

```bash
openssl s_client -connect portfolio.local:443 -tls1_2 < /dev/null 2>&1 | grep "Protocol"
```

**Résultat :**

```
    Protocol  : TLSv1.2
```

**[OK] TLS 1.2 supporté !**

---

**TLS 1.3 :**

```bash
openssl s_client -connect portfolio.local:443 -tls1_3 < /dev/null 2>&1 | grep "Protocol"
```

**Résultat :**

```
    Protocol  : TLSv1.3
```

**[OK] TLS 1.3 supporté !**

---

**TLS 1.0 (devrait échouer) :**

```bash
openssl s_client -connect portfolio.local:443 -tls1 < /dev/null 2>&1 | grep -E "Protocol|error"
```

**Résultat :**

```
140736185743552:error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:ssl/record/rec_layer_s3.c:1543:SSL alert number 70
```

**[OK] TLS 1.0 REFUSÉ (comme configuré) !**

---

### ÉTAPE 8 : Vérifier les headers de sécurité

```bash
curl -Ik https://portfolio.local
```

**`-I`** = Headers seulement (HEAD request)
**`-k`** = Ignore les erreurs de certificat (nécessaire avec auto-signé)

**Résultat :**

```
HTTP/2 200 
server: nginx/1.18.0 (Ubuntu)
date: Mon, 16 Dec 2024 15:00:00 GMT
content-type: text/html
content-length: 1234
last-modified: Mon, 16 Dec 2024 14:00:00 GMT
etag: "675f9a40-4d2"
strict-transport-security: max-age=31536000; includeSubDomains; preload
x-frame-options: DENY
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
referrer-policy: strict-origin-when-cross-origin
accept-ranges: bytes
```

**Vérifier :**

[OK] `HTTP/2` (pas HTTP/1.1)
[OK] `strict-transport-security` présent
[OK] `x-frame-options: DENY`
[OK] `x-content-type-options: nosniff`
[OK] `x-xss-protection: 1; mode=block`
[OK] `referrer-policy: strict-origin-when-cross-origin`

---

### ÉTAPE 9 : Benchmarker les performances SSL

**Installer ApacheBench (si pas déjà fait) :**

```bash
sudo apt install apache2-utils -y
```

---

**Benchmarker HTTPS :**

```bash
ab -n 1000 -c 100 https://portfolio.local/
```

**`-n 1000`** = 1000 requêtes
**`-c 100`** = 100 requêtes concurrentes

**Résultat (exemple) :**

```
Concurrency Level:      100
Time taken for tests:   0.523 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      1234000 bytes
HTML transferred:       123400 bytes
Requests per second:    1912.05 [#/sec] (mean)
Time per request:       52.299 [ms] (mean)
Time per request:       0.523 [ms] (mean, across all concurrent requests)
Transfer rate:          2304.57 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1   20  10.2     18      50
Processing:     2   25  12.3     23      65
Waiting:        1   20   9.8     18      52
Total:          5   45  18.5     42     102

Percentage of the requests served within a certain time (ms)
  50%     42
  66%     48
  75%     53
  80%     57
  90%     67
  95%     78
  98%     89
  99%     94
 100%    102 (longest request)
```

**[OK] Nginx gère 1000+ requêtes HTTPS par seconde !**

---

**Comparer avec HTTP (sans SSL) :**

```bash
# Temporairement, créer un site HTTP pour comparer
# ...

ab -n 1000 -c 100 http://test.local/

# HTTP : ~3000-5000 req/s
# HTTPS : ~1500-2500 req/s
# -> SSL overhead : ~40-50% (normal)
```

---

### ÉTAPE 10 : Optimiser encore plus la configuration SSL

**Ajouter la configuration optimale dans `nginx.conf` (contexte http) :**

```bash
sudo nano /etc/nginx/nginx.conf
```

**Ajouter dans le bloc `http {}` :**

```nginx
http {
    # ... (configuration existante)
    
    # ═══════════════════════════════════════════════════════════
    # OPTIMISATION SSL GLOBALE
    # ═══════════════════════════════════════════════════════════
    
    # Buffer SSL
    ssl_buffer_size 4k;
    # Taille du buffer d'envoi SSL
    # Par défaut : 16k (optimisé pour throughput)
    # 4k : Optimisé pour latence (réduit le TTFB)
    #
    # TTFB = Time To First Byte
    # Plus petit buffer = Premier byte arrive plus vite
    # Mais : Moins efficace pour gros fichiers
    #
    # Recommandation :
    # 4k : Sites dynamiques (API, pages HTML)
    # 16k : Téléchargement de fichiers volumineux
    
    # Resolver DNS pour OCSP stapling
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
    # Serveurs DNS pour résoudre le serveur OCSP
    # 8.8.8.8 / 8.8.4.4 = Google DNS (publics)
    # valid=300s : Cache DNS pendant 5 minutes
    # timeout 5s : Échec après 5 secondes
    #
    # Nécessaire pour ssl_stapling
    # En production, utiliser les DNS de ton hébergeur
}
```

**Sauvegarde.**

---

**Tester et recharger :**

```bash
sudo nginx -t
sudo systemctl reload nginx
```

---

### [OK] TESTS DE VALIDATION

**1. HTTPS fonctionne**

```bash
curl -Ik https://portfolio.local
```

- [ ] Code 200 OK
- [ ] HTTP/2 utilisé
- [ ] Headers de sécurité présents

---

**2. Redirection HTTP -> HTTPS**

```bash
curl -I http://portfolio.local
```

**Doit retourner :**

```
HTTP/1.1 301 Moved Permanently
Location: https://portfolio.local/
```

---

**3. Protocoles sécurisés uniquement**

```bash
# TLS 1.2 : [OK] Doit fonctionner
openssl s_client -connect portfolio.local:443 -tls1_2 < /dev/null 2>&1 | grep "Protocol"

# TLS 1.1 : [X] Doit échouer
openssl s_client -connect portfolio.local:443 -tls1_1 < /dev/null 2>&1 | grep -i "error"
```

---

**4. Session resumption (cache)**

**Première connexion :**

```bash
openssl s_client -connect portfolio.local:443 -reconnect -brief 2>&1 | grep -E "Reused|New"
```

**Résultat attendu :**

```
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Reused, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Reused, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Reused, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Reused, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
```

**1ère connexion = "New" (full handshake)**
**Suivantes = "Reused" (session resumption)**

**[OK] Session cache fonctionne !**

---

**5. HSTS fonctionne**

**1ère visite :**

```bash
curl -Ik https://portfolio.local | grep -i strict
```

**Résultat :**

```
strict-transport-security: max-age=31536000; includeSubDomains; preload
```

**Navigateur mémorise : "Ce site = HTTPS seulement pendant 1 an"**

---

### [ROUGE] ERREURS COURANTES ET SOLUTIONS

#### Erreur 1 : "SSL: error:0200107B:system library:fopen:Unknown error"

**Symptôme :**

```
nginx: [emerg] SSL_CTX_use_certificate_chain_file("/etc/nginx/ssl/portfolio.crt") failed
```

**Cause : Fichier certificat introuvable ou mauvaises permissions**

**Solution :**

```bash
# Vérifier que le fichier existe
ls -l /etc/nginx/ssl/portfolio.crt

# Si absent, le recréer (voir ÉTAPE 2)

# Corriger les permissions
sudo chmod 644 /etc/nginx/ssl/portfolio.crt
sudo chmod 600 /etc/nginx/ssl/portfolio.key
```

---

#### Erreur 2 : "SSL: error:140AB18F:SSL routines:SSL_CTX_use_certificate:ee key too small"

**Symptôme :**

```
nginx: [emerg] SSL_CTX_use_certificate("/etc/nginx/ssl/portfolio.crt") failed
```

**Cause : Clé RSA trop petite (< 2048 bits)**

**Solution :**

Recréer le certificat avec au moins 2048 bits :

```bash
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/ssl/portfolio.key \
  -out /etc/nginx/ssl/portfolio.crt \
  -subj "/C=SN/ST=Dakar/L=Dakar/O=Portfolio/CN=portfolio.local"
```

---

#### Erreur 3 : "nginx: [warn] "ssl_stapling" ignored, issuer certificate not found"

**Symptôme :**

Warning lors du `nginx -t`

**Cause : Certificat auto-signé (pas de CA pour OCSP)**

**Solution :**

**Pour un certificat auto-signé, c'est normal et sans danger.**

**Pour désactiver le warning, commenter dans le server block :**

```nginx
# ssl_stapling on;
# ssl_stapling_verify on;
```

**Ou ignorer (le warning n'empêche pas Nginx de fonctionner).**

---

#### Erreur 4 : "This site can't provide a secure connection"

**Symptôme :**

Erreur dans le navigateur, impossible de continuer.

**Code : ERR_SSL_PROTOCOL_ERROR**

**Cause : Nginx n'écoute pas en SSL sur le port 443**

**Solution :**

**Vérifier le server block :**

```nginx
listen 443 ssl http2;  # <- "ssl" doit être présent !
```

**Sans `ssl`, Nginx écoute en HTTP sur le port 443 -> Erreur.**

---

#### Erreur 5 : Headers de sécurité absents

**Symptôme :**

```bash
curl -Ik https://portfolio.local | grep -i hsts
# Aucun résultat
```

**Cause : Oubli du mot-clé `always`**

**Solution :**

**Avec `always` :**

```nginx
add_header Strict-Transport-Security "max-age=31536000" always;
```

**Sans `always`, le header n'est ajouté que pour les codes 200, 204, 301, 302, 304.**

**Les codes d'erreur (404, 500, etc.) n'auront PAS le header.**

**`always` = Ajoute le header POUR TOUS les codes.**

---

### [IMPORTANT] POINTS CLÉS À RETENIR

**1. Nginx et SSL/TLS**
- SSL compilé nativement (pas de module)
- Plus performant qu'Apache pour SSL
- Configuration plus simple et concise

**2. Protocoles**
- TLS 1.2 et 1.3 uniquement
- Désactiver tout le reste (SSLv2, SSLv3, TLS 1.0/1.1)

**3. Session cache**
- `ssl_session_cache shared:SSL:10m;`
- Partage entre workers
- Réduit drastiquement la latence SSL

**4. Session tickets**
- À désactiver (`ssl_session_tickets off;`)
- Problème de Perfect Forward Secrecy

**5. OCSP Stapling**
- Évite les requêtes OCSP du client
- Plus rapide et plus privé
- Nécessite un resolver DNS

**6. Headers de sécurité**
- HSTS (Force HTTPS)
- X-Frame-Options (Anti-clickjacking)
- X-Content-Type-Options (Anti-MIME sniffing)
- `always` pour tous les codes de statut

**7. HTTP/2**
- Automatique avec `ssl http2` sur listen
- Multiplexage, compression headers
- Nécessite HTTPS

---

### [RAPIDE] POUR ALLER PLUS LOIN

**1. Passer à un vrai certificat Let's Encrypt**

```bash
# Installer Certbot
sudo apt install certbot python3-certbot-nginx -y

# Obtenir un certificat
sudo certbot --nginx -d portfolio.local

# Renouvellement automatique (cron déjà configuré)
sudo certbot renew --dry-run
```

---

**2. HTTP/3 (QUIC)**

**Nginx 1.25+ supporte HTTP/3 (protocole QUIC sur UDP) :**

```nginx
listen 443 ssl http2;
listen 443 quic reuseport;
add_header Alt-Svc 'h3=":443"; ma=86400';
```

**HTTP/3 = Encore plus rapide (0-RTT, pas de head-of-line blocking)**

---

**3. Certificat avec clé ECDSA (au lieu de RSA)**

**ECDSA = Plus petite clé, même sécurité, handshake plus rapide**

```bash
# Générer clé ECDSA P-256
openssl ecparam -genkey -name prime256v1 -out portfolio-ecdsa.key

# Créer certificat
openssl req -new -x509 -key portfolio-ecdsa.key -out portfolio-ecdsa.crt -days 365
```

**Nginx peut servir 2 certificats (RSA + ECDSA) :**

```nginx
ssl_certificate /etc/nginx/ssl/portfolio-rsa.crt;
ssl_certificate_key /etc/nginx/ssl/portfolio-rsa.key;

ssl_certificate /etc/nginx/ssl/portfolio-ecdsa.crt;
ssl_certificate_key /etc/nginx/ssl/portfolio-ecdsa.key;
```

**Navigateur choisit ECDSA si supporté (plus rapide).**

---

**4. TLS 1.3 uniquement (maximum sécurité)**

```nginx
ssl_protocols TLSv1.3;
```

**[ATTENTION] Incompatible avec IE11, Android < 10, iOS < 12.2**

---

**5. Early Data (0-RTT)**

**TLS 1.3 permet d'envoyer des données dès le premier paquet :**

```nginx
ssl_early_data on;
```

**[ATTENTION] Risque de replay attacks pour requêtes non-idempotentes (POST, etc.)**

**Protection :**

```nginx
location / {
    if ($ssl_early_data) {
        return 425;  # Too Early
    }
}
```

---

## [COURS] CONCLUSION DE L'EXERCICE 2

**[OK] Félicitations ! Tu as sécurisé ton site avec HTTPS sur Nginx !**

**Ce que tu as appris :**
- Créer un certificat SSL auto-signé
- Configurer SSL/TLS sur Nginx
- Optimiser les performances SSL (session cache, buffer size)
- Implémenter OCSP Stapling
- Ajouter tous les headers de sécurité (HSTS, X-Frame-Options, etc.)
- Forcer HTTPS avec redirection
- Activer HTTP/2
- Différences SSL/TLS entre Apache et Nginx

**Compétences acquises :**
- [OK] Configuration SSL/TLS Nginx (niveau intermédiaire)
- [OK] Optimisation performance SSL
- [OK] Headers de sécurité modernes
- [OK] HTTP/2
- [OK] Debugging SSL avec OpenSSL

**Temps moyen de réalisation :** 1-2 heures

**Prochaine étape :** Exercice 3 - Site PHP avec MySQL ! [POSTGRES]

---

---

# [JAUNE] EXERCICE 3 : SITE PHP AVEC MYSQL SUR NGINX

## [LISTE] ÉNONCÉ

### Contexte professionnel

Tu es développeur dans une agence qui a migré d'Apache vers Nginx. Un client te demande de créer un **mini-blog** identique à celui de l'exercice Apache 3, mais optimisé pour Nginx avec PHP-FPM.

### Objectifs

- Installer et configurer PHP-FPM avec Nginx
- Créer une base de données MySQL
- Développer un blog fonctionnel (liste, détail, ajout d'articles)
- Comprendre les différences PHP entre Apache et Nginx
- Optimiser PHP-FPM pour les performances

### Différences majeures Apache vs Nginx pour PHP

| Aspect | Apache | Nginx |
|--------|--------|-------|
| **Gestion PHP** | mod_php OU PHP-FPM | PHP-FPM uniquement |
| **Config PHP** | .htaccess possible | [X] Pas de .htaccess |
| **Performance** | Moyen | * Excellent |
| **Isolation** | Mixte | * Complète (FPM) |
| **Syntaxe** | Location PHP complexe | Location PHP simple |

### Contraintes

- Nginx 1.18+
- PHP 8.1+ avec PHP-FPM
- MySQL 8.0 ou MariaDB 10.6+
- Temps estimé : 3-4 heures

---

## [OBJECTIF] OBJECTIFS PÉDAGOGIQUES

À la fin de cet exercice, tu sauras :

- [OK] Installer PHP-FPM
- [OK] Configurer Nginx pour PHP
- [OK] Comprendre la communication Nginx <-> PHP-FPM
- [OK] Optimiser les pools PHP-FPM
- [OK] Sécuriser les scripts PHP
- [OK] Gérer les sessions PHP
- [OK] Différences pratiques Apache/Nginx pour PHP

---

## [DOCS] PRÉREQUIS

- Exercices 1 et 2 terminés
- MySQL/MariaDB installé (voir exercice Apache 3)
- Connaissances PHP de base

---

## [OK] SOLUTION COMPLÈTE

### ÉTAPE 1 : Comprendre PHP-FPM avec Nginx

**PHP-FPM = PHP FastCGI Process Manager**

#### Architecture Apache avec mod_php

```
Requête -> Apache -> mod_php (dans le process Apache) -> PHP
                   v
              Réponse HTML
```

**Problème :** PHP tourne DANS Apache
- Consommation mémoire élevée
- Crash PHP = Crash Apache
- Pas d'isolation

---

#### Architecture Nginx avec PHP-FPM

```
Requête -> Nginx -> Socket/TCP -> PHP-FPM (process séparé) -> PHP
          v                      v
     Fichiers statiques    Fichiers PHP
          v                      v
     Réponse directe        Réponse HTML
```

**Avantages :**
- [OK] Isolation complète (PHP crash ≠ Nginx crash)
- [OK] Nginx sert les fichiers statiques (ultra rapide)
- [OK] PHP-FPM ne traite QUE le PHP (efficace)
- [OK] Scalabilité (plusieurs pools PHP-FPM)
- [OK] Gestion avancée des processus

---

#### Communication Nginx <-> PHP-FPM

**2 méthodes possibles :**

**1. Socket UNIX (recommandé pour local) :**

```
/run/php/php8.1-fpm.sock
```

**Avantages :**
- Plus rapide (pas de stack TCP/IP)
- Plus sécurisé (fichier local)
- Moins de ressources

---

**2. Socket TCP (pour distant) :**

```
127.0.0.1:9000
```

**Avantages :**
- PHP-FPM peut être sur un autre serveur
- Utile pour architecture distribuée

**Pour cet exercice : Socket UNIX**

---

### ÉTAPE 2 : Installer PHP-FPM et extensions

```bash
# Mettre à jour
sudo apt update

# Installer PHP-FPM et extensions
sudo apt install -y \
    php8.1-fpm \
    php8.1-mysql \
    php8.1-mbstring \
    php8.1-xml \
    php8.1-curl \
    php8.1-gd \
    php8.1-zip
```

**Explications des paquets :**

**php8.1-fpm** : FastCGI Process Manager
**php8.1-mysql** : Extension MySQL/MariaDB (PDO + mysqli)
**php8.1-mbstring** : Chaînes multi-octets (UTF-8)
**php8.1-xml** : Parsing XML
**php8.1-curl** : Requêtes HTTP
**php8.1-gd** : Manipulation d'images
**php8.1-zip** : Compression/décompression ZIP

---

**Vérifier l'installation :**

```bash
# Version PHP
php -v

# Vérifier que PHP-FPM tourne
sudo systemctl status php8.1-fpm
```

**Résultat :**

```
[BLACK_CIRCLE] php8.1-fpm.service - The PHP 8.1 FastCGI Process Manager
     Loaded: loaded (/lib/systemd/system/php8.1-fpm.service; enabled)
     Active: active (running) since Mon 2024-12-16 16:00:00 UTC; 1min ago
   Main PID: 12345 (php-fpm8.1)
      Tasks: 3 (limit: 4915)
     Memory: 15.2M
```

**[OK] PHP-FPM actif !**

---

**Vérifier le socket :**

```bash
ls -lh /run/php/php8.1-fpm.sock
```

**Résultat :**

```
srw-rw---- 1 www-data www-data 0 Dec 16 16:00 /run/php/php8.1-fpm.sock
```

**`s` = Socket (type de fichier spécial)**

**[OK] Socket créé !**

---

### ÉTAPE 3 : Comprendre la configuration PHP-FPM

**Fichiers de configuration PHP-FPM :**

```
/etc/php/8.1/fpm/
├── php.ini                     <- Configuration PHP (comme CLI)
├── php-fpm.conf                <- Configuration principale FPM
└── pool.d/
    └── www.conf                <- Pool "www" (par défaut)
```

---

**Consulter le pool par défaut :**

```bash
sudo nano /etc/php/8.1/fpm/pool.d/www.conf
```

**Contenu (extrait commenté) :**

```ini
; ═══════════════════════════════════════════════════════════════
; POOL PHP-FPM "www"
; ═══════════════════════════════════════════════════════════════

[www]
; Nom du pool
; On peut avoir plusieurs pools (www, admin, api, etc.)
; Chaque pool = Configuration indépendante

; ───────────────────────────────────────────────────────────────
; UTILISATEUR ET GROUPE
; ───────────────────────────────────────────────────────────────

user = www-data
group = www-data
; Processus PHP-FPM tournent sous cet utilisateur
; DOIT correspondre à l'utilisateur Nginx !
; Sinon : erreur de permissions

; ───────────────────────────────────────────────────────────────
; SOCKET D'ÉCOUTE
; ───────────────────────────────────────────────────────────────

listen = /run/php/php8.1-fpm.sock
; Socket UNIX pour communication avec Nginx
;
; Alternatives :
; listen = 127.0.0.1:9000    -> Socket TCP
; listen = 9000              -> TCP sur toutes les IPs

listen.owner = www-data
listen.group = www-data
listen.mode = 0660
; Permissions du socket
; 0660 = rw-rw---- (user et group peuvent lire/écrire)
; Nginx (www-data) peut communiquer avec le socket

; ───────────────────────────────────────────────────────────────
; GESTION DES PROCESSUS
; ───────────────────────────────────────────────────────────────

pm = dynamic
; Mode de gestion des processus
;
; Modes disponibles :
; static : Nombre FIXE de processus (simple, prévisible)
; dynamic : Nombre VARIABLE selon la charge (recommandé)
; ondemand : Créés à la demande (économise mémoire)
;
; dynamic = Meilleur compromis performance/mémoire

pm.max_children = 5
; Nombre MAXIMUM de processus enfants
; 1 process = 1 requête PHP simultanée
;
; Calcul recommandé :
; RAM dispo pour PHP / Mémoire par process
; Exemple : 2 GB / 50 MB = 40 processus
;
; Par défaut : 5 (très bas, pour serveurs limités)
; Production web moyenne : 20-50
; Gros site : 100+

pm.start_servers = 2
; Nombre de processus au DÉMARRAGE
; Doit être entre min_spare et max_spare

pm.min_spare_servers = 1
; Minimum de processus INACTIFS (en attente)
; Si moins -> PHP-FPM en crée

pm.max_spare_servers = 3
; Maximum de processus INACTIFS
; Si plus -> PHP-FPM en tue

; Exemple de cycle (pm = dynamic) :
; Démarrage : 2 processus
; Trafic faible : 1-3 processus inactifs maintenus
; Pic de trafic : Monte jusqu'à 5 processus
; Trafic baisse : Redescend à 1-3 processus inactifs

pm.max_requests = 500
; Nombre de requêtes MAX par processus avant recyclage
; Après 500 requêtes, le processus est tué et recréé
;
; Pourquoi recycler ?
; - Libère la mémoire (évite memory leaks)
; - Réinitialise l'état (évite bugs subtils)
;
; Valeur standard : 500-1000
; Si scripts lents : Réduire (200)
; Si scripts rapides : Augmenter (2000)

; ───────────────────────────────────────────────────────────────
; MONITORING
; ───────────────────────────────────────────────────────────────

;pm.status_path = /status
; URL pour voir le statut du pool
; Activer en production pour monitoring
; Accès : http://blog.local/status

;ping.path = /ping
; URL de health check
; Retourne "pong" si FPM répond
; Utile pour load balancers

; ───────────────────────────────────────────────────────────────
; TIMEOUTS
; ───────────────────────────────────────────────────────────────

;request_terminate_timeout = 0
; Timeout d'exécution d'un script (secondes)
; 0 = Pas de timeout (utilise php.ini max_execution_time)
; 30 = Tue le script après 30 secondes
;
; Utile pour tuer les scripts bloqués
; Évite les processus zombies

; ───────────────────────────────────────────────────────────────
; LOGS
; ───────────────────────────────────────────────────────────────

;slowlog = /var/log/php8.1-fpm-slow.log
;request_slowlog_timeout = 10s
; Log des scripts LENTS (> 10 secondes)
; Aide à identifier les bottlenecks
; Activer en production !

;catch_workers_output = yes
; Capture les echo/print_r dans les logs FPM
; Utile pour debugging
```

**Fermer sans modifier (pour l'instant).**

---

**Points clés du pool :**

**1. Socket** : `/run/php/php8.1-fpm.sock`
**2. User/Group** : `www-data` (DOIT correspondre à Nginx)
**3. PM mode** : `dynamic` (adaptatif)
**4. Max children** : `5` (à augmenter en production)

---

### ÉTAPE 4 : Créer la structure du blog

```bash
# Créer le dossier
sudo mkdir -p /var/www/blog

# Permissions
sudo chown -R www-data:www-data /var/www/blog
```

---

### ÉTAPE 5 : Configurer Nginx pour PHP

**Créer le server block :**

```bash
sudo nano /etc/nginx/sites-available/blog
```

**Contenu :**

```nginx
# ═══════════════════════════════════════════════════════════════
# SERVER BLOCK - BLOG PHP
# ═══════════════════════════════════════════════════════════════

server {
    listen 80;
    listen [::]:80;
    server_name blog.local;
    
    root /var/www/blog;
    index index.php index.html;
    
    # ───────────────────────────────────────────────────────────
    # LOCATION PRINCIPALE
    # ───────────────────────────────────────────────────────────
    
    location / {
        try_files $uri $uri/ =404;
    }
    
    # ───────────────────────────────────────────────────────────
    # LOCATION PHP (* CRUCIAL !)
    # ───────────────────────────────────────────────────────────
    
    location ~ \.php$ {
        # Regex case-sensitive pour fichiers .php
        # ~ = Regex
        # \. = Point littéral
        # php$ = Se termine par "php"
        #
        # Cette location s'applique à :
        # index.php, article.php, admin.php, etc.
        
        # ═══════════════════════════════════════════════════════
        # SÉCURITÉ : VÉRIFIER QUE LE FICHIER EXISTE
        # ═══════════════════════════════════════════════════════
        
        try_files $uri =404;
        # [ATTENTION] TRÈS IMPORTANT !
        #
        # Vérifie que le fichier .php EXISTE
        # Sinon retourne 404
        #
        # Pourquoi c'est crucial ?
        # Évite la faille "PHP-CGI Path Info"
        #
        # Exemple d'attaque sans try_files :
        # http://blog.local/image.jpg/exploit.php
        # Nginx voit ".php" à la fin -> Envoie à PHP-FPM
        # PHP-FPM exécute image.jpg comme PHP !
        # -> Exécution de code arbitraire [!]
        #
        # Avec try_files $uri =404 :
        # Nginx vérifie que "image.jpg/exploit.php" existe
        # N'existe pas -> 404 AVANT d'envoyer à PHP-FPM
        # -> Attaque bloquée [OK]
        
        # ═══════════════════════════════════════════════════════
        # FASTCGI : COMMUNICATION AVEC PHP-FPM
        # ═══════════════════════════════════════════════════════
        
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        # Envoie la requête à PHP-FPM via le socket UNIX
        #
        # Équivalent Apache :
        # <FilesMatch "\.php$">
        #     SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"
        # </FilesMatch>
        #
        # Nginx : Plus simple et direct !
        #
        # Alternative TCP :
        # fastcgi_pass 127.0.0.1:9000;
        
        fastcgi_index index.php;
        # Fichier par défaut si dossier
        # http://blog.local/admin/ -> /admin/index.php
        
        # ═══════════════════════════════════════════════════════
        # FASTCGI_PARAMS : VARIABLES POUR PHP
        # ═══════════════════════════════════════════════════════
        
        include fastcgi_params;
        # Inclut les paramètres FastCGI standards
        # Défini dans /etc/nginx/fastcgi_params
        #
        # Variables importantes envoyées à PHP :
        # - REQUEST_METHOD (GET, POST, etc.)
        # - QUERY_STRING (?id=1&page=2)
        # - REQUEST_URI (/article.php?id=1)
        # - REMOTE_ADDR (IP du client)
        # - SERVER_NAME (blog.local)
        # - SERVER_PORT (80)
        # - HTTP_USER_AGENT (navigateur)
        # - etc.
        
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        # * VARIABLE LA PLUS IMPORTANTE !
        #
        # Indique à PHP-FPM quel fichier exécuter
        #
        # Décomposition :
        # $document_root = /var/www/blog
        # $fastcgi_script_name = /index.php
        # SCRIPT_FILENAME = /var/www/blog/index.php
        #
        # PHP-FPM reçoit :
        # "Exécute /var/www/blog/index.php"
        #
        # [ATTENTION] Sans cette ligne, PHP-FPM ne sait pas quoi exécuter !
        
        # ═══════════════════════════════════════════════════════
        # VARIABLES SUPPLÉMENTAIRES (OPTIONNELLES)
        # ═══════════════════════════════════════════════════════
        
        fastcgi_param PATH_INFO $fastcgi_path_info;
        # Pour les URLs de type :
        # /index.php/extra/path/info
        #
        # PHP reçoit :
        # SCRIPT_FILENAME = /var/www/blog/index.php
        # PATH_INFO = /extra/path/info
        #
        # Utile pour frameworks (Laravel, Symfony, etc.)
        
        # ═══════════════════════════════════════════════════════
        # TIMEOUTS
        # ═══════════════════════════════════════════════════════
        
        fastcgi_connect_timeout 60s;
        # Timeout pour SE CONNECTER à PHP-FPM
        # 60s = Largement suffisant
        # Si dépassé : "502 Bad Gateway"
        
        fastcgi_send_timeout 60s;
        # Timeout pour ENVOYER la requête à PHP-FPM
        # Rare de dépasser (sauf gros upload)
        
        fastcgi_read_timeout 60s;
        # Timeout pour LIRE la réponse de PHP-FPM
        # [ATTENTION] LE PLUS IMPORTANT !
        #
        # Si script PHP prend > 60s -> Nginx coupe la connexion
        #
        # Scripts lourds (import CSV, génération PDF, etc.) :
        # Augmenter à 300s (5 minutes) ou plus
        #
        # Par défaut Nginx : 60s (suffisant pour 99% des cas)
        
        # ═══════════════════════════════════════════════════════
        # BUFFERS (PERFORMANCE)
        # ═══════════════════════════════════════════════════════
        
        fastcgi_buffer_size 128k;
        # Taille du buffer pour les HEADERS de réponse PHP
        # Par défaut : 4k-8k
        # Augmenter si headers volumineux (cookies, etc.)
        
        fastcgi_buffers 4 256k;
        # Nombre et taille des buffers pour le CORPS de réponse
        # 4 buffers de 256k = 1 MB total
        #
        # Buffers trop petits :
        # -> Nginx écrit sur disque (lent)
        #
        # Buffers trop grands :
        # -> Gaspillage de RAM
        #
        # Défaut : 8 buffers de 4k-8k (32k-64k total)
        # Pour réponses JSON/HTML : 256k-512k suffisant
        
        fastcgi_busy_buffers_size 256k;
        # Taille des buffers "occupés" (envoyés au client)
        # Généralement = fastcgi_buffers
    }
    
    # ───────────────────────────────────────────────────────────
    # BLOQUER LES FICHIERS SENSIBLES
    # ───────────────────────────────────────────────────────────
    
    location ~ /\.(?!well-known) {
        # Bloque fichiers cachés SAUF .well-known
        # .well-known = Utilisé par Let's Encrypt, etc.
        deny all;
    }
    
    location ~ \.(bak|config|sql|log|ini)$ {
        # Bloque fichiers de backup et config
        deny all;
    }
    
    # ───────────────────────────────────────────────────────────
    # LOGS
    # ───────────────────────────────────────────────────────────
    
    access_log /var/log/nginx/blog-access.log;
    error_log /var/log/nginx/blog-error.log;
}
```

**Sauvegarde.**

---

**Points clés de la configuration PHP :**

**1. `location ~ \.php$`** : Traite les fichiers .php
**2. `try_files $uri =404;`** : Sécurité (évite faille Path Info)
**3. `fastcgi_pass unix:/run/php/php8.1-fpm.sock;`** : Socket FPM
**4. `fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;`** : Fichier à exécuter
**5. `include fastcgi_params;`** : Variables d'environnement

---

### ÉTAPE 6 : Activer le site

```bash
# Créer lien symbolique
sudo ln -s /etc/nginx/sites-available/blog /etc/nginx/sites-enabled/

# Tester
sudo nginx -t

# Recharger
sudo systemctl reload nginx
```

---

**Ajouter dans `/etc/hosts` (sur ton PC) :**

```
192.168.1.100    blog.local
```

---

### ÉTAPE 7 : Tester PHP avec phpinfo()

**Créer un fichier de test :**

```bash
sudo nano /var/www/blog/info.php
```

**Contenu :**

```php
<?php
phpinfo();
?>
```

**Sauvegarde.**

---

**Accéder dans le navigateur :**

```
http://blog.local/info.php
```

**[BRAVO] Page phpinfo() devrait s'afficher ! [BRAVO]**

**Vérifications :**

[OK] **Server API** : `FPM/FastCGI` (pas Apache 2.0 Handler)
[OK] **PHP Version** : 8.1.x
[OK] **PDO drivers** : mysql présent

---

**Supprimer phpinfo.php (sécurité) :**

```bash
sudo rm /var/www/blog/info.php
```

---

### ÉTAPE 8 : Créer la base de données MySQL

**(Identique à l'exercice Apache 3)**

```bash
# Se connecter à MySQL
sudo mysql -u root -p
```

**Dans MySQL :**

```sql
-- Créer la base
CREATE DATABASE blog_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- Créer l'utilisateur
CREATE USER 'blog_user'@'localhost' IDENTIFIED BY 'BlogPassword123!';

-- Donner les droits
GRANT ALL PRIVILEGES ON blog_db.* TO 'blog_user'@'localhost';
FLUSH PRIVILEGES;

-- Utiliser la base
USE blog_db;

-- Créer la table articles
CREATE TABLE articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    titre VARCHAR(255) NOT NULL,
    contenu TEXT NOT NULL,
    auteur VARCHAR(100) NOT NULL,
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    date_modification DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Insérer des articles de test
INSERT INTO articles (titre, contenu, auteur) VALUES
('Premier Article avec Nginx', 'Ceci est un article hébergé sur Nginx avec PHP-FPM. Les performances sont excellentes !', 'Jean Dupont'),
('Guide Nginx et PHP', 'Nginx combiné à PHP-FPM offre des performances 2-3x supérieures à Apache avec mod_php.', 'Marie Martin'),
('Architecture Moderne', 'La séparation Nginx/PHP-FPM permet une meilleure scalabilité et isolation des services.', 'Paul Durand');

-- Vérifier
SELECT * FROM articles;

-- Quitter
EXIT;
```

---

### ÉTAPE 9 : Créer les fichiers PHP du blog

**Les fichiers PHP sont IDENTIQUES à l'exercice Apache 3.**

**config.php (connexion base de données) :**

```bash
sudo nano /var/www/blog/config.php
```

**Contenu (identique Apache) :**

```php
<?php
define('DB_HOST', 'localhost');
define('DB_NAME', 'blog_db');
define('DB_USER', 'blog_user');
define('DB_PASS', 'BlogPassword123!');
define('DB_CHARSET', 'utf8mb4');

function getDBConnection() {
    try {
        $dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
        $options = [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ];
        $pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
        return $pdo;
    } catch (PDOException $e) {
        error_log($e->getMessage());
        die("Erreur de connexion à la base de données.");
    }
}
?>
```

---

**index.php (liste des articles) :**

```bash
sudo nano /var/www/blog/index.php
```

**Contenu (identique Apache, avec petite mention Nginx) :**

```php
<?php
require_once 'config.php';
$pdo = getDBConnection();

try {
    $sql = "SELECT id, titre, LEFT(contenu, 200) AS extrait, auteur, date_creation 
            FROM articles 
            ORDER BY date_creation DESC";
    $stmt = $pdo->query($sql);
    $articles = $stmt->fetchAll();
} catch (PDOException $e) {
    $error = "Erreur : " . $e->getMessage();
    $articles = [];
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mon Blog PHP + Nginx</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: Arial, sans-serif; background: #f4f4f4; line-height: 1.6; }
        header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 2rem; text-align: center; }
        .container { max-width: 1000px; margin: 2rem auto; padding: 0 2rem; }
        .article { background: white; padding: 2rem; margin-bottom: 2rem; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
        .article h2 { color: #667eea; margin-bottom: 0.5rem; }
        .article h2 a { color: #667eea; text-decoration: none; }
        .article h2 a:hover { text-decoration: underline; }
        .meta { color: #666; font-size: 0.9rem; margin-bottom: 1rem; }
        .btn { display: inline-block; background: #667eea; color: white; padding: 0.5rem 1.5rem; text-decoration: none; border-radius: 5px; margin-top: 1rem; }
        .btn:hover { background: #764ba2; }
        footer { background: #333; color: white; text-align: center; padding: 2rem; margin-top: 3rem; }
        .error { background: #f8d7da; color: #721c24; padding: 1rem; border-radius: 5px; margin-bottom: 1rem; }
    </style>
</head>
<body>
    <header>
        <h1>[NOTE] Mon Blog PHP + Nginx</h1>
        <p>Propulsé par Nginx et PHP-FPM</p>
    </header>

    <div class="container">
        <?php if (isset($error)): ?>
            <div class="error"><?php echo htmlspecialchars($error); ?></div>
        <?php endif; ?>

        <?php if (empty($articles)): ?>
            <div class="article">
                <p>Aucun article pour le moment.</p>
            </div>
        <?php else: ?>
            <?php foreach ($articles as $article): ?>
                <div class="article">
                    <h2>
                        <a href="article.php?id=<?php echo $article['id']; ?>">
                            <?php echo htmlspecialchars($article['titre']); ?>
                        </a>
                    </h2>
                    <div class="meta">
                        Par <strong><?php echo htmlspecialchars($article['auteur']); ?></strong>
                        le <?php echo date('d/m/Y à H:i', strtotime($article['date_creation'])); ?>
                    </div>
                    <p>
                        <?php 
                        echo htmlspecialchars($article['extrait']); 
                        if (strlen($article['extrait']) >= 200) echo '...';
                        ?>
                    </p>
                    <a href="article.php?id=<?php echo $article['id']; ?>" class="btn">Lire la suite -></a>
                </div>
            <?php endforeach; ?>
        <?php endif; ?>

        <p style="text-align: center; margin-top: 2rem;">
            <a href="admin.php" class="btn">+ Ajouter un article</a>
        </p>
    </div>

    <footer>
        <p>&copy; 2024 Mon Blog PHP + Nginx - Tous droits réservés</p>
        <p>Serveur: Nginx + PHP-FPM 8.1</p>
    </footer>
</body>
</html>
```

---

**article.php et admin.php (identiques à Apache) :**

Pour gagner du temps, créer des versions minimales ou copier de l'exercice Apache 3.

**article.php :**

```bash
sudo nano /var/www/blog/article.php
```

**(Copier le contenu de l'exercice Apache 3, fonctionne identique)**

---

**admin.php :**

```bash
sudo nano /var/www/blog/admin.php
```

**(Copier le contenu de l'exercice Apache 3, fonctionne identique)**

---

**Permissions finales :**

```bash
sudo chown -R www-data:www-data /var/www/blog
sudo find /var/www/blog -type d -exec chmod 755 {} \;
sudo find /var/www/blog -type f -exec chmod 644 {} \;
```

---

### ÉTAPE 10 : Tester le blog

**Accéder à :**

```
http://blog.local
```

**[BRAVO] Le blog devrait fonctionner ! [BRAVO]**

**Tester :**

[OK] Page d'accueil : Liste des 3 articles
[OK] Clic sur un article : Détail s'affiche
[OK] Ajouter un article : Formulaire fonctionne
[OK] Nouvel article apparaît dans la liste

---

### ÉTAPE 11 : Optimiser PHP-FPM pour les performances

**Éditer le pool www :**

```bash
sudo nano /etc/php/8.1/fpm/pool.d/www.conf
```

**Modifier (pour un serveur de production) :**

```ini
; Augmenter le nombre de processus
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10

; Augmenter le nombre de requêtes avant recyclage
pm.max_requests = 1000

; Activer le monitoring
pm.status_path = /fpm-status

; Log des scripts lents
slowlog = /var/log/php8.1-fpm-slow.log
request_slowlog_timeout = 5s
```

**Sauvegarde.**

---

**Redémarrer PHP-FPM :**

```bash
sudo systemctl restart php8.1-fpm
```

---

**Ajouter le monitoring dans Nginx :**

```bash
sudo nano /etc/nginx/sites-available/blog
```

**Ajouter avant le dernier `}` :**

```nginx
    location ~ ^/(fpm-status|fpm-ping)$ {
        access_log off;
        allow 127.0.0.1;
        deny all;
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
```

**Recharger Nginx :**

```bash
sudo nginx -t
sudo systemctl reload nginx
```

---

**Tester le monitoring :**

```bash
curl http://blog.local/fpm-status
```

**Résultat :**

```
pool:                 www
process manager:      dynamic
start time:           16/Dec/2024:17:00:00 +0000
start since:          120
accepted conn:        50
listen queue:         0
max listen queue:     0
listen queue len:     0
idle processes:       4
active processes:     1
total processes:      5
max active processes: 3
max children reached: 0
slow requests:        0
```

**Informations utiles :**

**idle processes** : Processus en attente
**active processes** : Processus traitant une requête
**total processes** : Total
**max children reached** : Nombre de fois où on a atteint la limite ([ATTENTION] si >0)
**slow requests** : Requêtes lentes (> slowlog_timeout)

---

### [OK] TESTS DE VALIDATION

**1. PHP fonctionne**

```bash
curl http://blog.local/
```

- [ ] HTML retourné (pas de téléchargement du fichier .php)
- [ ] Articles affichés

---

**2. Connexion MySQL**

```bash
# Dans la page d'accueil, tu devrais voir les articles de la BDD
```

- [ ] 3 articles s'affichent

---

**3. Performance Nginx vs Apache**

**Benchmarker Nginx + PHP-FPM :**

```bash
ab -n 1000 -c 100 http://blog.local/
```

**Benchmarker Apache + mod_php (si disponible) :**

```bash
ab -n 1000 -c 100 http://blog-apache.local/
```

**Résultat attendu :**

```
Nginx + PHP-FPM : 1500-2500 req/s
Apache + mod_php : 500-1000 req/s

-> Nginx 2-3x plus rapide !
```

---

**4. Logs PHP-FPM**

```bash
sudo tail -f /var/log/php8.1-fpm.log
```

**Faire des requêtes et observer les logs.**

---

**5. Sécurité : Faille Path Info bloquée**

**Tester l'attaque :**

```bash
curl http://blog.local/index.php/hack.php
```

**Doit retourner : 404 Not Found**

**Avec `try_files $uri =404;`, l'attaque est bloquée !**

---

### [ROUGE] ERREURS COURANTES ET SOLUTIONS

#### Erreur 1 : "502 Bad Gateway"

**Symptôme :**

```
502 Bad Gateway
nginx/1.18.0
```

**Causes possibles :**

**1. PHP-FPM ne tourne pas**

```bash
sudo systemctl status php8.1-fpm
# Si inactif :
sudo systemctl start php8.1-fpm
```

---

**2. Socket incorrect dans Nginx**

**Vérifier le socket :**

```bash
ls -l /run/php/php8.1-fpm.sock
```

**Vérifier la config Nginx :**

```bash
grep fastcgi_pass /etc/nginx/sites-enabled/blog
```

**Doit contenir :**

```nginx
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
```

---

**3. Permissions du socket**

```bash
ls -l /run/php/php8.1-fpm.sock
```

**Doit être :**

```
srw-rw---- 1 www-data www-data ... /run/php/php8.1-fpm.sock
```

**Si mauvais user/group, éditer `/etc/php/8.1/fpm/pool.d/www.conf` :**

```ini
listen.owner = www-data
listen.group = www-data
```

**Redémarrer PHP-FPM :**

```bash
sudo systemctl restart php8.1-fpm
```

---

#### Erreur 2 : Fichier .php téléchargé au lieu d'être exécuté

**Symptôme :**

Le navigateur télécharge `index.php` au lieu de l'exécuter.

**Cause : Location PHP mal configurée**

**Solution :**

**Vérifier que le bloc `location ~ \.php$` existe dans le server block.**

**Vérifier que `fastcgi_pass` pointe vers le bon socket.**

---

#### Erreur 3 : "File not found" dans les logs PHP-FPM

**Symptôme :**

```
[error] 12345#12345: *1 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream
```

**Cause : `SCRIPT_FILENAME` mal configuré**

**Solution :**

**Vérifier dans le server block :**

```nginx
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
```

**Doit être présent et correct !**

---

#### Erreur 4 : "Access denied" dans les logs PHP-FPM

**Symptôme :**

```
[error] 12345#12345: *1 FastCGI sent in stderr: "Access to the script '/var/www/blog/index.php' has been denied" while reading response header from upstream
```

**Cause : Mauvaises permissions**

**Solution :**

```bash
# Vérifier propriétaire
ls -la /var/www/blog/

# Corriger
sudo chown -R www-data:www-data /var/www/blog
sudo chmod 755 /var/www/blog
sudo chmod 644 /var/www/blog/*.php
```

---

#### Erreur 5 : Scripts lents (timeout)

**Symptôme :**

```
504 Gateway Timeout
```

**Cause : Script PHP prend > 60 secondes**

**Solution :**

**Augmenter les timeouts dans Nginx :**

```nginx
location ~ \.php$ {
    # ...
    fastcgi_read_timeout 300s;  # 5 minutes
}
```

**Augmenter dans PHP-FPM :**

```ini
# /etc/php/8.1/fpm/pool.d/www.conf
request_terminate_timeout = 300
```

**Redémarrer :**

```bash
sudo systemctl reload nginx
sudo systemctl restart php8.1-fpm
```

---

### [IMPORTANT] POINTS CLÉS À RETENIR

**1. Nginx + PHP-FPM**
- Séparation complète Nginx/PHP
- Communication via socket UNIX (rapide)
- Isolation des processus (sécurité)

**2. Configuration PHP sur Nginx**
- `location ~ \.php$` pour traiter les .php
- `fastcgi_pass` pour communiquer avec PHP-FPM
- `SCRIPT_FILENAME` obligatoire
- `try_files $uri =404;` pour sécurité

**3. Performance**
- 2-3x plus rapide qu'Apache + mod_php
- PM mode `dynamic` adaptatif
- Session cache, buffers optimisés

**4. Monitoring**
- `/fpm-status` pour statistiques
- Logs PHP-FPM séparés
- Slowlog pour identifier bottlenecks

**5. Sécurité**
- Bloquer fichiers cachés et sensibles
- `try_files` avant `fastcgi_pass`
- Permissions strictes (www-data, 644/755)

---

### [RAPIDE] POUR ALLER PLUS LOIN

**1. Créer plusieurs pools PHP-FPM**

**Pool "admin" pour zone admin :**

```bash
sudo nano /etc/php/8.1/fpm/pool.d/admin.conf
```

```ini
[admin]
listen = /run/php/php8.1-fpm-admin.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
# Configuration spécifique pour admin
```

**Dans Nginx :**

```nginx
location ~ ^/admin/.*\.php$ {
    fastcgi_pass unix:/run/php/php8.1-fpm-admin.sock;
    # ...
}
```

---

**2. Cache OPcache (accélération PHP)**

**Activer OPcache :**

```bash
sudo nano /etc/php/8.1/fpm/conf.d/10-opcache.ini
```

```ini
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
opcache.fast_shutdown=1
```

**Redémarrer PHP-FPM :**

```bash
sudo systemctl restart php8.1-fpm
```

**Gain : 2-3x plus rapide !**

---

**3. Cache de contenu avec FastCGI Cache**

**Dans Nginx (contexte http) :**

```nginx
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=PHPCACHE:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
```

**Dans le server block :**

```nginx
location ~ \.php$ {
    # ...
    fastcgi_cache PHPCACHE;
    fastcgi_cache_valid 200 60m;
    add_header X-Cache-Status $upstream_cache_status;
}
```

**Pages statiques servies depuis le cache (ultra rapide) !**

---

**4. Séparer Nginx et PHP-FPM sur serveurs différents**

**Sur serveur 1 (Nginx) :**

```nginx
fastcgi_pass 192.168.1.200:9000;  # IP du serveur PHP-FPM
```

**Sur serveur 2 (PHP-FPM) :**

```ini
listen = 9000
listen.allowed_clients = 192.168.1.100  # IP du serveur Nginx
```

**Architecture distribuée pour scalabilité !**

---

## [COURS] CONCLUSION DE L'EXERCICE 3

**[OK] Félicitations ! Tu maîtrises maintenant PHP avec Nginx !**

**Ce que tu as appris :**
- Installer et configurer PHP-FPM
- Configurer Nginx pour exécuter PHP
- Comprendre la communication Nginx <-> PHP-FPM
- Optimiser les pools PHP-FPM
- Créer un blog dynamique avec base de données
- Différences PHP entre Apache et Nginx
- Monitoring et performance PHP-FPM

**Compétences acquises :**
- [OK] Configuration PHP-FPM (niveau intermédiaire)
- [OK] Nginx + PHP (niveau intermédiaire)
- [OK] Optimisation performance
- [OK] Debugging PHP-FPM
- [OK] Sécurisation scripts PHP

**Temps moyen de réalisation :** 3-4 heures

**Prochaine étape :** Exercice 4 - Reverse Proxy + Load Balancing ! [RAPIDE]

---

---

*(Voulez-vous que je continue avec les exercices 4 et 5 ? Ils seront sur le Reverse Proxy/Load Balancing et la Sécurisation complète, avec le même niveau de détail)*

# [ROUGE] EXERCICE 4 : REVERSE PROXY + LOAD BALANCING AVEC NGINX

## [LISTE] ÉNONCÉ

### Contexte professionnel

Tu es DevOps dans une startup en forte croissance. L'application Node.js de l'entreprise doit supporter 10,000+ utilisateurs simultanés. Ta mission : mettre en place une architecture hautement disponible avec Nginx comme reverse proxy et load balancer.

### Objectifs

- Déployer plusieurs instances Node.js (backend)
- Configurer Nginx comme reverse proxy
- Implémenter le load balancing avec plusieurs algorithmes
- Gérer la haute disponibilité (failover automatique)
- Mettre en place des health checks
- Optimiser les performances du reverse proxy

### Architecture cible

```
                    Internet
                       v
              Nginx (Reverse Proxy + Load Balancer)
              Port 80/443
                       v
        ┌──────────────┼──────────────┐
        v              v              v
   Node.js 1      Node.js 2      Node.js 3
   Port 3001      Port 3002      Port 3003
```

### Pourquoi Nginx est MEILLEUR qu'Apache pour le Reverse Proxy

| Critère | Apache | Nginx |
|---------|--------|-------|
| **Architecture** | Thread-based | * Event-driven |
| **Concurrence** | 1,000-2,000 | * 10,000+ |
| **Mémoire/connexion** | ~5-10 MB | * ~2.5 KB |
| **Performance proxy** | Bon | * Excellent |
| **Config proxy** | Verbeux | * Simple |
| **Load balancing** | mod_proxy_balancer | * Native, plus rapide |

### Contraintes

- Nginx 1.18+
- Node.js 18+
- Algorithmes : Round-robin, Least connections, IP hash
- Health checks actifs
- Temps estimé : 2-3 heures

---

## [OBJECTIF] OBJECTIFS PÉDAGOGIQUES

À la fin de cet exercice, tu sauras :

- [OK] Configurer Nginx comme reverse proxy
- [OK] Implémenter plusieurs algorithmes de load balancing
- [OK] Gérer le failover automatique
- [OK] Configurer des health checks
- [OK] Utiliser les sessions sticky
- [OK] Optimiser les performances du proxy
- [OK] Monitorer les backends
- [OK] Comprendre les headers de proxying

---

## [DOCS] PRÉREQUIS

- Exercices 1, 2 et 3 terminés
- Node.js installé (voir exercice Apache 4)
- Compréhension des architectures distribuées

---

## [OK] SOLUTION COMPLÈTE

### ÉTAPE 1 : Comprendre le Reverse Proxy avec Nginx

#### Qu'est-ce qu'un Reverse Proxy ?

**Sans Reverse Proxy (accès direct) :**

```
Client -> http://app.com:3001 -> Node.js (port 3001)
```

**Problèmes :**
- Port non-standard (3001) exposé
- Pas de SSL/TLS centralisé
- Pas de load balancing
- Application directement accessible (sécurité)

---

**Avec Reverse Proxy :**

```
Client -> http://app.com (port 80) -> Nginx -> http://localhost:3001 -> Node.js
```

**Avantages :**
- [OK] Port standard (80/443)
- [OK] SSL/TLS terminé au niveau Nginx
- [OK] Plusieurs backends (load balancing)
- [OK] Cache possible
- [OK] Application protégée (pas d'accès direct)
- [OK] Compression, headers de sécurité centralisés

---

#### Architecture Nginx Reverse Proxy

**Nginx excelle en reverse proxy grâce à son architecture événementielle :**

```
[Client] ──HTTP──> [Nginx Worker 1] ──FastCGI/HTTP──> [Backend 1]
                         v                              [Backend 2]
                    Event Loop                          [Backend 3]
                         v
                   (1 worker = 10,000+ connexions)
```

**Flux détaillé :**

1. **Client** envoie requête HTTP(S) à Nginx
2. **Nginx** (reverse proxy) :
   - Termine la connexion SSL (si HTTPS)
   - Parse la requête HTTP
   - Choisit un backend (selon algorithme load balancing)
   - Établit connexion avec le backend
   - Transmet la requête
3. **Backend** traite et répond
4. **Nginx** :
   - Reçoit la réponse du backend
   - Peut la mettre en cache (optionnel)
   - Ajoute des headers
   - Renvoie au client

**Tout ça de manière asynchrone (non-bloquant) !**

---

#### Nginx vs Apache pour Reverse Proxy

**Apache (mod_proxy) :**

```
1 connexion client = 1 thread/process Apache
Thread bloqué pendant toute la durée de la requête backend
-> Si backend lent (5 secondes), thread bloqué 5 secondes
-> Limite : ~1000 connexions simultanées
```

**Nginx (reverse proxy) :**

```
1 worker = 10,000+ connexions
Worker jamais bloqué (asynchrone)
-> Si backend lent, worker traite d'autres connexions
-> Limite : RAM (pas CPU)
```

**Résultat : Nginx 10x plus efficace pour reverse proxy !**

---

### ÉTAPE 2 : Installer Node.js et PM2

```bash
# Installer Node.js (si pas déjà fait)
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs

# Installer PM2 (Process Manager)
sudo npm install -g pm2

# Vérifier
node --version  # v18.x.x
pm2 --version   # 5.x.x
```

---

### ÉTAPE 3 : Créer l'application Node.js

**Créer le dossier du projet :**

```bash
sudo mkdir -p /var/www/nodejs-api
cd /var/www/nodejs-api
```

---

**Initialiser le projet npm :**

```bash
sudo npm init -y
```

---

**Installer Express :**

```bash
sudo npm install express
```

---

**Créer le serveur (`server.js`) :**

```bash
sudo nano /var/www/nodejs-api/server.js
```

**Contenu :**

```javascript
// ═══════════════════════════════════════════════════════════════
// API REST NODE.JS - BACKEND POUR REVERSE PROXY
// ═══════════════════════════════════════════════════════════════

const express = require('express');
const app = express();

// ───────────────────────────────────────────────────────────────
// CONFIGURATION
// ───────────────────────────────────────────────────────────────

const PORT = process.env.PORT || 3001;
const INSTANCE_ID = process.env.INSTANCE_ID || 'Backend-1';

// Couleur pour logs (esthétique)
const COLORS = {
    reset: '\x1b[0m',
    red: '\x1b[31m',
    green: '\x1b[32m',
    yellow: '\x1b[33m',
    blue: '\x1b[34m'
};

// ───────────────────────────────────────────────────────────────
// MIDDLEWARE : LOGGER
// ───────────────────────────────────────────────────────────────

app.use((req, res, next) => {
    const color = PORT === 3001 ? COLORS.green : 
                  PORT === 3002 ? COLORS.blue : COLORS.yellow;
    console.log(`${color}[${INSTANCE_ID}]${COLORS.reset} ${req.method} ${req.path}`);
    next();
});

// ───────────────────────────────────────────────────────────────
// ROUTES
// ───────────────────────────────────────────────────────────────

// Route racine
app.get('/', (req, res) => {
    res.json({
        message: 'API Node.js avec Nginx Reverse Proxy',
        instance: INSTANCE_ID,
        port: PORT,
        hostname: require('os').hostname(),
        timestamp: new Date().toISOString(),
        // Headers reçus du reverse proxy
        headers: {
            'x-forwarded-for': req.headers['x-forwarded-for'] || 'non défini',
            'x-real-ip': req.headers['x-real-ip'] || 'non défini',
            'x-forwarded-proto': req.headers['x-forwarded-proto'] || 'non défini',
            'host': req.headers['host'] || 'non défini'
        }
    });
});

// Health check (pour load balancer)
app.get('/health', (req, res) => {
    res.status(200).json({
        status: 'OK',
        instance: INSTANCE_ID,
        uptime: process.uptime(),
        memory: process.memoryUsage()
    });
});

// Route API exemple
app.get('/api/users', (req, res) => {
    res.json({
        users: [
            { id: 1, name: 'Alice', email: 'alice@example.com' },
            { id: 2, name: 'Bob', email: 'bob@example.com' },
            { id: 3, name: 'Charlie', email: 'charlie@example.com' }
        ],
        servedBy: INSTANCE_ID,
        timestamp: new Date().toISOString()
    });
});

// Route avec délai (pour tester load balancing)
app.get('/api/slow', (req, res) => {
    const delay = Math.random() * 2000; // 0-2 secondes
    setTimeout(() => {
        res.json({
            message: 'Réponse après délai',
            instance: INSTANCE_ID,
            delay: Math.round(delay) + 'ms'
        });
    }, delay);
});

// Simulation d'erreur (pour tester failover)
let errorMode = false;
app.get('/api/toggle-error', (req, res) => {
    errorMode = !errorMode;
    res.json({
        instance: INSTANCE_ID,
        errorMode: errorMode,
        message: errorMode ? 'Cette instance va maintenant renvoyer des erreurs 500' : 'Mode normal restauré'
    });
});

app.get('/api/data', (req, res) => {
    if (errorMode) {
        return res.status(500).json({
            error: 'Erreur serveur simulée',
            instance: INSTANCE_ID
        });
    }
    res.json({
        data: 'Données de test',
        instance: INSTANCE_ID
    });
});

// ───────────────────────────────────────────────────────────────
// GESTION DES ERREURS
// ───────────────────────────────────────────────────────────────

app.use((req, res) => {
    res.status(404).json({
        error: 'Route non trouvée',
        instance: INSTANCE_ID
    });
});

app.use((err, req, res, next) => {
    console.error(`${COLORS.red}[${INSTANCE_ID}] Erreur:${COLORS.reset}`, err);
    res.status(500).json({
        error: 'Erreur interne du serveur',
        instance: INSTANCE_ID
    });
});

// ───────────────────────────────────────────────────────────────
// DÉMARRAGE DU SERVEUR
// ───────────────────────────────────────────────────────────────

app.listen(PORT, () => {
    console.log(`${COLORS.green}═══════════════════════════════════════════════${COLORS.reset}`);
    console.log(`${COLORS.green}[${INSTANCE_ID}] Serveur démarré sur le port ${PORT}${COLORS.reset}`);
    console.log(`${COLORS.green}Health check: http://localhost:${PORT}/health${COLORS.reset}`);
    console.log(`${COLORS.green}═══════════════════════════════════════════════${COLORS.reset}`);
});

// Gestion propre de l'arrêt
process.on('SIGTERM', () => {
    console.log(`${COLORS.yellow}[${INSTANCE_ID}] SIGTERM reçu, arrêt propre...${COLORS.reset}`);
    process.exit(0);
});
```

**Sauvegarde.**

---

### ÉTAPE 4 : Créer la configuration PM2 multi-instances

**Créer le fichier de configuration PM2 :**

```bash
sudo nano /var/www/nodejs-api/ecosystem.config.js
```

**Contenu :**

```javascript
// ═══════════════════════════════════════════════════════════════
// CONFIGURATION PM2 - MULTI-INSTANCES
// ═══════════════════════════════════════════════════════════════

module.exports = {
    apps: [
        {
            name: 'api-backend-1',
            script: './server.js',
            instances: 1,
            exec_mode: 'fork',
            env: {
                PORT: 3001,
                INSTANCE_ID: 'Backend-1',
                NODE_ENV: 'production'
            },
            error_file: '/var/log/pm2/api-backend-1-error.log',
            out_file: '/var/log/pm2/api-backend-1-out.log',
            log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
        },
        {
            name: 'api-backend-2',
            script: './server.js',
            instances: 1,
            exec_mode: 'fork',
            env: {
                PORT: 3002,
                INSTANCE_ID: 'Backend-2',
                NODE_ENV: 'production'
            },
            error_file: '/var/log/pm2/api-backend-2-error.log',
            out_file: '/var/log/pm2/api-backend-2-out.log',
            log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
        },
        {
            name: 'api-backend-3',
            script: './server.js',
            instances: 1,
            exec_mode: 'fork',
            env: {
                PORT: 3003,
                INSTANCE_ID: 'Backend-3',
                NODE_ENV: 'production'
            },
            error_file: '/var/log/pm2/api-backend-3-error.log',
            out_file: '/var/log/pm2/api-backend-3-out.log',
            log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
        }
    ]
};
```

**Sauvegarde.**

---

**Créer le dossier de logs PM2 :**

```bash
sudo mkdir -p /var/log/pm2
sudo chown -R $USER:$USER /var/log/pm2
```

---

**Démarrer les 3 instances :**

```bash
cd /var/www/nodejs-api
pm2 start ecosystem.config.js
```

**Résultat :**

```
┌─────────────────┬────┬─────────┬──────┬────────┬─────────┬────────┬─────┐
│ App name        │ id │ mode    │ pid  │ status │ restart │ uptime │ cpu │
├─────────────────┼────┼─────────┼──────┼────────┼─────────┼────────┼─────┤
│ api-backend-1   │ 0  │ fork    │ 1234 │ online │ 0       │ 0s     │ 0%  │
│ api-backend-2   │ 1  │ fork    │ 1235 │ online │ 0       │ 0s     │ 0%  │
│ api-backend-3   │ 2  │ fork    │ 1236 │ online │ 0       │ 0s     │ 0%  │
└─────────────────┴────┴─────────┴──────┴────────┴─────────┴────────┴─────┘
```

**[OK] 3 instances démarrées !**

---

**Vérifier que les instances répondent :**

```bash
curl http://localhost:3001
curl http://localhost:3002
curl http://localhost:3003
```

**Chaque instance devrait renvoyer son ID.**

---

**Sauvegarder la configuration PM2 (redémarrage auto au boot) :**

```bash
pm2 startup systemd
# Copier-coller la commande affichée (ex: sudo env PATH=... pm2 startup...)

pm2 save
```

---

### ÉTAPE 5 : Configurer Nginx comme Reverse Proxy avec Load Balancing

**Créer le server block :**

```bash
sudo nano /etc/nginx/sites-available/nodejs-api
```

**Contenu :**

```nginx
# ═══════════════════════════════════════════════════════════════
# REVERSE PROXY + LOAD BALANCER - API NODE.JS
# ═══════════════════════════════════════════════════════════════

# ───────────────────────────────────────────────────────────────
# UPSTREAM : DÉFINITION DU CLUSTER DE BACKENDS
# ───────────────────────────────────────────────────────────────

upstream nodejs_backend {
    # Nom du cluster (utilisé dans proxy_pass)
    # Peut être n'importe quel nom (nodejs_backend, api_cluster, etc.)
    
    # ═══════════════════════════════════════════════════════════
    # ALGORITHME DE LOAD BALANCING
    # ═══════════════════════════════════════════════════════════
    
    # Round-robin (par défaut, si aucune directive)
    # Répartit les requêtes à tour de rôle
    # Backend 1 -> Backend 2 -> Backend 3 -> Backend 1 -> ...
    #
    # Équivalent Apache :
    # ProxySet lbmethod=byrequests
    
    # Autres algorithmes disponibles :
    
    # least_conn;
    # Envoie vers le backend avec le MOINS de connexions actives
    # Utile si les backends ont des capacités différentes
    # ou si les requêtes ont des durées variables
    #
    # Exemple :
    # Backend 1 : 5 connexions
    # Backend 2 : 3 connexions  <- Prochaine requête ici
    # Backend 3 : 7 connexions
    
    # ip_hash;
    # Hash de l'IP client -> Toujours le même backend
    # Garantit la persistance de session (sticky session)
    # Même client -> Même backend
    #
    # Avantage : Sessions en mémoire fonctionnent
    # Inconvénient : Pas de vrai load balancing si peu de clients
    #
    # Hash basé sur : IP du client
    # 192.168.1.50 -> hash -> Backend 2 (toujours)
    # 192.168.1.51 -> hash -> Backend 1 (toujours)
    
    # hash $request_uri consistent;
    # Hash basé sur l'URI de la requête
    # /api/users -> Toujours le même backend
    # /api/products -> Peut-être un autre backend
    #
    # Utile pour cache backend (même URL = même cache)
    # consistent = Consistent hashing (minimise redistribution si backend down)
    
    # Pour cet exercice : Round-robin (défaut)
    
    # ═══════════════════════════════════════════════════════════
    # DÉFINITION DES BACKENDS (SERVERS)
    # ═══════════════════════════════════════════════════════════
    
    server 127.0.0.1:3001 weight=1 max_fails=3 fail_timeout=30s;
    # Backend 1
    #
    # Décomposition :
    # 127.0.0.1:3001 = Adresse:Port du backend
    #
    # weight=1 (optionnel)
    # Poids pour le load balancing
    # Plus le poids est élevé, plus il reçoit de requêtes
    #
    # Exemple avec poids différents :
    # server backend1:3001 weight=3;  -> 60% des requêtes (3/5)
    # server backend2:3002 weight=2;  -> 40% des requêtes (2/5)
    #
    # Utile si les backends ont des capacités différentes
    # (Serveur puissant weight=5, serveur faible weight=1)
    #
    # max_fails=3 (optionnel, défaut: 1)
    # Nombre d'échecs avant de marquer le backend comme "down"
    # Après 3 échecs consécutifs, le backend est désactivé
    #
    # fail_timeout=30s (optionnel, défaut: 10s)
    # Durée pendant laquelle le backend reste marqué "down"
    # Après 30 secondes, Nginx réessaie le backend
    #
    # Cycle de vie :
    # 1. Backend fonctionne normalement
    # 2. Échec 1, 2, 3 -> Backend marqué "down"
    # 3. Attente de 30 secondes
    # 4. Nginx réessaie le backend (health check passif)
    # 5. Si succès -> Backend "up", sinon reste "down" 30s de plus
    
    server 127.0.0.1:3002 weight=1 max_fails=3 fail_timeout=30s;
    # Backend 2
    
    server 127.0.0.1:3003 weight=1 max_fails=3 fail_timeout=30s;
    # Backend 3
    
    # ═══════════════════════════════════════════════════════════
    # BACKUP BACKEND (OPTIONNEL)
    # ═══════════════════════════════════════════════════════════
    
    # server 127.0.0.1:3004 backup;
    # Backend de secours (backup)
    # Utilisé SEULEMENT si tous les backends principaux sont down
    #
    # Scénario :
    # Backends 1, 2, 3 : Down
    # -> Nginx envoie au backend 4 (backup)
    #
    # Utile pour page d'erreur personnalisée ou mode dégradé
    
    # ═══════════════════════════════════════════════════════════
    # KEEPALIVE (OPTIMISATION PERFORMANCE)
    # ═══════════════════════════════════════════════════════════
    
    keepalive 32;
    # Nombre de connexions keepalive vers les backends
    #
    # Sans keepalive :
    # Chaque requête = Nouvelle connexion TCP (handshake 3-way)
    # -> Overhead : ~1-2ms par requête
    #
    # Avec keepalive :
    # Réutilise les connexions existantes
    # -> Pas de handshake TCP, plus rapide
    #
    # keepalive 32 = Pool de 32 connexions maintenues ouvertes
    #
    # Recommandation :
    # Trafic faible : 8-16
    # Trafic moyen : 32-64
    # Trafic élevé : 128-256
    #
    # [ATTENTION] Nécessite aussi proxy_http_version 1.1 (voir plus bas)
}

# ───────────────────────────────────────────────────────────────
# SERVER BLOCK : REVERSE PROXY
# ───────────────────────────────────────────────────────────────

server {
    listen 80;
    listen [::]:80;
    server_name api.local;
    
    # ═══════════════════════════════════════════════════════════
    # LOCATION PRINCIPALE : PROXY VERS BACKENDS
    # ═══════════════════════════════════════════════════════════
    
    location / {
        # Proxier vers le cluster upstream
        proxy_pass http://nodejs_backend;
        # http://nodejs_backend = Référence au bloc upstream défini plus haut
        #
        # Nginx va :
        # 1. Choisir un backend (selon algorithme)
        # 2. Envoyer la requête à ce backend
        # 3. Recevoir la réponse
        # 4. La renvoyer au client
        #
        # Si le backend ne répond pas (down) :
        # -> Nginx essaie automatiquement le suivant (failover)
        
        # ═══════════════════════════════════════════════════════
        # HEADERS ENVOYÉS AU BACKEND
        # ═══════════════════════════════════════════════════════
        
        proxy_set_header Host $host;
        # Envoie le header "Host" original au backend
        # Sans ça, le backend reçoit "Host: nodejs_backend"
        # Avec ça, le backend reçoit "Host: api.local"
        #
        # Important si le backend se base sur le hostname
        # (Virtual hosts, redirection, etc.)
        
        proxy_set_header X-Real-IP $remote_addr;
        # Envoie l'IP RÉELLE du client
        # $remote_addr = IP du client (ex: 192.168.1.50)
        #
        # Sans ça, le backend voit l'IP de Nginx (127.0.0.1)
        # Avec ça, le backend voit l'IP du vrai client
        #
        # Utile pour :
        # - Logs (savoir qui a fait la requête)
        # - Géolocalisation
        # - Rate limiting par IP
        
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # Liste des IPs traversées (chaîne de proxies)
        #
        # $proxy_add_x_forwarded_for = IP client + X-Forwarded-For existant
        #
        # Exemple :
        # Client (1.2.3.4) -> Proxy 1 (5.6.7.8) -> Nginx (9.10.11.12) -> Backend
        #
        # Backend reçoit :
        # X-Forwarded-For: 1.2.3.4, 5.6.7.8
        #
        # Utile pour tracer le chemin complet de la requête
        
        proxy_set_header X-Forwarded-Proto $scheme;
        # Protocole original (http ou https)
        # $scheme = "http" ou "https"
        #
        # Si le client accède en HTTPS mais Nginx->Backend en HTTP :
        # Backend sait que la requête originale était en HTTPS
        #
        # Utile pour :
        # - Générer des redirections HTTPS
        # - Savoir si connexion sécurisée
        
        proxy_set_header X-Forwarded-Host $host;
        # Hostname original
        # Redondant avec Host, mais certains frameworks le lisent
        
        proxy_set_header X-Forwarded-Port $server_port;
        # Port original (80 ou 443)
        
        # ═══════════════════════════════════════════════════════
        # HTTP VERSION ET KEEPALIVE
        # ═══════════════════════════════════════════════════════
        
        proxy_http_version 1.1;
        # Utilise HTTP/1.1 pour communiquer avec le backend
        # Par défaut : HTTP/1.0 (pas de keepalive)
        #
        # HTTP/1.1 nécessaire pour :
        # - Keepalive (réutilisation connexions)
        # - WebSocket
        # - Chunked encoding
        #
        # [ATTENTION] Obligatoire si keepalive dans upstream !
        
        proxy_set_header Connection "";
        # Supprime le header "Connection: close"
        # Permet le keepalive avec les backends
        #
        # Sans ça : Connection: close (pas de keepalive)
        # Avec ça : Pas de header Connection (keepalive activé)
        
        # ═══════════════════════════════════════════════════════
        # TIMEOUTS
        # ═══════════════════════════════════════════════════════
        
        proxy_connect_timeout 60s;
        # Timeout pour ÉTABLIR la connexion avec le backend
        # Si le backend ne répond pas en 60s -> Essaie le suivant
        
        proxy_send_timeout 60s;
        # Timeout pour ENVOYER la requête au backend
        # Rare de dépasser (sauf upload très volumineux)
        
        proxy_read_timeout 60s;
        # Timeout pour LIRE la réponse du backend
        # [ATTENTION] LE PLUS IMPORTANT !
        #
        # Si le backend prend > 60s pour répondre :
        # -> Nginx coupe et essaie un autre backend (si disponible)
        # -> Ou retourne 504 Gateway Timeout
        #
        # Pour API lentes (rapports, exports) :
        # Augmenter à 300s (5 minutes) ou plus
        
        # ═══════════════════════════════════════════════════════
        # BUFFERS (PERFORMANCE)
        # ═══════════════════════════════════════════════════════
        
        proxy_buffering on;
        # Active le buffering des réponses backend
        #
        # Avec buffering ON :
        # Nginx lit la réponse du backend dans des buffers
        # Puis envoie au client de manière optimisée
        # -> Backend libéré rapidement (peut traiter d'autres requêtes)
        #
        # Avec buffering OFF :
        # Nginx transmet la réponse au client en temps réel
        # -> Backend bloqué jusqu'à ce que le client reçoive tout
        # -> Utile pour SSE (Server-Sent Events), streaming
        #
        # Défaut : on (recommandé sauf cas spécifiques)
        
        proxy_buffer_size 4k;
        # Taille du buffer pour les HEADERS de réponse
        # 4k = Suffisant pour la plupart des headers
        # Augmenter si headers volumineux (cookies, etc.)
        
        proxy_buffers 8 4k;
        # Nombre et taille des buffers pour le CORPS de réponse
        # 8 buffers × 4k = 32k total
        #
        # Taille totale doit être adaptée à la taille des réponses :
        # API JSON petites : 32k-64k (défaut)
        # Réponses moyennes : 128k-256k
        # Grosses réponses : 512k-1M
        #
        # Si réponse > taille totale buffers :
        # -> Nginx écrit le surplus sur disque (lent)
        
        proxy_busy_buffers_size 8k;
        # Taille des buffers "occupés" (envoyés au client)
        # Généralement = 2 × proxy_buffer_size
        
        # ═══════════════════════════════════════════════════════
        # GESTION DES ERREURS (FAILOVER)
        # ═══════════════════════════════════════════════════════
        
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        # Définit quand essayer le backend suivant
        #
        # Conditions :
        # error : Erreur lors de la connexion/communication
        # timeout : Timeout (connect, send ou read)
        # invalid_header : Header de réponse invalide
        # http_500 : Internal Server Error
        # http_502 : Bad Gateway
        # http_503 : Service Unavailable
        # http_504 : Gateway Timeout
        #
        # Si un de ces cas se produit :
        # -> Nginx marque le backend comme "failed"
        # -> Essaie automatiquement le backend suivant
        #
        # Exemple :
        # Requête -> Backend 1 (500 Internal Error)
        # -> Nginx essaie Backend 2 (200 OK) [OK]
        
        proxy_next_upstream_tries 3;
        # Nombre MAX de tentatives (backends différents)
        # 3 = Essaie jusqu'à 3 backends différents
        #
        # Si 3 backends échouent tous :
        # -> Nginx retourne l'erreur au client
        
        proxy_next_upstream_timeout 10s;
        # Timeout TOTAL pour toutes les tentatives
        # 10s = Au bout de 10s, arrête d'essayer d'autres backends
        #
        # Évite de faire attendre le client trop longtemps
    }
    
    # ═══════════════════════════════════════════════════════════
    # LOCATION : MONITORING NGINX
    # ═══════════════════════════════════════════════════════════
    
    location /nginx-status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }
    # stub_status = Module de monitoring Nginx
    # Affiche :
    # - Connexions actives
    # - Connexions acceptées/traitées
    # - Requêtes en cours
    
    # ═══════════════════════════════════════════════════════════
    # LOGS
    # ═══════════════════════════════════════════════════════════
    
    access_log /var/log/nginx/api-access.log;
    error_log /var/log/nginx/api-error.log;
}
```

**Sauvegarde.**

---

**Points clés de la configuration :**

**1. `upstream nodejs_backend {}`** : Définit le cluster de backends
**2. `server 127.0.0.1:3001;`** : Ajoute un backend au cluster
**3. `proxy_pass http://nodejs_backend;`** : Proxie vers le cluster
**4. `proxy_set_header`** : Envoie des headers au backend
**5. `proxy_next_upstream`** : Gère le failover automatique
**6. `keepalive 32;`** : Optimise les connexions backend

---

### ÉTAPE 6 : Activer le site

```bash
# Créer lien symbolique
sudo ln -s /etc/nginx/sites-available/nodejs-api /etc/nginx/sites-enabled/

# Tester
sudo nginx -t

# Recharger
sudo systemctl reload nginx
```

---

**Ajouter dans `/etc/hosts` (sur ton PC) :**

```
192.168.1.100    api.local
```

---

### ÉTAPE 7 : Tester le Reverse Proxy et Load Balancing

**Tester une requête simple :**

```bash
curl http://api.local/
```

**Résultat (exemple) :**

```json
{
  "message": "API Node.js avec Nginx Reverse Proxy",
  "instance": "Backend-1",
  "port": 3001,
  "hostname": "myserver",
  "timestamp": "2024-12-16T18:00:00.000Z",
  "headers": {
    "x-forwarded-for": "192.168.1.50",
    "x-real-ip": "192.168.1.50",
    "x-forwarded-proto": "http",
    "host": "api.local"
  }
}
```

**[OK] Le reverse proxy fonctionne !**

**Vérifications :**
- [OK] `instance` indique quel backend a répondu
- [OK] `x-real-ip` contient l'IP du client (pas 127.0.0.1)
- [OK] `host` contient "api.local" (pas "nodejs_backend")

---

**Tester le load balancing (10 requêtes) :**

```bash
for i in {1..10}; do curl -s http://api.local/ | grep -o '"instance":"[^"]*"'; done
```

**Résultat (round-robin) :**

```
"instance":"Backend-1"
"instance":"Backend-2"
"instance":"Backend-3"
"instance":"Backend-1"
"instance":"Backend-2"
"instance":"Backend-3"
"instance":"Backend-1"
"instance":"Backend-2"
"instance":"Backend-3"
"instance":"Backend-1"
```

**[OK] Les requêtes sont réparties équitablement ! (1->2->3->1->2->3...)**

---

**Compter les requêtes par backend (30 requêtes) :**

```bash
for i in {1..30}; do curl -s http://api.local/ | grep -o '"instance":"[^"]*"'; done | sort | uniq -c
```

**Résultat :**

```
     10 "instance":"Backend-1"
     10 "instance":"Backend-2"
     10 "instance":"Backend-3"
```

**[OK] Répartition parfaite : 33.3% chacun !**

---

### ÉTAPE 8 : Tester le Failover (Haute Disponibilité)

**Stopper le Backend 2 :**

```bash
pm2 stop api-backend-2
```

---

**Faire des requêtes :**

```bash
for i in {1..10}; do curl -s http://api.local/ | grep -o '"instance":"[^"]*"'; done
```

**Résultat :**

```
"instance":"Backend-1"
"instance":"Backend-3"
"instance":"Backend-1"
"instance":"Backend-3"
"instance":"Backend-1"
"instance":"Backend-3"
...
```

**[OK] Backend-2 n'apparaît plus ! Nginx a détecté qu'il était down.**

**Le trafic est automatiquement redirigé vers Backend-1 et Backend-3.**

---

**Redémarrer Backend-2 :**

```bash
pm2 start api-backend-2
```

**Attendre 30 secondes (fail_timeout), puis refaire des requêtes :**

```bash
for i in {1..10}; do curl -s http://api.local/ | grep -o '"instance":"[^"]*"'; done
```

**Résultat :**

```
"instance":"Backend-1"
"instance":"Backend-2"  <- De retour !
"instance":"Backend-3"
...
```

**[OK] Failover automatique + Retour automatique !**

---

### ÉTAPE 9 : Tester différents algorithmes de Load Balancing

**Algorithme 1 : Round-robin (défaut, déjà testé)**

Configuration actuelle (aucune directive dans upstream).

---

**Algorithme 2 : Least Connections**

**Éditer le upstream :**

```bash
sudo nano /etc/nginx/sites-available/nodejs-api
```

**Ajouter au début du bloc `upstream` :**

```nginx
upstream nodejs_backend {
    least_conn;  # <- Ajouter cette ligne
    
    server 127.0.0.1:3001 weight=1 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3002 weight=1 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3003 weight=1 max_fails=3 fail_timeout=30s;
    
    keepalive 32;
}
```

**Recharger Nginx :**

```bash
sudo nginx -t
sudo systemctl reload nginx
```

---

**Tester avec requêtes lentes :**

```bash
# Terminal 1 : Requête lente vers Backend 1 (bloque pendant 2s)
curl http://api.local/api/slow

# Terminal 2 : Pendant ce temps, lancer d'autres requêtes
for i in {1..5}; do curl -s http://api.local/ | grep -o '"instance":"[^"]*"'; done
```

**Résultat :**

Pendant que Backend-1 traite la requête lente, Nginx envoie les nouvelles requêtes vers Backend-2 et Backend-3 (qui ont moins de connexions actives).

**[OK] Least connections fonctionne !**

---

**Algorithme 3 : IP Hash (Session Sticky)**

**Éditer le upstream :**

```nginx
upstream nodejs_backend {
    ip_hash;  # <- Remplacer least_conn par ip_hash
    
    server 127.0.0.1:3001 weight=1 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3002 weight=1 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3003 weight=1 max_fails=3 fail_timeout=30s;
    
    keepalive 32;
}
```

**Recharger Nginx :**

```bash
sudo nginx -t
sudo systemctl reload nginx
```

---

**Tester (toutes les requêtes du même client vont au même backend) :**

```bash
for i in {1..10}; do curl -s http://api.local/ | grep -o '"instance":"[^"]*"'; done
```

**Résultat :**

```
"instance":"Backend-2"
"instance":"Backend-2"
"instance":"Backend-2"
"instance":"Backend-2"
"instance":"Backend-2"
...
```

**[OK] Toutes les requêtes du même client (même IP) vont au même backend !**

**Session sticky activée (utile pour sessions en mémoire).**

---

**[ATTENTION] Pour la suite de l'exercice, remettre round-robin (défaut) :**

**Commenter `ip_hash;` dans le upstream :**

```nginx
upstream nodejs_backend {
    # ip_hash;  # <- Commenté
    
    server 127.0.0.1:3001 weight=1 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3002 weight=1 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3003 weight=1 max_fails=3 fail_timeout=30s;
    
    keepalive 32;
}
```

**Recharger Nginx.**

---

### ÉTAPE 10 : Ajouter des Health Checks Actifs (Module Premium)

**[ATTENTION] Note : Les health checks actifs nécessitent Nginx Plus (payant) ou un module tiers.**

**Avec Nginx Open Source, les health checks sont PASSIFS :**
- Nginx détecte qu'un backend est down quand il échoue `max_fails` fois
- Puis le réessaie après `fail_timeout`

**Avec Nginx Plus (ou module) :**
- Nginx envoie périodiquement des requêtes de health check
- `/health` toutes les 5 secondes par exemple
- Si échec -> Backend marqué down immédiatement

---

**Alternative : Health Checks passifs optimisés**

**Notre configuration actuelle suffit pour la plupart des cas :**

```nginx
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
```

- Après 3 échecs : Backend down
- Réessai après 30 secondes
- Failover automatique vers les autres backends

---

**Tester le health check passif :**

**1. Activer le mode erreur sur Backend-2 :**

```bash
curl http://localhost:3002/api/toggle-error
```

**Résultat :**

```json
{
  "instance": "Backend-2",
  "errorMode": true,
  "message": "Cette instance va maintenant renvoyer des erreurs 500"
}
```

---

**2. Faire des requêtes via Nginx :**

```bash
for i in {1..20}; do 
    curl -s http://api.local/api/data | grep -o '"instance":"[^"]*"' || echo "Erreur"
done
```

**Résultat :**

```
"instance":"Backend-1"
"instance":"Backend-2"  <- Erreur 500 (1ère fois)
"instance":"Backend-3"
"instance":"Backend-1"
Erreur                  <- Backend-2 en erreur (2e fois)
"instance":"Backend-3"
"instance":"Backend-1"
Erreur                  <- Backend-2 en erreur (3e fois, marqué down)
"instance":"Backend-3"
"instance":"Backend-1"
"instance":"Backend-3"  <- Backend-2 n'est plus utilisé
"instance":"Backend-1"
...
```

**[OK] Après 3 échecs, Backend-2 est automatiquement exclu !**

---

**3. Désactiver le mode erreur :**

```bash
curl http://localhost:3002/api/toggle-error
```

**Attendre 30 secondes (fail_timeout), Backend-2 reviendra automatiquement.**

---

### ÉTAPE 11 : Monitoring des Backends

**Consulter le statut Nginx :**

```bash
curl http://api.local/nginx-status
```

**Résultat :**

```
Active connections: 5
server accepts handled requests
 1234 1234 5678
Reading: 0 Writing: 2 Waiting: 3
```

**Explication :**

**Active connections: 5** : 5 connexions actives actuellement

**server accepts handled requests**
- 1234 : Connexions acceptées
- 1234 : Connexions traitées (handled)
- 5678 : Nombre total de requêtes

**Reading: 0** : Nginx lit la requête (0 en cours)
**Writing: 2** : Nginx écrit la réponse (2 en cours)
**Waiting: 3** : Connexions en keepalive (3 en attente)

---

**Consulter les logs des backends (PM2) :**

```bash
pm2 logs
```

**Tu verras les logs en temps réel des 3 backends.**

**Chaque requête est loguée avec la couleur du backend.**

---

**Voir les statistiques PM2 :**

```bash
pm2 monit
```

**Interface en temps réel :**
- CPU usage par backend
- Memory usage
- Nombre de restarts

---

### ÉTAPE 12 : Benchmarker les Performances

**Installer Apache Bench (si pas déjà fait) :**

```bash
sudo apt install apache2-utils -y
```

---

**Benchmarker le reverse proxy Nginx :**

```bash
ab -n 10000 -c 100 http://api.local/
```

**`-n 10000`** : 10,000 requêtes
**`-c 100`** : 100 requêtes concurrentes

**Résultat (exemple) :**

```
Server Software:        nginx/1.18.0
Server Hostname:        api.local
Server Port:            80

Concurrency Level:      100
Time taken for tests:   3.456 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      2340000 bytes
HTML transferred:       1234000 bytes
Requests per second:    2893.52 [#/sec] (mean)
Time per request:       34.563 [ms] (mean)
Time per request:       0.346 [ms] (mean, across all concurrent requests)
Transfer rate:          660.75 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.5      1       5
Processing:     2   33  15.2     30     120
Waiting:        1   30  14.8     28     115
Total:          3   34  15.3     31     122

Percentage of the requests served within a certain time (ms)
  50%     31
  66%     38
  75%     43
  80%     47
  90%     56
  95%     65
  98%     78
  99%     89
 100%    122 (longest request)
```

**[OK] Nginx gère ~3000 requêtes/seconde avec 3 backends !**

**Si on compare avec un backend seul :**

```bash
ab -n 10000 -c 100 http://localhost:3001/
# Résultat : ~1000 req/s

# Avec load balancing (3 backends) : ~3000 req/s
# -> 3x plus performant ! [OK]
```

---

### [OK] TESTS DE VALIDATION

**1. Reverse proxy fonctionne**

```bash
curl http://api.local/
```

- [ ] Répond avec JSON (pas d'erreur)
- [ ] `instance` indique quel backend a répondu
- [ ] Headers `x-real-ip`, `x-forwarded-for` corrects

---

**2. Load balancing round-robin**

```bash
for i in {1..30}; do curl -s http://api.local/ | grep -o '"instance":"[^"]*"'; done | sort | uniq -c
```

- [ ] Répartition équitable : ~10 requêtes par backend

---

**3. Failover automatique**

- [ ] Stopper un backend : `pm2 stop api-backend-2`
- [ ] Requêtes continuent vers Backend-1 et Backend-3
- [ ] Redémarrer : `pm2 start api-backend-2`
- [ ] Backend-2 reprend du trafic après 30s

---

**4. Health check passif**

- [ ] Activer mode erreur sur Backend-2
- [ ] Après 3 erreurs, Backend-2 exclu automatiquement
- [ ] Désactiver mode erreur
- [ ] Backend-2 revient après 30s

---

**5. Performance**

```bash
ab -n 1000 -c 100 http://api.local/
```

- [ ] Requests per second > 2000
- [ ] Failed requests = 0
- [ ] Charge répartie sur les 3 backends

---

### [ROUGE] ERREURS COURANTES ET SOLUTIONS

#### Erreur 1 : "502 Bad Gateway"

**Symptôme :**

```
502 Bad Gateway
nginx/1.18.0
```

**Causes possibles :**

**1. Aucun backend ne répond**

```bash
pm2 list
# Vérifier que les 3 backends sont "online"

# Si stopped :
pm2 start ecosystem.config.js
```

---

**2. Mauvais port dans upstream**

**Vérifier que les ports correspondent :**

```nginx
# Nginx upstream
server 127.0.0.1:3001;

# PM2 ecosystem.config.js
PORT: 3001
```

---

**3. Firewall bloque les ports**

```bash
# Vérifier que les ports sont accessibles en local
curl http://localhost:3001
curl http://localhost:3002
curl http://localhost:3003
```

**Si erreur, vérifier le firewall :**

```bash
sudo ufw status
# Ports 3001-3003 doivent être accessibles en local (pas besoin d'ouvrir au monde)
```

---

#### Erreur 2 : Pas de load balancing (toujours le même backend)

**Symptôme :**

Toutes les requêtes vont au même backend.

**Cause : `ip_hash` activé**

**Vérifier dans le upstream :**

```nginx
upstream nodejs_backend {
    ip_hash;  # <- Si présent, commenter
    ...
}
```

**Avec `ip_hash`, même IP -> même backend.**

**Solution : Commenter ou supprimer `ip_hash;`**

---

#### Erreur 3 : Keepalive ne fonctionne pas

**Symptôme :**

Nouvelles connexions TCP pour chaque requête (lent).

**Cause : Oubli de `proxy_http_version 1.1` ou `Connection ""`**

**Vérifier dans la location :**

```nginx
location / {
    proxy_pass http://nodejs_backend;
    proxy_http_version 1.1;         # <- Obligatoire
    proxy_set_header Connection ""; # <- Obligatoire
    ...
}
```

**Sans ces directives, pas de keepalive !**

---

#### Erreur 4 : Backend marqué "down" trop rapidement

**Symptôme :**

Un backend est désactivé après un seul échec.

**Cause : `max_fails=1` (défaut)**

**Solution : Augmenter `max_fails` :**

```nginx
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
```

**`max_fails=3`** : Nécessite 3 échecs avant de marquer down

---

#### Erreur 5 : Headers client non transmis au backend

**Symptôme :**

Le backend ne voit pas l'IP réelle du client (voit 127.0.0.1).

**Cause : Oubli de `proxy_set_header`**

**Vérifier :**

```nginx
location / {
    proxy_pass http://nodejs_backend;
    proxy_set_header X-Real-IP $remote_addr;        # <- Ajouter
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # <- Ajouter
    ...
}
```

---

### [IMPORTANT] POINTS CLÉS À RETENIR

**1. Reverse Proxy Nginx**
- Architecture événementielle (10x plus performant qu'Apache)
- Configuration simple et concise
- Optimisations natives (keepalive, buffering)

**2. Upstream et Load Balancing**
- `upstream {}` définit le cluster de backends
- Algorithmes : round-robin (défaut), least_conn, ip_hash
- Weight pour backends de capacités différentes

**3. Failover automatique**
- `max_fails` + `fail_timeout` pour détecter les backends down
- `proxy_next_upstream` pour essayer d'autres backends
- Récupération automatique après fail_timeout

**4. Headers de Proxying**
- `X-Real-IP` : IP du client
- `X-Forwarded-For` : Chaîne des proxies
- `X-Forwarded-Proto` : Protocole original (http/https)
- `Host` : Hostname original

**5. Optimisations**
- Keepalive vers backends (réutilisation connexions)
- Buffering pour libérer backends rapidement
- HTTP/1.1 obligatoire pour keepalive

---

### [RAPIDE] POUR ALLER PLUS LOIN

**1. HTTPS avec SSL Termination**

**Nginx termine SSL, backends en HTTP :**

```nginx
server {
    listen 443 ssl http2;
    ssl_certificate /etc/nginx/ssl/api.crt;
    ssl_certificate_key /etc/nginx/ssl/api.key;
    
    location / {
        proxy_pass http://nodejs_backend;  # HTTP (pas HTTPS)
        proxy_set_header X-Forwarded-Proto https;
        ...
    }
}
```

**Avantages :**
- SSL/TLS géré centralement (1 certificat)
- Backends plus simples (pas de SSL)
- Performance (CPU déchargé)

---

**2. Cache de contenu avec proxy_cache**

```nginx
# Dans http {}
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=APICACHE:100m inactive=60m;
proxy_cache_key "$scheme$request_method$host$request_uri";

# Dans location /
proxy_cache APICACHE;
proxy_cache_valid 200 60m;
proxy_cache_bypass $http_cache_control;
add_header X-Cache-Status $upstream_cache_status;
```

**Réponses servies depuis le cache (ultra rapide) !**

---

**3. Rate Limiting par IP**

```nginx
# Dans http {}
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

# Dans location /
limit_req zone=api_limit burst=20 nodelay;
```

**Limite : 10 requêtes/seconde par IP**

---

**4. Sticky Sessions avec cookies (Nginx Plus)**

**Nginx Plus peut gérer les sessions sticky sans ip_hash :**

```nginx
upstream nodejs_backend {
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
    
    sticky cookie srv_id expires=1h domain=.api.local path=/;
}
```

**Nginx ajoute un cookie `srv_id` identifiant le backend.**

---

**5. WebSocket Proxying**

```nginx
location /ws {
    proxy_pass http://nodejs_backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 86400; # 24h pour WebSocket long-lived
}
```

---

**6. Backends sur serveurs distants**

```nginx
upstream nodejs_backend {
    server 192.168.1.10:3001;  # Serveur 1
    server 192.168.1.11:3001;  # Serveur 2
    server 192.168.1.12:3001;  # Serveur 3
    
    keepalive 32;
}
```

**Architecture distribuée pour scalabilité horizontale !**

---

## [COURS] CONCLUSION DE L'EXERCICE 4

**[OK] Félicitations ! Tu maîtrises le Reverse Proxy et Load Balancing avec Nginx !**

**Ce que tu as appris :**
- Configurer Nginx comme reverse proxy
- Mettre en place un cluster de backends (upstream)
- Implémenter plusieurs algorithmes de load balancing
- Gérer le failover automatique
- Optimiser les performances (keepalive, buffering)
- Transmettre les headers correctement
- Monitorer les backends

**Compétences acquises :**
- [OK] Reverse Proxy Nginx (niveau avancé)
- [OK] Load Balancing multi-algorithmes
- [OK] Haute disponibilité et failover
- [OK] Optimisation performance proxy
- [OK] Architecture distribuée

**Temps moyen de réalisation :** 2-3 heures

**Prochaine étape :** Exercice 5 - Sécurisation complète de Nginx ! [VERROUILLE]

---

---

# [ROUGE] EXERCICE 5 : SÉCURISATION COMPLÈTE DE NGINX (EXPERT)

## [LISTE] ÉNONCÉ

### Contexte professionnel

Tu es consultant en sécurité pour une entreprise qui lance un service web critique. Le RSSI exige un niveau de sécurité maximal conforme aux standards OWASP, PCI-DSS et ISO 27001. Ton mission : auditer et sécuriser complètement le serveur Nginx.

### Objectifs

Implémenter une protection multi-niveaux :
1. **Authentification HTTP** : Protéger zones sensibles
2. **Restrictions d'accès** : IP whitelisting, géo-blocking
3. **Headers de sécurité** : HSTS, CSP, X-Frame-Options, etc.
4. **Rate Limiting** : Protection anti-DDoS/brute-force
5. **WAF** : ModSecurity + OWASP CRS
6. **Durcissement Nginx** : Masquer version, sécuriser config
7. **Monitoring** : Détection intrusions, alertes
8. **Conformité** : OWASP Top 10, PCI-DSS

### Pourquoi Nginx est meilleur qu'Apache pour la Sécurité

| Critère | Apache | Nginx |
|---------|--------|-------|
| **Surface d'attaque** | Grande (modules) | * Petite (minimaliste) |
| **Isolation** | Moyenne | * Excellente |
| **Rate limiting** | mod_ratelimit | * Native, plus efficace |
| **DDoS protection** | Limité | * Excellent |
| **Config vulnérable** | .htaccess distribué | * Centralisée |
| **Performance sécurité** | Bon | * Excellent |

### Contraintes

- Nginx 1.18+
- ModSecurity 3.0+
- Fail2Ban
- Conformité OWASP Top 10
- Temps estimé : 3-5 heures

---

## [OBJECTIF] OBJECTIFS PÉDAGOGIQUES

À la fin de cet exercice, tu sauras :

- [OK] Configurer l'authentification HTTP Basic/Digest
- [OK] Implémenter des ACL (Access Control Lists)
- [OK] Ajouter TOUS les headers de sécurité
- [OK] Configurer le rate limiting multi-niveaux
- [OK] Installer et configurer ModSecurity WAF
- [OK] Intégrer Fail2Ban avec Nginx
- [OK] Durcir la configuration Nginx
- [OK] Auditer la sécurité (Nikto, OWASP ZAP)
- [OK] Monitorer et détecter les intrusions

---

## [DOCS] PRÉREQUIS

- Tous les exercices précédents terminés
- Compréhension solide de Nginx
- Notions de sécurité web
- Familiarité avec regex

---

## [OK] SOLUTION COMPLÈTE

### ÉTAPE 1 : Créer un site sécurisé avec zone admin

**Créer la structure :**

```bash
sudo mkdir -p /var/www/secure-site/{public,admin}
```

---

**Page d'accueil publique :**

```bash
sudo nano /var/www/secure-site/public/index.html
```

**Contenu :**

```html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Site Sécurisé</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
        h1 { color: #2c3e50; }
        .security-badge { background: #27ae60; color: white; padding: 10px 20px; border-radius: 5px; display: inline-block; margin: 20px 0; }
        .info { background: #ecf0f1; padding: 15px; border-left: 4px solid #3498db; margin: 20px 0; }
        a { color: #3498db; text-decoration: none; }
        a:hover { text-decoration: underline; }
    </style>
</head>
<body>
    <h1>[SECURISE] Site Web Ultra-Sécurisé</h1>
    <div class="security-badge">[OK] Protégé par Nginx + ModSecurity</div>
    
    <div class="info">
        <h3>Mesures de sécurité actives :</h3>
        <ul>
            <li>HTTPS avec TLS 1.3</li>
            <li>Headers de sécurité (HSTS, CSP, X-Frame-Options)</li>
            <li>Rate Limiting (protection DDoS)</li>
            <li>WAF ModSecurity + OWASP CRS</li>
            <li>Fail2Ban (anti brute-force)</li>
            <li>Authentification zone admin</li>
            <li>Monitoring temps réel</li>
        </ul>
    </div>
    
    <p><a href="/admin/">-> Accéder à la zone d'administration (protégée)</a></p>
</body>
</html>
```

---

**Page admin protégée :**

```bash
sudo nano /var/www/secure-site/admin/index.html
```

**Contenu :**

```html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Administration</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; background: #2c3e50; color: white; }
        h1 { color: #ecf0f1; }
        .admin-panel { background: #34495e; padding: 20px; border-radius: 8px; margin: 20px 0; }
        .success { background: #27ae60; padding: 10px; border-radius: 5px; margin: 10px 0; }
    </style>
</head>
<body>
    <h1>[VERROUILLE] Panneau d'Administration</h1>
    <div class="success">[OK] Authentification réussie</div>
    
    <div class="admin-panel">
        <h3>Options disponibles :</h3>
        <ul>
            <li>Gestion des utilisateurs</li>
            <li>Configuration système</li>
            <li>Logs de sécurité</li>
            <li>Monitoring en temps réel</li>
        </ul>
    </div>
    
    <p><a href="/" style="color: #3498db;"><- Retour à l'accueil</a></p>
</body>
</html>
```

---

**Permissions :**

```bash
sudo chown -R www-data:www-data /var/www/secure-site
sudo find /var/www/secure-site -type d -exec chmod 755 {} \;
sudo find /var/www/secure-site -type f -exec chmod 644 {} \;
```

---

### ÉTAPE 2 : Configurer l'authentification HTTP Basic

**Créer le dossier pour les fichiers d'auth :**

```bash
sudo mkdir -p /etc/nginx/auth
```

---

**Créer le fichier de mots de passe :**

```bash
# Installer htpasswd (si pas déjà fait)
sudo apt install apache2-utils -y

# Créer le fichier et ajouter un utilisateur
sudo htpasswd -c /etc/nginx/auth/.htpasswd admin
```

**Entre un mot de passe fort : `AdminSecure2024!`**

---

**Ajouter d'autres utilisateurs :**

```bash
# SANS -c (n'écrase pas le fichier)
sudo htpasswd /etc/nginx/auth/.htpasswd user2
sudo htpasswd /etc/nginx/auth/.htpasswd user3
```

---

**Sécuriser le fichier de mots de passe :**

```bash
sudo chown root:www-data /etc/nginx/auth/.htpasswd
sudo chmod 640 /etc/nginx/auth/.htpasswd
```

**Permissions 640 :**
- root : rw- (lecture/écriture)
- www-data : r-- (lecture seule)
- others : --- (aucun accès)

---

**Vérifier le contenu (mots de passe hashés) :**

```bash
sudo cat /etc/nginx/auth/.htpasswd
```

**Résultat :**

```
admin:$apr1$xyz123...$aBcDeFgHiJkLmNoPqRsTuVwXy
user2:$apr1$abc456...$ZyXwVuTsRqPoNmLkJiHgFeDcBa
user3:$apr1$def789...$1234567890abcdefghijklmnop
```

**Format :** `username:hashed_password`

**Hash BCrypt/APR1 (sécurisé, impossible à décrypter)**

---

### ÉTAPE 3 : Créer le server block sécurisé

**Créer la configuration :**

```bash
sudo nano /etc/nginx/sites-available/secure-site
```

**Contenu (partie 1/3) :**

```nginx
# ═══════════════════════════════════════════════════════════════
# SITE WEB ULTRA-SÉCURISÉ
# ═══════════════════════════════════════════════════════════════

# ───────────────────────────────────────────────────────────────
# RATE LIMITING ZONES (GLOBAL)
# ───────────────────────────────────────────────────────────────

# Zone pour limiter par IP
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=10r/s;
# $binary_remote_addr = IP du client en format binaire (4 octets pour IPv4)
# Pourquoi binaire ? Plus compact que texte (économie RAM)
# 192.168.1.50 (texte) = 12 octets
# 192.168.1.50 (binaire) = 4 octets
#
# zone=general_limit:10m :
# Nom de la zone : general_limit
# Taille : 10 MB (stocke ~160,000 IPs)
#
# rate=10r/s :
# Limite : 10 requêtes par seconde par IP
# Calcul sur 1 minute glissante

# Zone stricte pour admin
limit_req_zone $binary_remote_addr zone=admin_limit:10m rate=2r/s;
# Admin : 2 requêtes/seconde (plus strict)

# Zone pour login (anti brute-force)
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/m;
# Login : 1 requête par MINUTE (très strict)

# ───────────────────────────────────────────────────────────────
# CONNECTION LIMITING (LIMITE DE CONNEXIONS SIMULTANÉES)
# ───────────────────────────────────────────────────────────────

limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# Limite le nombre de connexions SIMULTANÉES par IP
# 10m = stocke ~160,000 IPs
# Utilisation : limit_conn conn_limit 10; (dans server/location)

# ───────────────────────────────────────────────────────────────
# SERVER BLOCK HTTPS
# ───────────────────────────────────────────────────────────────

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name secure.local;
    
    # Racine selon l'URL
    root /var/www/secure-site/public;
    index index.html;
    
    # ═══════════════════════════════════════════════════════════
    # CERTIFICAT SSL
    # ═══════════════════════════════════════════════════════════
    
    ssl_certificate /etc/nginx/ssl/secure.crt;
    ssl_certificate_key /etc/nginx/ssl/secure.key;
    
    # Protocoles sécurisés
    ssl_protocols TLSv1.2 TLSv1.3;
    
    # Ciphers modernes et sécurisés
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
    ssl_prefer_server_ciphers on;
    
    # Session cache
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_session_tickets off;
    
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    
    # ═══════════════════════════════════════════════════════════
    # HEADERS DE SÉCURITÉ (COMPLETS)
    # ═══════════════════════════════════════════════════════════
    
    # HSTS : Force HTTPS pendant 2 ans
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    # 63072000 secondes = 2 ans
    # includeSubDomains = S'applique aussi à *.secure.local
    # preload = Eligible pour HSTS preload list (navigateurs)
    #
    # Preload list = Liste hardcodée dans les navigateurs
    # Domaines dans cette liste : TOUJOURS en HTTPS
    # Même à la 1ère visite (avant de recevoir le header)
    #
    # Soumettre à : https://hstspreload.org/
    
    # Clickjacking protection
    add_header X-Frame-Options "DENY" always;
    # DENY = Interdit TOUTES les iframes
    # SAMEORIGIN = Autorise iframes du même domaine
    # Protège contre attaques clickjacking
    
    # XSS Protection
    add_header X-XSS-Protection "1; mode=block" always;
    # Active le filtre XSS du navigateur
    # mode=block = Bloque la page si XSS détecté
    # Note : Déprécié au profit de CSP, mais utile pour vieux navigateurs
    
    # MIME Sniffing protection
    add_header X-Content-Type-Options "nosniff" always;
    # Empêche le navigateur de "deviner" le type MIME
    # Force à respecter le Content-Type envoyé
    
    # Referrer Policy
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    # strict-origin-when-cross-origin :
    # - Même origine : URL complète
    # - Cross-origin HTTPS->HTTPS : Origine seulement
    # - Cross-origin HTTPS->HTTP : Rien (protège privacy)
    
    # Content Security Policy (CSP) - STRICT
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests;" always;
    # Décomposition ligne par ligne :
    #
    # default-src 'self' :
    # Par défaut, autoriser SEULEMENT les ressources du même domaine
    #
    # script-src 'self' :
    # Scripts JavaScript : même domaine uniquement
    # Pas de inline <script>, pas de eval(), pas de CDN externe
    # (Sauf si on ajoute 'unsafe-inline' ou les CDN)
    #
    # style-src 'self' 'unsafe-inline' :
    # CSS : même domaine + inline <style>
    # 'unsafe-inline' nécessaire pour <style> dans HTML
    # En production strict : Éviter inline, utiliser fichiers .css
    #
    # img-src 'self' data: https: :
    # Images : même domaine + data: URLs + toutes URLs HTTPS
    # data: pour images base64
    # https: pour images de CDN/externes en HTTPS
    #
    # font-src 'self' data: :
    # Polices : même domaine + data: URLs (polices inline)
    #
    # connect-src 'self' :
    # Connexions (fetch, XHR, WebSocket) : même domaine uniquement
    #
    # frame-ancestors 'none' :
    # Interdit d'afficher ce site dans une iframe (comme X-Frame-Options DENY)
    # Plus moderne que X-Frame-Options
    #
    # base-uri 'self' :
    # Balise <base> : même domaine uniquement
    # Protège contre injection de <base> malveillant
    #
    # form-action 'self' :
    # Formulaires : soumission vers même domaine uniquement
    # Empêche redirection de formulaire vers site malveillant
    #
    # upgrade-insecure-requests :
    # Convertit automatiquement HTTP en HTTPS
    # <img src="http://..."> -> <img src="https://...">
    
    # Permissions Policy (anciennement Feature Policy)
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=(), interest-cohort=()" always;
    # Désactive les fonctionnalités sensibles du navigateur
    #
    # geolocation=() : Pas de géolocalisation
    # microphone=() : Pas d'accès au micro
    # camera=() : Pas d'accès à la caméra
    # payment=() : Pas d'API Payment Request
    # usb=() : Pas d'accès USB
    # magnetometer=(), gyroscope=(), accelerometer=() : Pas de capteurs
    # interest-cohort=() : Opt-out de FLoC (Google)
    #
    # Pour autoriser sur ce domaine :
    # geolocation=(self)
    #
    # Pour autoriser sur domaine spécifique :
    # geolocation=(self "https://maps.example.com")
    
    # Expect-CT (Certificate Transparency)
    add_header Expect-CT "max-age=86400, enforce" always;
    # Certificate Transparency = Log public des certificats SSL
    # Détecte certificats SSL frauduleux
    # max-age=86400 = 24 heures
    # enforce = Bloque si certificat non loggé
    
    # Cross-Origin Policies
    add_header Cross-Origin-Embedder-Policy "require-corp" always;
    add_header Cross-Origin-Opener-Policy "same-origin" always;
    add_header Cross-Origin-Resource-Policy "same-origin" always;
    # Protection contre attaques Spectre/Meltdown via cross-origin
    
    # Masquer le serveur
    server_tokens off;
    # Masque la version de Nginx
    # Sans : Server: nginx/1.18.0 (Ubuntu)
    # Avec : Server: nginx
    # Sécurité par obscurcissement (pas 100% sûr mais utile)
    
    # Ne pas envoyer X-Powered-By
    more_clear_headers 'Server';
    more_clear_headers 'X-Powered-By';
    # Nécessite ngx_headers_more (à installer)
    # Supprime complètement le header Server
    
    # ═══════════════════════════════════════════════════════════
    # RATE LIMITING GLOBAL
    # ═══════════════════════════════════════════════════════════
    
    limit_req zone=general_limit burst=20 nodelay;
    # Applique le rate limiting général
    # zone=general_limit : 10 req/s
    # burst=20 : Buffer de 20 requêtes (pic de trafic)
    # nodelay : Pas de délai, rejette immédiatement si dépassé
    #
    # Comment ça marche :
    # Limite : 10 req/s (600 req/min)
    # Burst : 20 requêtes d'un coup OK
    # Au-delà : 503 Service Unavailable
    #
    # Exemple :
    # Client envoie 25 requêtes d'un coup
    # -> 20 acceptées (burst)
    # -> 5 rejetées (503)
    # Puis : 10 req/s max
    
    limit_conn conn_limit 10;
    # Limite : 10 connexions simultanées par IP
    # Si un client a 11 connexions ouvertes :
    # -> 11e connexion rejetée (503)
    #
    # Protège contre :
    # - Slowloris (connexions lentes qui saturent le serveur)
    # - Téléchargements massifs simultanés
    
    # ═══════════════════════════════════════════════════════════
    # LOCATION PUBLIQUE (/)
    # ═══════════════════════════════════════════════════════════
    
    location / {
        try_files $uri $uri/ =404;
    }
    
    # ═══════════════════════════════════════════════════════════
    # LOCATION ADMIN (PROTÉGÉE)
    # ═══════════════════════════════════════════════════════════
    
    location /admin/ {
        # Changer la racine pour admin
        root /var/www/secure-site;
        # /admin/ -> /var/www/secure-site/admin/
        
        # Authentification HTTP Basic
        auth_basic "Zone Administration - Accès Restreint";
        # Texte affiché dans la popup d'authentification
        # Équivalent Apache : AuthName "..."
        
        auth_basic_user_file /etc/nginx/auth/.htpasswd;
        # Fichier contenant les utilisateurs/mots de passe
        # Équivalent Apache : AuthUserFile ...
        
        # Satisfaire DEUX conditions (ET logique)
        satisfy all;
        # Exige authentification ET restriction IP
        #
        # satisfy any : OU logique (authentification OU IP)
        # satisfy all : ET logique (authentification ET IP) <- Plus strict
        
        # Restriction IP (whitelist)
        allow 192.168.1.0/24;  # Réseau local
        allow 127.0.0.1;        # Localhost
        deny all;               # Tout le reste interdit
        # L'ordre est important :
        # 1. allow ... (autorise)
        # 2. allow ... (autorise)
        # 3. deny all (interdit tout le reste)
        #
        # Si IP 192.168.1.50 :
        # -> Correspond à 192.168.1.0/24 -> Autorisé (puis demande auth)
        #
        # Si IP 8.8.8.8 :
        # -> Ne correspond à aucun allow -> deny all -> 403 Forbidden
        
        # Rate limiting STRICT pour admin
        limit_req zone=admin_limit burst=5 nodelay;
        # Admin : 2 req/s + burst de 5
        # Plus strict que zone publique
        
        # Bloquer certaines méthodes HTTP dangereuses
        if ($request_method !~ ^(GET|POST)$) {
            return 405;  # Method Not Allowed
        }
        # Autorise seulement GET et POST
        # Bloque : PUT, DELETE, TRACE, OPTIONS, etc.
        # Protège contre certaines attaques
        
        # Fichier index
        try_files $uri $uri/ =404;
    }
    
    # Suite dans partie 2...
```

**Partie 2/3 de la configuration :**

```nginx
    # ═══════════════════════════════════════════════════════════
    # LOCATIONS SÉCURITÉ
    # ═══════════════════════════════════════════════════════════
    
    # Bloquer fichiers cachés (sauf .well-known pour Let's Encrypt)
    location ~ /\.(?!well-known) {
        deny all;
        access_log off;
        log_not_found off;
    }
    # Regex :
    # ~ = Case-sensitive regex
    # /\. = Slash + point littéral
    # (?!well-known) = Negative lookahead (sauf .well-known)
    #
    # Bloque : .git, .svn, .env, .htaccess, etc.
    # Autorise : .well-known (nécessaire pour certbot)
    
    # Bloquer fichiers sensibles
    location ~ \.(bak|backup|config|sql|log|ini|old|temp|tmp|dist|save|swp)$ {
        deny all;
        access_log off;
        log_not_found off;
    }
    # Extensions dangereuses à bloquer :
    # .bak, .backup = Fichiers de sauvegarde
    # .config, .ini = Fichiers de configuration
    # .sql = Dumps de base de données
    # .log = Logs (peuvent contenir infos sensibles)
    # .old, .temp, .tmp = Fichiers temporaires
    # .dist = Fichiers de distribution (peuvent contenir secrets)
    # .save = Sauvegardes
    # .swp = Fichiers swap de vim (peuvent contenir mots de passe)
    
    # Bloquer exécution de scripts dans uploads
    location ~* ^/uploads/.*\.(php|php5|phtml|pl|py|jsp|asp|aspx|cgi)$ {
        deny all;
    }
    # Empêche l'exécution de scripts uploadés par des users
    # Protège contre upload de webshell
    #
    # Scénario d'attaque sans ce blocage :
    # 1. Attaquant upload shell.php dans /uploads/
    # 2. Accède à /uploads/shell.php
    # 3. PHP exécute le script -> Backdoor !
    #
    # Avec ce blocage :
    # -> /uploads/shell.php retourne 403 Forbidden
    # -> Script non exécuté [OK]
    
    # Bloquer user-agents malveillants
    if ($http_user_agent ~* (nikto|sqlmap|nmap|masscan|nessus|openvas|metasploit|burp|w3af|acunetix|havij)) {
        return 403;
    }
    # Détecte les scanners de vulnérabilités
    # User-Agent contient "nikto" -> 403 Forbidden
    #
    # Outils bloqués :
    # nikto, sqlmap = Scanners web
    # nmap, masscan = Scanners réseau
    # nessus, openvas = Scanners de vulnérabilités
    # metasploit = Framework d'exploitation
    # burp, w3af, acunetix, havij = Outils de pentest
    #
    # [ATTENTION] Sécurité par obscurcissement (pas 100% efficace)
    # Un attaquant peut changer son User-Agent
    # Mais bloque les script kiddies
    
    # Bloquer requêtes avec corps trop volumineux (DoS)
    client_max_body_size 10M;
    # Limite : 10 MB par requête
    # Protège contre upload massif (DoS)
    # Si upload > 10 MB -> 413 Request Entity Too Large
    #
    # À ajuster selon besoin :
    # API JSON : 1M
    # Upload images : 10M
    # Upload vidéos : 100M
    
    # Bloquer injections SQL dans URL
    location ~ (\<|%3C).*script.*(\>|%3E) {
        return 403;
    }
    # Détecte <script> dans l'URL (XSS)
    # %3C = < encodé
    # %3E = > encodé
    
    if ($query_string ~* "(union|select|insert|update|delete|drop|concat|--|'|;|\*|<|>)" ) {
        return 403;
    }
    # Détecte patterns d'injection SQL dans query string
    # Exemples bloqués :
    # ?id=1 UNION SELECT ...
    # ?name='; DROP TABLE users; --
    # ?search=<script>alert(1)</script>
    
    # ═══════════════════════════════════════════════════════════
    # PROTECTION ANTI-HOTLINKING (IMAGES)
    # ═══════════════════════════════════════════════════════════
    
    location ~ \.(jpg|jpeg|png|gif|webp)$ {
        # Vérifier le referer
        valid_referers none blocked server_names *.secure.local;
        # none = Pas de referer (accès direct)
        # blocked = Referer supprimé par firewall/proxy
        # server_names = Referers correspondant aux server_names
        # *.secure.local = Sous-domaines autorisés
        
        if ($invalid_referer) {
            return 403;
        }
        # Si referer invalide (ex: site externe) -> 403
        #
        # Protège contre hotlinking :
        # Site tiers affiche <img src="https://secure.local/image.jpg">
        # -> Consomme ta bande passante
        # -> 403 Forbidden [OK]
        
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
    
    # ═══════════════════════════════════════════════════════════
    # LOGS
    # ═══════════════════════════════════════════════════════════
    
    # Format de log personnalisé avec infos de sécurité
    log_format security '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent" '
                        '$request_time $upstream_response_time '
                        '"$http_x_forwarded_for"';
    # Ajout de request_time et upstream_response_time
    # Pour détecter les requêtes lentes (potentiellement attaques)
    
    access_log /var/log/nginx/secure-access.log security;
    error_log /var/log/nginx/secure-error.log warn;
}

# ═══════════════════════════════════════════════════════════════
# REDIRECTION HTTP -> HTTPS (FORCE HTTPS)
# ═══════════════════════════════════════════════════════════════

server {
    listen 80;
    listen [::]:80;
    server_name secure.local;
    
    # Redirection permanente vers HTTPS
    return 301 https://$server_name$request_uri;
    
    # Log minimal (juste pour monitoring)
    access_log /var/log/nginx/secure-http-access.log;
}
```

**Sauvegarde la configuration.**

---

### ÉTAPE 4 : Créer le certificat SSL

```bash
# Créer le dossier SSL
sudo mkdir -p /etc/nginx/ssl

# Générer certificat auto-signé
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/ssl/secure.key \
  -out /etc/nginx/ssl/secure.crt \
  -subj "/C=SN/ST=Dakar/L=Dakar/O=SecureCompany/CN=secure.local"

# Permissions
sudo chmod 600 /etc/nginx/ssl/secure.key
sudo chmod 644 /etc/nginx/ssl/secure.crt
```

---

### ÉTAPE 5 : Activer le site

```bash
# Lien symbolique
sudo ln -s /etc/nginx/sites-available/secure-site /etc/nginx/sites-enabled/

# Tester
sudo nginx -t

# Si erreur avec more_clear_headers (module manquant), commenter ces lignes :
# more_clear_headers 'Server';
# more_clear_headers 'X-Powered-By';

# Recharger
sudo systemctl reload nginx
```

---

**Ajouter dans `/etc/hosts` (sur ton PC) :**

```
192.168.1.100    secure.local
```

---

### ÉTAPE 6 : Tester l'authentification et les restrictions

**Tester l'accès à la zone admin :**

```
https://secure.local/admin/
```

**1. Depuis une IP autorisée (192.168.1.x) :**
- Popup d'authentification apparaît
- Entre : `admin` / `AdminSecure2024!`
- [OK] Accès accordé

**2. Depuis une IP non autorisée (changé ton IP ou utilise VPN) :**
- [X] 403 Forbidden (avant même l'authentification)

---

**Tester le rate limiting :**

```bash
# Envoyer 30 requêtes rapidement
for i in {1..30}; do curl -Ik https://secure.local/ 2>&1 | grep "HTTP/"; done
```

**Résultat :**

```
HTTP/2 200
HTTP/2 200
...
HTTP/2 503  <- Rate limit dépassé
HTTP/2 503
...
```

**[OK] Rate limiting fonctionne !**

---

### ÉTAPE 7 : Installer et configurer ModSecurity (WAF)

**ModSecurity = Web Application Firewall**

**Installer ModSecurity pour Nginx :**

```bash
sudo apt install -y libnginx-mod-security
```

---

**Configurer ModSecurity :**

```bash
# Copier la config recommandée
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf

# Éditer
sudo nano /etc/modsecurity/modsecurity.conf
```

**Modifier :**

```
SecRuleEngine On
```

**(Changer de `DetectionOnly` à `On`)**

**Sauvegarde.**

---

**Télécharger les règles OWASP Core Rule Set (CRS) :**

```bash
# Installer git
sudo apt install git -y

# Télécharger CRS
cd /tmp
git clone https://github.com/coreruleset/coreruleset.git
cd coreruleset

# Copier les règles
sudo cp -R rules/ /etc/modsecurity/
sudo cp crs-setup.conf.example /etc/modsecurity/crs-setup.conf
```

---

**Activer ModSecurity dans Nginx :**

```bash
sudo nano /etc/nginx/modsec/main.conf
```

**(Créer le fichier si absent)**

**Contenu :**

```nginx
Include /etc/modsecurity/modsecurity.conf
Include /etc/modsecurity/crs-setup.conf
Include /etc/modsecurity/rules/*.conf
```

**Sauvegarde.**

---

**Activer dans le server block :**

```bash
sudo nano /etc/nginx/sites-available/secure-site
```

**Ajouter dans le bloc `server {}` (après `server_name`) :**

```nginx
    modsecurity on;
    modsecurity_rules_file /etc/nginx/modsec/main.conf;
```

---

**Tester et recharger :**

```bash
sudo nginx -t
sudo systemctl reload nginx
```

---

**Tester ModSecurity (injection SQL) :**

```bash
curl "https://secure.local/?id=1' OR '1'='1" -k
```

**Résultat :**

```
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>
```

**[OK] ModSecurity bloque l'injection SQL !**

---

**Consulter les logs ModSecurity :**

```bash
sudo tail -20 /var/log/modsec_audit.log
```

**Tu verras les détails de l'attaque bloquée.**

---

### ÉTAPE 8 : Installer et configurer Fail2Ban

**Fail2Ban = Bannit les IPs après plusieurs tentatives échouées**

```bash
sudo apt install fail2ban -y
```

---

**Créer une jail pour Nginx :**

```bash
sudo nano /etc/fail2ban/jail.d/nginx.conf
```

**Contenu :**

```ini
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/*error.log
maxretry = 3
bantime = 3600
findtime = 600

[nginx-noscript]
enabled = true
port = http,https
filter = nginx-noscript
logpath = /var/log/nginx/*access.log
maxretry = 6
bantime = 3600

[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/*access.log
maxretry = 2
bantime = 86400

[nginx-noproxy]
enabled = true
port = http,https
filter = nginx-noproxy
logpath = /var/log/nginx/*access.log
maxretry = 2
bantime = 86400
```

**Sauvegarde.**

---

**Redémarrer Fail2Ban :**

```bash
sudo systemctl restart fail2ban
```

---

**Vérifier les jails actives :**

```bash
sudo fail2ban-client status
```

**Résultat :**

```
Status
|- Number of jail:	4
`- Jail list:	nginx-badbots, nginx-http-auth, nginx-noproxy, nginx-noscript
```

---

**Simuler une attaque (3 échecs d'authentification) :**

```bash
for i in {1..5}; do curl -u wrong:wrong https://secure.local/admin/ -k; done
```

---

**Vérifier que l'IP est bannie :**

```bash
sudo fail2ban-client status nginx-http-auth
```

**Résultat :**

```
Status for the jail: nginx-http-auth
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	5
|  `- File list:	/var/log/nginx/secure-error.log
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list:	192.168.1.50
```

**[OK] IP bannie pendant 1 heure !**

---

**Débannir manuellement (pour continuer les tests) :**

```bash
sudo fail2ban-client set nginx-http-auth unbanip 192.168.1.50
```

---

### ÉTAPE 9 : Durcir la configuration globale Nginx

**Éditer nginx.conf :**

```bash
sudo nano /etc/nginx/nginx.conf
```

**Ajouter/modifier dans le contexte `http {}` :**

```nginx
http {
    # ... (configuration existante)
    
    # ═══════════════════════════════════════════════════════════
    # SÉCURITÉ GLOBALE
    # ═══════════════════════════════════════════════════════════
    
    # Masquer version Nginx
    server_tokens off;
    
    # Désactiver les méthodes HTTP dangereuses globalement
    # (Complété par les règles dans les server blocks)
    
    # Limites globales
    client_body_timeout 10s;
    client_header_timeout 10s;
    send_timeout 10s;
    # Timeouts courts pour éviter Slowloris
    
    # Taille des buffers (limiter pour éviter overflow)
    client_header_buffer_size 1k;
    large_client_header_buffers 2 1k;
    
    # Désactiver autoindex (listing de répertoires)
    autoindex off;
    
    # ═══════════════════════════════════════════════════════════
    # OPTIMISATIONS (BONUS)
    # ═══════════════════════════════════════════════════════════
    
    # Compression Gzip
    gzip on;
    gzip_vary on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_min_length 1000;
    
    # Open File Cache (performance)
    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}
```

**Sauvegarde.**

---

**Tester et recharger :**

```bash
sudo nginx -t
sudo systemctl reload nginx
```

---

### ÉTAPE 10 : Audit de sécurité

**Installer Nikto (scanner de vulnérabilités) :**

```bash
sudo apt install nikto -y
```

---

**Scanner le site :**

```bash
nikto -h https://secure.local -ssl
```

**Nikto va tester :**
- Versions obsolètes
- Fichiers sensibles exposés
- Vulnérabilités connues
- Configurations dangereuses

**Analyser les résultats et corriger si nécessaire.**

---

**Tester les headers de sécurité :**

```bash
curl -Ik https://secure.local
```

**Vérifier que TOUS les headers sont présents :**

[OK] `Strict-Transport-Security`
[OK] `X-Frame-Options`
[OK] `X-Content-Type-Options`
[OK] `X-XSS-Protection`
[OK] `Content-Security-Policy`
[OK] `Referrer-Policy`
[OK] `Permissions-Policy`

---

**Test SSL avec OpenSSL :**

```bash
openssl s_client -connect secure.local:443 -tls1_2 < /dev/null 2>&1 | grep "Protocol"
# Doit retourner : Protocol  : TLSv1.2

openssl s_client -connect secure.local:443 -tls1_3 < /dev/null 2>&1 | grep "Protocol"
# Doit retourner : Protocol  : TLSv1.3

openssl s_client -connect secure.local:443 -tls1 < /dev/null 2>&1 | grep -i "error"
# Doit retourner : error (TLS 1.0 refusé)
```

---

### [OK] CHECKLIST DE SÉCURITÉ FINALE

**Configuration Nginx :**
- [ ] `server_tokens off`
- [ ] Timeouts configurés
- [ ] `autoindex off`
- [ ] Limites de buffers définies

**SSL/TLS :**
- [ ] HTTPS activé
- [ ] TLS 1.2 et 1.3 uniquement
- [ ] Ciphers modernes
- [ ] Session cache configuré
- [ ] OCSP Stapling activé

**Headers de sécurité :**
- [ ] HSTS avec preload
- [ ] X-Frame-Options: DENY
- [ ] X-Content-Type-Options: nosniff
- [ ] X-XSS-Protection: 1; mode=block
- [ ] Content-Security-Policy (strict)
- [ ] Referrer-Policy
- [ ] Permissions-Policy
- [ ] Cross-Origin Policies

**Authentification :**
- [ ] HTTP Basic configuré
- [ ] Fichier .htpasswd sécurisé (640)
- [ ] Mots de passe forts hashés

**Restrictions d'accès :**
- [ ] IP whitelisting sur zone admin
- [ ] `satisfy all` (auth ET IP)
- [ ] Méthodes HTTP dangereuses bloquées

**Rate Limiting :**
- [ ] Zone générale (10 req/s)
- [ ] Zone admin (2 req/s)
- [ ] Zone login (1 req/min)
- [ ] Limite connexions simultanées (10)

**Protection fichiers :**
- [ ] Fichiers cachés bloqués
- [ ] Fichiers sensibles bloqués (.bak, .sql, etc.)
- [ ] Scripts dans /uploads bloqués

**Protection injections :**
- [ ] Patterns SQL bloqués
- [ ] Patterns XSS bloqués
- [ ] ModSecurity + OWASP CRS activés

**Anti-bots :**
- [ ] User-Agents malveillants bloqués
- [ ] Fail2Ban configuré

**Monitoring :**
- [ ] Logs détaillés
- [ ] Format de log personnalisé
- [ ] Fail2Ban surveille les logs

**Audit :**
- [ ] Scan Nikto effectué
- [ ] Vulnérabilités corrigées
- [ ] Headers testés
- [ ] SSL testé

---

## [COURS] CONCLUSION DE L'EXERCICE 5

**[BRAVO] BRAVO ! Tu maîtrises maintenant la sécurisation Nginx au niveau expert ! [BRAVO]**

**Ce que tu as appris :**
- Authentification HTTP Basic avec restrictions IP
- Tous les headers de sécurité modernes
- Rate limiting multi-niveaux
- ModSecurity WAF + OWASP CRS
- Fail2Ban anti brute-force
- Durcissement configuration Nginx
- Protection contre injections SQL/XSS
- Blocage bots et scanners
- Audit de sécurité complet

**Compétences acquises :**
- [OK] Sécurisation Nginx (niveau expert)
- [OK] ModSecurity WAF
- [OK] Rate limiting avancé
- [OK] Conformité OWASP Top 10
- [OK] Fail2Ban et détection intrusions
- [OK] Audit de sécurité

**Temps moyen de réalisation :** 3-5 heures

---

---

# [COURS] CONCLUSION GÉNÉRALE DES 5 EXERCICES NGINX

## [TROPHEE] FÉLICITATIONS !

**Tu as terminé les 5 exercices Nginx avec succès !**

Tu es maintenant capable de :
- [OK] Créer et gérer des sites web statiques et dynamiques avec Nginx
- [OK] Sécuriser avec HTTPS et optimiser SSL/TLS
- [OK] Développer des applications PHP avec MySQL et PHP-FPM
- [OK] Configurer des reverse proxies et load balancers haute performance
- [OK] Sécuriser complètement un serveur Nginx (niveau production)

**Niveau atteint : EXPERT NGINX ! [RAPIDE]**

## [GRAPHIQUE] RÉCAPITULATIF DES COMPÉTENCES

| Exercice | Niveau | Compétences clés |
|----------|--------|------------------|
| 1 - Portfolio | [VERT] Débutant | Server blocks, try_files, fichiers statiques |
| 2 - HTTPS | [JAUNE] Intermédiaire | SSL/TLS, HTTP/2, headers sécurité, performance SSL |
| 3 - Blog PHP | [JAUNE] Intermédiaire | PHP-FPM, communication Nginx<->PHP, optimisation pools |
| 4 - Reverse Proxy | [ROUGE] Avancé | Load balancing, failover, keepalive, upstream |
| 5 - Sécurisation | [ROUGE] Expert | WAF, rate limiting, Fail2Ban, conformité OWASP |

## [OBJECTIF] NGINX VS APACHE - VERDICT FINAL

**Après ces 10 exercices (5 Apache + 5 Nginx), tu as découvert que :**

| Critère | Gagnant | Raison |
|---------|---------|--------|
| **Performance** | * Nginx | Architecture événementielle 10x plus efficace |
| **Concurrence** | * Nginx | 10,000+ connexions vs 1,000-2,000 |
| **Mémoire** | * Nginx | 2.5 KB/connexion vs 5-10 MB |
| **Reverse Proxy** | * Nginx | Conçu pour ça, ultra performant |
| **Fichiers statiques** | * Nginx | 2-3x plus rapide qu'Apache |
| **PHP** | Égalité | PHP-FPM fonctionne bien avec les deux |
| **Configuration** | Nginx | Plus simple et concise |
| **Flexibilité** | Apache | .htaccess distribué (mais moins performant) |
| **Modules** | Apache | Plus de modules disponibles |
| **Documentation** | Apache | Plus ancienne et fournie |

**Recommandation :**

- **Sites statiques / SPA** : Nginx 100%
- **Reverse Proxy / Load Balancer** : Nginx 100%
- **API REST haute performance** : Nginx
- **Site dynamique PHP classique** : Les deux (léger avantage Nginx)
- **Applications legacy complexes** : Apache (si besoin .htaccess)

**En 2024 : Nginx est le choix majoritaire (34% de part de marché)**

## [RAPIDE] PROCHAINES ÉTAPES

**Pour continuer ta progression :**

1. **Projets réels** : Déployer un vrai site en production avec Nginx
2. **Certifications** : LPIC-2, LFCS (Linux Foundation)
3. **Technologies complémentaires** :
   - Docker + Nginx (containerisation)
   - Kubernetes + Nginx Ingress Controller
   - Nginx Plus (version commerciale avec fonctionnalités avancées)
4. **Cloud** : AWS ALB, Azure Application Gateway, GCP Load Balancer
5. **Monitoring** : Prometheus + Grafana pour Nginx
6. **CI/CD** : GitLab CI/CD, GitHub Actions avec déploiement Nginx

**Bon courage dans ta carrière de DevOps/SysAdmin ! [FORCE]**

**Tu maîtrises maintenant Apache ET Nginx au niveau expert ! [TROPHEE]**

---

**FIN DES EXERCICES NGINX**

═══════════════════════════════════════════════════════════════