Module 2 — Nginx + TLS opérationnel

Servir HTTPS proprement : vhost isolé, Let's Encrypt automatisé, headers de sécurité, pièges du default_server, rate-limiting.

🏗️ Architecture vhost — un fichier par site

Sur Debian/Ubuntu, nginx propose un pattern sites-available / sites-enabled qu'il faut suivre pour rester maintenable.

/etc/nginx/
├── nginx.conf              # config globale
├── sites-available/        # définitions vhosts (toutes)
│   ├── default
│   ├── formation-odoo
│   └── formation-secu
└── sites-enabled/          # symlinks vers ceux qui sont actifs
    ├── default → ../sites-available/default
    └── formation-odoo → ../sites-available/formation-odoo

Activer / désactiver un site

# Activer
sudo ln -s /etc/nginx/sites-available/mon-site /etc/nginx/sites-enabled/

# Désactiver (le fichier source reste disponible)
sudo rm /etc/nginx/sites-enabled/mon-site

# Tester + reload (TOUJOURS dans cet ordre)
sudo nginx -t && sudo systemctl reload nginx
JAMAIS reload sans nginx -t d'abord

Si la config a une erreur de syntaxe, nginx -t vous l'indique avant qu'elle ne casse votre nginx. Sans le test, vous risquez de couper tous vos sites jusqu'à correction.

⚠️ Le piège du default_server

Dans un nginx avec plusieurs vhosts, il y a TOUJOURS un vhost qui répond aux requêtes dont le server_name ne matche aucun bloc explicite. C'est le default_server.

Cas pédagogique — réel vécu sur le VPS Ezway

On déploie un nouveau site formation.odoo.ezway.technology avec :

Symptôme : on accède au domaine et... page "Welcome to nginx" par défaut. Pourquoi ?

Diagnostic : Certbot, lors d'un premier passage, a injecté les directives SSL dans le fichier /etc/nginx/sites-available/default (déjà activé), et pas dans notre vhost. Du coup, le default matche notre server_name avant nous, et sert /var/www/html/index.nginx-debian.html.

Résolution

# 1. Désactiver le default (le symlink, le fichier source reste)
sudo rm /etc/nginx/sites-enabled/default

# 2. Vérifier que NOTRE vhost a bien les blocs 80 ET 443
sudo grep -E "listen|server_name" /etc/nginx/sites-available/formation-odoo

# 3. Si le bloc 443 manque (Certbot a fait son job ailleurs), relancer
sudo certbot install --cert-name formation.odoo.ezway.technology

# 4. Reload
sudo nginx -t && sudo systemctl reload nginx

# 5. Tester
curl -I https://formation.odoo.ezway.technology/
Best practice : désactiver le default

Sur un VPS multi-sites, désactivez systématiquement le default. Mieux vaut un 404 propre sur un domaine non configuré qu'un risque de fuite (welcome page, ou pire, mauvais site servi).

🔒 Vhost type — sécurisé par défaut

Voici le template à reprendre pour tout nouveau site statique HTTPS :

server {
    listen 80;
    listen [::]:80;
    server_name mon-site.example.com;

    # Permettre les challenges ACME pour Certbot
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
        default_type "text/plain";
    }

    # Tout le reste : redirect vers HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name mon-site.example.com;

    ssl_certificate     /etc/letsencrypt/live/mon-site.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mon-site.example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    root /var/www/mon-site;
    index index.html;

    # Headers de sécurité
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    # Content Security Policy à adapter selon votre app
    # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline';" always;

    # Cacher la version nginx
    server_tokens off;

    # Limites
    client_max_body_size 5M;

    location / {
        try_files $uri $uri/ $uri.html =404;
    }

    # Bloquer fichiers cachés (sauf .well-known)
    location ~ /\.(?!well-known) {
        deny all;
        access_log off;
        log_not_found off;
    }

    # Logs séparés (essentiel pour le multi-sites)
    access_log /var/log/nginx/mon-site.access.log;
    error_log  /var/log/nginx/mon-site.error.log;

    # Compression
    gzip on;
    gzip_types text/css application/json application/javascript text/xml application/xml image/svg+xml;
}

📜 Headers de sécurité — détails

HeaderRôleValeur type
Strict-Transport-SecurityForce HTTPS pendant N secondes (cache navigateur)max-age=31536000; includeSubDomains
X-Content-Type-OptionsEmpêche MIME sniffing (exécution JS dans fichier servi en text/plain)nosniff
X-Frame-OptionsEmpêche clickjacking (iframe d'un autre site sur votre page)SAMEORIGIN ou DENY
Referrer-PolicyContrôle ce qui est envoyé dans le header Refererstrict-origin-when-cross-origin
Content-Security-PolicyWhitelist sources scripts/styles/img/iframes — anti-XSSdefault-src 'self'; ...
Permissions-PolicyDésactive features browser (camera, mic, geoloc) non utiliséescamera=(), microphone=(), geolocation=()
⚠️
CSP — le plus utile mais le plus chiant

Content-Security-Policy est la défense XSS la plus efficace, mais aussi la plus dure à configurer (chaque CDN, chaque libairie externe doit être whitelistée). Commencez en mode Report-Only pour voir ce qui casse avant d'appliquer en bloquant.

Tester votre conf sécu

🔐 Let's Encrypt avec Certbot

Installation

sudo apt install certbot python3-certbot-nginx -y

Émission d'un certificat

# Option A : Certbot configure nginx automatiquement (plus simple)
sudo certbot --nginx -d mon-site.example.com

# Option B : juste émettre, on configure manuellement le bloc 443 (plus contrôlé)
sudo certbot certonly --nginx -d mon-site.example.com

Renouvellement automatique

Certbot installe automatiquement un timer systemd (/etc/systemd/system/timers.target.wants/certbot.timer) qui tente le renouvellement 2× / jour. Les certs ne sont renouvelés que s'ils expirent dans moins de 30 jours.

# Vérifier
sudo systemctl status certbot.timer

# Tester le renouvellement (dry-run)
sudo certbot renew --dry-run

Cert wildcard via DNS-01

Pour couvrir *.zoho.ezway.technology avec un seul cert, il faut le challenge DNS-01 (HTTP-01 ne supporte pas les wildcards) :

# Via Cloudflare (exemple)
sudo apt install python3-certbot-dns-cloudflare -y

# Créer le fichier credentials
echo "dns_cloudflare_api_token = VOTRE_TOKEN_API" | sudo tee /etc/letsencrypt/cloudflare.ini
sudo chmod 600 /etc/letsencrypt/cloudflare.ini

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d "*.zoho.ezway.technology" \
  -d "zoho.ezway.technology"
💡
Rate limit Let's Encrypt

50 certificats / semaine / registered domain. Si vous multipliez les sous-domaines en série, attention au plafond. Solution : wildcard (1 cert pour tous les sous-domaines) ou utiliser --staging pour les tests (ne consomme pas le quota prod).

🚧 Rate-limiting et Basic Auth

Rate-limiting pour bloquer scrappeurs / brute-force

# Dans http {} (nginx.conf) ou un fichier dans conf.d/
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=60r/m;

# Dans le vhost :
location /wp-login.php {
    limit_req zone=auth_limit burst=3 nodelay;
    # ...
}

location /api/ {
    limit_req zone=api_limit burst=20;
    # ...
}

Basic Auth — protéger une page admin/staging

# Installer htpasswd
sudo apt install apache2-utils -y

# Créer le fichier de credentials
sudo htpasswd -c /etc/nginx/.htpasswd francois
# (entrer un password fort)

# Ajouter d'autres users
sudo htpasswd /etc/nginx/.htpasswd autre_user

# Dans le vhost
location /admin/ {
    auth_basic "Admin Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
}
⚠️
Basic Auth ≠ vraie sécurité

Basic Auth envoie le password en base64 à chaque requête. C'est OK uniquement en HTTPS. Et c'est une protection pénible (pas de logout, pas de 2FA). Utilisez pour du staging/temporaire, jamais pour un vrai login utilisateur final.

🔗 Ressources

📋 Quiz de validation

← Module 1 — Linux Module 3 — Secrets →