Chapitre 16.3 — Checklist de mise en prod
⏱️ TL;DR — La liste complète à dérouler avant, pendant et après un go-live, pour ne laisser aucun trou. Elle rassemble tout le cours en cases à cocher, par domaine : serveur durci, réseau/DNS, web + HTTPS, application en service, base non exposée, sauvegardes testées, observabilité, déploiement réversible, revue de sécurité finale. Coche au fur et à mesure. Le jour où tu ouvres au public, chaque case décochée est un incident en puissance.
🎯 Objectifs
- Mettre en prod sans rien oublier : sécurité, HTTPS, redémarrage auto, sauvegardes, monitoring.
- Distinguer ce qui doit être fait avant d’ouvrir au public de ce qui se vérifie après.
- Refuser le « ça marche donc c’est bon » : un site accessible n’est pas un site en prod.
Comment utiliser cette checklist
Déroule-la dans l’ordre, de haut en bas — elle suit la logique du cours (on empile les couches). Adapte à ton projet (tout ne s’applique pas partout), mais justifie chaque case que tu sautes. Les cases marquées (bloquant) ne se négocient pas avant d’ouvrir au public.
⚠️ Piège — Le plus grand piège du go-live : croire qu’un site joignable est un site en prod. Sans HTTPS, sans redémarrage auto, sans pare-feu, sans sauvegarde testée, sans monitoring, c’est une panne ou une fuite qui attend son heure. Cette liste existe pour transformer « ça tourne » en « ça tient ».
🖥️ Serveur
- Système à jour :
sudo apt update && sudo apt full-upgrade(bloquant) - Utilisateur non-root de travail créé (ex.
deploy), avecsudo(bloquant) - Connexion root en SSH désactivée (
PermitRootLogin no) (bloquant) - SSH par clés uniquement (
PasswordAuthentication no), clé testée dans un 2e terminal (bloquant) -
sshd_configdurci :AllowUsers deploy, éventuellementPortnon standard ;sudo sshd -tOK - Pare-feu UFW actif :
default deny incoming, seuls SSH + 80/443 ouverts (bloquant) - fail2ban installé et actif (protège SSH du bruteforce)
- Mises à jour de sécurité automatiques (
unattended-upgrades) activées - Swap en place (1–2 Go sur un petit VPS) et persistant dans
/etc/fstab - Fuseau horaire et NTP réglés (
timedatectl) — logs et cron à la bonne heure - Hostname propre (
hostnamectl)
🌐 Réseau & DNS
- Enregistrement A (et AAAA si IPv6) pointant vers l’IP du VPS (bloquant)
-
wwwgéré (redirection ou enregistrement dédié) - Propagation DNS vérifiée :
dig +short formacampus.frrenvoie la bonne IP - Seuls les ports nécessaires sont ouverts (vérifié depuis l’extérieur, pas juste
ufw status) - Port 80 ouvert (nécessaire au challenge Certbot, même si tout est en HTTPS)
- Reverse DNS / SPF-DKIM-DMARC si le serveur envoie des mails (sinon spam)
🔒 Web & HTTPS
- Nginx installé,
sudo nginx -tpasse (bloquant) - Server block configuré, site activé (symlink dans
sites-enabled/) - HTTPS actif via Let’s Encrypt (
certbot --nginx) (bloquant) - Renouvellement automatique du certificat vérifié :
certbot renew --dry-runOK (bloquant) - Redirection HTTP → HTTPS en place (301) (bloquant)
- HSTS activé (
Strict-Transport-Security) - En-têtes de sécurité posés (
X-Content-Type-Options,X-Frame-Options/ CSP,Referrer-Policy) - Compression gzip (ou brotli) activée
-
client_max_body_sizeréglé sur la taille max légitime des uploads - Note SSL Labs vérifiée (viser A / A+), pas de TLS obsolète
- La bannière de version Nginx masquée (
server_tokens off)
⚙️ Application
- Build de production (pas le mode dev) :
NODE_ENV=production,next build, cache Symfony réchauffé (bloquant) - App lancée en service systemd, avec
enable(survit au reboot) (bloquant) -
Restart=always(ouon-failure) : l’app remonte après un crash (bloquant) - App qui écoute en local (
127.0.0.1:3000), jamais exposée directement (bloquant) - App tourne sous un utilisateur non-root (moindre privilège) (bloquant)
- Variables d’env / secrets hors du dépôt git, dans un
.enven600(bloquant) - Migrations de base jouées, dans le bon ordre du déploiement
-
curl -I http://127.0.0.1:3000répond 200 (l’app va bien, indépendamment de Nginx) - Health-check applicatif exposé (ex.
/health) pour le monitoring
🗄️ Base de données
- Base non exposée à Internet : écoute en local,
pg_hba.confrestrictif (bloquant) - Port DB (5432 / 3306) fermé au pare-feu / non routé (bloquant)
- Utilisateur dédié à l’app (pas le superuser), droits limités à sa base (bloquant)
- Mot de passe fort et unique, stocké hors git (bloquant)
- Encodage / locale corrects (UTF-8)
- Réglages mémoire de base adaptés à la RAM du VPS
💾 Sauvegardes
- Sauvegardes automatisées (cron / timer), pas « à la main quand j’y pense » (bloquant)
- Règle 3-2-1 respectée : 3 copies, 2 supports, 1 hors-site (bloquant)
- La base est dumpée régulièrement (
pg_dump/mysqldump), pas juste les fichiers - Sauvegardes chiffrées (restic / borg) — surtout l’offsite (bloquant)
- Restauration TESTÉE de bout en bout — une sauvegarde non testée n’existe pas (bloquant)
- Rétention définie (ex. 7 quotidiennes, 4 hebdo, 6 mensuelles) et purge automatique
- Runbook de reprise écrit : comment reconstruire le serveur de zéro
📈 Observabilité
- Logs centralisés et consultables (
journalctl,/var/log) - logrotate configuré (logs applicatifs + Nginx) — sinon disque plein garanti (bloquant)
- Logs Docker plafonnés (
max-size) si Docker - Monitoring d’uptime externe (Uptime Kuma, healthchecks) qui teste le site de l’extérieur (bloquant)
- Alertes configurées et reçues (test réel) sur : site down, disque à plus de 80 %, certificat proche expiration (bloquant)
- Métriques machine surveillées (CPU, RAM, disque, load)
- Supervision applicative des erreurs (ex. Sentry) si pertinent
🚀 Déploiement
- Déploiement reproductible : un
deploy.sh(set -euo pipefail) ou un pipeline CI/CD (bloquant) - Build/test en CI (pas sur le petit VPS) pour éviter l’OOM au build
- Déploiement par releases + symlink
current(ou équivalent) pour un rollback instantané (bloquant) - Procédure de rollback testée (revenir à la release précédente en une commande)
- Anciennes releases purgées automatiquement (sinon disque plein)
- Secrets CI dans le coffre du fournisseur (GitHub Actions secrets), jamais en clair
- Déploiement sans downtime vérifié (health-check avant bascule)
🛡️ Sécurité — revue finale
-
sudo ss -tulpn: aucun service inattendu n’écoute sur0.0.0.0(bloquant) - Aucun secret dans le dépôt git (historique compris) (bloquant)
- Permissions saines : secrets en
600, pas de777, bon propriétaire partout - Pages/outils d’admin protégés (auth, IP allow-list) ou non exposés
-
fail2banet UFW confirmés actifs après le dernier reboot - Un reboot de test effectué : tout remonte tout seul (services
enable, swap, pare-feu) (bloquant) - Accès de secours connu : console du fournisseur testée (au cas où SSH lâche)
🔒 Sécurité — La ligne qui résume la revue finale : rien qui écoute sur
0.0.0.0sauf ce qui doit l’être (Nginx sur 80/443, SSH). Une base, un cache, un dashboard qui traînent exposés sont la faille la plus courante.sudo ss -tulpnte le dit en une commande — fais-le avant d’annoncer le go-live.
🧭 Sur FormaCampus — Avant d’ouvrir
formacampus.frau public, l’équipe déroule toute cette liste : VPS durci, DNS pointé, frontformacampus-webet API en servicesenable, Nginx + HTTPS auto-renouvelé, Postgres injoignable de l’extérieur, sauvegardes restaurées pour de vrai sur un serveur de test, Uptime Kuma qui alerte,deploy.shavec rollback. Puis un reboot de contrôle : tout remonte seul. Alors seulement, on annonce.
💡 Réflexe — Fais de cette checklist un fichier vivant dans ton dépôt (
GO-LIVE.md). À chaque nouveau projet, tu la copies, tu coches, tu l’adaptes. Une checklist qu’on rejoue est une checklist qui s’améliore à chaque incident évité.
Il te reste le vocabulaire, un dernier quiz de synthèse et la suite du voyage : 16.4 — Glossaire, quiz & suite.