Configuration d'un nouveau VPS pour la production (pour débutant)
Aperçu
Cette compétence vous guide pas à pas dans la préparation d'un serveur Ubuntu vierge pour la production, de sorte qu'il puisse ensuite être maintenu le plus possible de manière autonome. Le public cible est une personne sans expérience en administration système. Par conséquent :
- Des réglages par défaut sécurisés, pas « comme un pro ». Pas de NOPASSWD-sudo, pas de vérifications désactivées pour le confort. Ce qui simplifie la vie d'un expert est un piège pour un débutant.
- Autonomie grâce à l'automatisation. Les mises à jour de sécurité, les sauvegardes et la surveillance doivent fonctionner toutes seules. L'humain ne fait que réagir aux alertes.
- Chaque étape est vérifiée. Après une modification, vérification du résultat, et seulement ensuite passage à l'étape suivante.
OS cible : Ubuntu 22.04 / 24.04 LTS. Les commandes pour d'autres distributions diffèrent.
Règles d'or (avant TOUTE modification)
- Sauvegarde de la config :
sudo cp /path/config /path/config.bak.$(date +%F-%H%M) - Vérifie l'état :
sudo systemctl status <service>/sudo docker ps - Après une modification, vérifie le résultat (logs, statut, connexion de test).
- Ne ferme jamais la session SSH actuelle tant que dans une nouvelle session tu n'as pas vérifié que l'accès et sudo fonctionnent. Perdre SSH sur un serveur distant = se rendre à la console du fournisseur (VNC).
Principes de sécurité pour la prod (à respecter à toutes les étapes) :
- Les secrets (clés API, jetons, mots de passe) — uniquement dans
.envou les variables d'environnement, jamais dans le code. .env— obligatoirement dans.gitignore(sinon les secrets fuient dans git dès le premier commit).- Les bases de données ne sont pas exposées sur Internet — uniquement localhost ou via un réseau privé/VPN.
- Pas de mode debug sur le serveur :
DEBUG=False,NODE_ENV=production. - Les applications ne s'exécutent pas en tant que root — utilisateur/conteneur dédié.
- Pas de ports inutiles ouverts (admin, tableaux de bord, métriques — fermés ou derrière des IP de confiance).
- Ne désactive pas les vérifications de sécurité pour le confort (
verify=False,allowAll— uniquement en local). - SSH — uniquement par clés, pas par mot de passe.
0. Diagnostic — ce qui existe déjà
À exécuter EN PREMIER. Les hébergeurs fournissent souvent un serveur avec une configuration partielle (cloud-init, ufw, etc.).
# Système
lsb_release -a && uname -r
free -h && df -h && nproc
# Qui suis-je et quels sont mes droits
whoami && id
# Configuration SSH (valeurs effectives, pas seulement les fichiers !)
sudo sshd -T | grep -iE 'permitrootlogin|passwordauthentication|pubkeyauthentication|^port '
# Pare-feu
sudo ufw status verbose
# Quels ports écoutent
sudo ss -tlnp
# Mises à jour automatiques (activées ?)
systemctl status unattended-upgrades 2>/dev/null | head -5
cat /etc/apt/apt.conf.d/20auto-upgrades 2>/dev/null
# Docker / fail2ban
sudo docker ps 2>/dev/null
sudo fail2ban-client status 2>/dev/null
Après le diagnostic, informe l'utilisateur de ce qui a été trouvé et ne commence pas la configuration sans confirmation.
1. Configuration initiale et sécurité
Consulte la section 0 — une partie a peut-être déjà été faite. Ne fais pas deux fois la même chose.
1.1 Mise à jour du système
sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y
# Si le noyau a été mis à jour, un redémarrage sera nécessaire (voir la sortie). Redémarre de manière réfléchie :
# sudo reboot # et reconnecte-toi après ~30-60s
1.2 Utilisateur sudo dédié (NE PAS travailler en tant que root)
sudo adduser <USERNAME> # définira un mot de passe — choisis-en un robuste, conserve-le dans un gestionnaire de mots de passe
sudo usermod -aG sudo <USERNAME>
Pour un débutant, on laisse sudo avec demande de mot de passe. C'est intentionnel : NOPASSWD-sudo supprime la dernière barrière en cas de compromission de la clé ou de la session. Le mot de passe sudo est aussi nécessaire comme solution de repli si la clé SSH est perdue (voir 1.3).
1.3 Clés SSH (connexion sans mot de passe par clé)
Type de clé — ed25519 (pas RSA). Nom — <server>_<device>, avec une phrase de passe.
Sur la machine locale :
ssh-keygen -t ed25519 -f ~/.ssh/<server>_<device> -C "<server>_<device>"
ssh-copy-id -i ~/.ssh/<server>_<device>.pub <USERNAME>@<SERVER_IP>
Vérifie la connexion par clé dans une session SÉPARÉE (ne ferme pas l'ancienne !) :
ssh -i ~/.ssh/<server>_<device> -o IdentitiesOnly=yes <USERNAME>@<SERVER_IP> 'whoami; sudo whoami'
Alias pratique dans ~/.ssh/config sur la machine locale :
Host <alias>
HostName <SERVER_IP>
User <USERNAME>
IdentityFile ~/.ssh/<server>_<device>
IdentitiesOnly yes
1.4 Durcissement SSH
Crée /etc/ssh/sshd_config.d/99-hardening.conf :
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
AllowTcpForwarding local
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers <USERNAME>
AllowTcpForwarding localautorise le forwarding local (ssh -L— pour accéder à un service sur127.0.0.1avant la configuration du domaine) et interdit le forwarding distant (-R). Si le forwarding n'est pas du tout nécessaire — metsno.
Applique et vérifie :
sudo sshd -t # syntaxe OK ?
sudo systemctl restart ssh
sudo sshd -T | grep -iE 'permitroot|passwordauth' # valeurs effectives = no
Gotcha 1 — cloud-init écrase le durcissement. Le fichier /etc/ssh/sshd_config.d/50-cloud-init.conf contient souvent PasswordAuthentication yes et dans sshd_config.d/ c'est « le premier qui correspond gagne ». Vérifie via sudo sshd -T (valeurs effectives), pas seulement ton fichier. Si ça écrase — corrige/supprime la ligne dans 50-cloud-init.conf.
Gotcha 2 — Ubuntu 24.04 utilise ssh.socket. Port dans sshd_config est ignoré. Pour changer le port SSH : sudo systemctl edit ssh.socket → ajouter ListenStream=<PORT> (et supprimer le port par défaut). Vérifie via la console du fournisseur — une erreur ici verrouille l'accès.
1.5 Mises à jour automatiques de sécurité ⭐ (cœur de l'autonomie)
C'est l'étape la plus importante pour que « le serveur se gère tout seul ». Sans cela, les failles dans les paquets s'accumulent.
sudo apt install unattended-upgrades apt-listchanges -y
sudo dpkg-reconfigure -plow unattended-upgrades # choisis "Oui" – crée 20auto-upgrades
Vérifie /etc/apt/apt.conf.d/20auto-upgrades :
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
Dans /etc/apt/apt.conf.d/50unattended-upgrades (décommente/configure les lignes clés) :
// Installer les mises à jour de sécurité (activé par défaut) :
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Remove-Unused-Dependencies "true";
// Redémarrage automatique si la mise à jour le nécessite (noyau). Choisis une fenêtre de charge minimale :
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Décision « redémarrage automatique » :
true= le serveur redémarre tout seul la nuit lors d'une mise à jour du noyau (plus sûr, mais possible courte indisponibilité).false= le noyau est mis à jour, mais le redémarrage t'attend (nécessite une surveillance). Pour un serveur de production unique d'un débutant,true+ fenêtre de nuit est généralement mieux.
Vérification — exécution à blanc sans installation :
sudo unattended-upgrade --dry-run --debug 2>&1 | tail -20
systemctl list-timers 'apt-daily*' # les minuteurs sont-ils actifs ?
1.6 fail2ban (protection SSH contre le brute force)
La configuration de base suffit — ne complique pas avec recidive/nftables au début.
sudo apt install fail2ban -y
/etc/fail2ban/jail.d/sshd.local :
[sshd]
enabled = true
bantime = 1h
findtime = 30m
maxretry = 4
# Ne pas se bannir soi-même : ajoute TES IP statiques (trouve-les : curl ifconfig.me)
ignoreip = 127.0.0.1/8 ::1 <YOUR_HOME_IP>
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
1.7 Fuseau horaire et (pour les petits VPS) swap
sudo timedatectl set-timezone <Region/City> # ex. Europe/Paris ou UTC
# Swap nécessaire sur les VPS avec peu de RAM (< 2 Go) pour éviter que OOM ne tue les services :
free -h
sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swap.conf && sudo sysctl --system
2. Pare-feu UFW
Principe : tout est fermé, seul le nécessaire est ouvert. Autorise d'abord SSH, sinon tu te bloqueras toi-même.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH' # IMPORTANT : avant enable !
sudo ufw enable
sudo ufw status verbose
Pour un serveur web, ajoute HTTP/HTTPS :
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
Interfaces d'admin/tableaux de bord/métriques — uniquement aux IP de confiance, pas à « tout le monde » :
sudo ufw allow from <TRUSTED_IP> to any port <ADMIN_PORT> proto tcp comment 'admin panel'
N'utilise jamais
sudo ufw reset— cela réinitialisera toutes les règles, y compris SSH.
3. Docker (optionnel — si les applications sont dans des conteneurs)
Installation
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker <USERNAME> # reconnecte-toi pour que le groupe soit appliqué
L'appartenance au groupe
dockeréquivaut en fait à root (viadocker run -v /:/host). C'est acceptable pour un seul administrateur, mais n'accorde pas ce groupe à des utilisateurs auxquels tu ne fais pas entièrement confiance pour l'accès au serveur.
Rotation des logs (obligatoire — sinon les logs remplissent le disque)
/etc/docker/daemon.json :
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
sudo systemctl restart docker
⚠️ Docker contourne UFW
Un port exposé avec -p 8080:8080 est accessible depuis Internet, même si UFW ne l'a pas autorisé.
Options sécurisées :
- Lier uniquement à localhost :
-p 127.0.0.1:8080:8080(accès uniquement via nginx/tunnel SSH). - Ne pas exposer les ports de la BDD du tout — communiquer par nom de service dans le réseau docker.
4. nginx + HTTPS (optionnel — si application web et domaine)
nginx comme proxy inverse devant l'application (l'application écoute sur 127.0.0.1, à l'extérieur — seulement nginx).
sudo apt install nginx -y
/etc/nginx/sites-available/<app> :
server {
listen 80;
server_name <DOMAIN>;
location / {
proxy_pass http://127.0.0.1:<APP_PORT>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
sudo ln -s /etc/nginx/sites-available/<app> /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
HTTPS gratuitement via Let's Encrypt (certbot émet et configure le renouvellement automatique) :
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d <DOMAIN>
# Le renouvellement automatique est déjà configuré par un minuteur. Vérification :
sudo certbot renew --dry-run
Protection : ajoute server_tokens off; dans /etc/nginx/nginx.conf (cacher la version de nginx) et les en-têtes de sécurité (Strict-Transport-Security, X-Content-Type-Options nosniff).
5. Sauvegardes automatiques (restic + hors site)
Instruction complète (installation, dépôt, script, minuteur, restauration) : lis references/backups.md — c'est détaillé pas à pas.
Principes clés à NE PAS enfreindre :
- La sauvegarde doit être hors site. Une copie sur le même serveur (ou disque) ne sauve pas en cas de perte du serveur/disque. Minimum — stockage externe (compatible S3 : Backblaze B2, rsync.net, etc.).
- Le mot de passe de chiffrement du dépôt est stocké HORS du serveur (gestionnaire de mots de passe). Perdre le mot de passe → toutes les sauvegardes sont définitivement illisibles. C'est le moyen le plus courant de se retrouver sans sauvegarde.
- Une sauvegarde sans test de restauration n'est pas une sauvegarde. Au moins une fois, fais une restauration de test (voir backups.md, « Restauration d'entraînement »).
- Automatisation : exécution quotidienne via un minuteur systemd + rotation des anciens snapshots.
Quoi sauvegarder
Pense par catégories, et non par chemins individuels — ainsi tu n'oublieras rien :
| Catégorie | Exemples de chemins | Pourquoi |
|---|---|---|
| Configs système | /etc entier | nginx, ssh, ufw, fail2ban, unités systemd — pour remonter le serveur « comme avant » |
| Données d'application | /home/<USERNAME>/app/data, /var/lib/<service> | ce qui ne peut pas être réinstallé : contenu utilisateur, téléchargements |
| Dumps de BDD | /var/backups/*.sql.gz | dump via pg_dump/.backup avant restic (le fichier BDD vivant peut être corrompu) |
| Secrets | .env, /root/.config/* | sont sauvegardés chiffrés (restic chiffre le dépôt). Mot de passe de chiffrement — hors du serveur |
Ce qu'il NE FAUT PAS sauvegarder (se réinstalle, ne fait que gonfler la sauvegarde) :
node_modules, .venv/venv, images Docker (docker pull), paquets système (apt), caches, /var/log, /tmp. Ces chemins sont déjà dans --exclude du script (voir backups.md).
Combien de temps conserver (rétention) — choisis une politique
restic stocke des snapshots dédupliqués, donc « plus de copies » coûte peu en espace.
Commande : restic forget --prune --keep-daily N --keep-weekly N --keep-monthly N.
| Politique | Commande | Pour qui | Justification |
|---|---|---|---|
| Basique (défaut) | --keep-daily 7 --keep-weekly 4 --keep-monthly 6 | La plupart des serveurs de production | Retour sur n'importe lequel des 7 derniers jours + historique jusqu'à six mois. Capture à la fois une erreur récente et « quand est-ce que ça s'est cassé ». Déjà présent dans backups.md. |
| Légère | --keep-daily 7 --keep-weekly 4 | Sites/bots sans données cumulatives précieuses | ~1 mois d'historique. Minimum d'espace, plus simple. Adapté si la perte de données anciennes n'est pas critique. |
| Longue | --keep-daily 14 --keep-weekly 8 --keep-monthly 12 | BDD avec données importantes, finances, obligations légales | Un an d'historique + 2 semaines par jour. Plus coûteux en espace, mais protège contre une corruption « silencieuse » des données, non détectée immédiatement. |
Recommandation pour un débutant : commence par la Basique — elle couvre les deux scénarios (retour rapide et historique long) pour un coût de stockage dérisoire. Passer à la Longue est possible à tout moment, en changeant simplement les nombres dans
forget— les anciens snapshots ne sont pas perdus rétroactivement.
Important pour les BDD : la rétention est inutile si tu sauvegardes le fichier BDD « vivant » — il peut être corrompu. Fais toujours d'abord un dump (
pg_dump/ SQLite.backup), et restic sauvegarde le dump.
6. Surveillance
...
[texte tronqué pour la traduction]