L'ancienne fiche datait de Debian 11 et s'arrêtait à une liste de commandes : rien sur les clés SSH, le pare-feu, fail2ban, les mises à jour automatiques ni la sauvegarde. Elle portait aussi deux erreurs qui mordent — une locale écrite avec un underscore, et l'ordre des noms sur la ligne 127.0.1.1 qui décide de ce que renvoie « hostname -f ». La nouvelle page couvre les quinze étapes, du DNS à la sauvegarde testée, avec les pièges propres à Trixie : sources deb822, SSH activé par socket, /tmp en tmpfs. Elle reste générique — aucun service particulier n'y est déployé. Deux extensions Markdown sont activées pour elle : les listes de tâches (la checklist récapitulative) et mermaid (l'ordre des opérations). Aucune dépendance nouvelle : le thème Material embarque déjà mermaid. Les deux sont documentées dans la page Markdown du wiki. L'adresse change, donc l'ancienne reste servie par une page de renvoi. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BE4rvHVETRoWnYNDTGssdo
43 KiB
| description |
|---|
| Préparer un serveur Debian 13 « Trixie » livré par un hébergeur — DNS, nom d'hôte, sources deb822, comptes, SSH par clé, pare-feu, fail2ban, mises à jour automatiques, supervision et sauvegarde. |
Préparation d'un serveur Debian 13 (Trixie)
!!! info "État du document" Rédigé le 19/09/2026 pour Debian 13 « Trixie ». Il remplace l'ancienne fiche Debian 11 : ce qui a changé est résumé en fin de page.
Avant de commencer
Cette procédure part d'une installation minimale de Debian 13 « Trixie » fraîchement livrée par un hébergeur, et s'arrête au moment où le serveur est prêt à recevoir ses services. Comptez 30 à 45 minutes.
Règle d'or : tant que vous touchez à SSH ou au pare-feu, gardez une deuxième session SSH ouverte sur le serveur et ne la fermez jamais. Elle est votre filet de sécurité si une modification vous verrouille dehors.
Vérifiez avant de commencer que vous disposez d'un accès de secours hors SSH : console KVM/VNC, console série ou mode rescue de l'hébergeur. Sans lui, une erreur de pare-feu signifie une réinstallation.
Variables utilisées dans ce document
Remplacez systématiquement ces valeurs par les vôtres.
| Variable | Exemple | Signification |
|---|---|---|
SRV |
web01 |
Nom court du serveur (sans domaine) |
FQDN |
web01.exemple.fr |
Nom complet, celui du DNS et du reverse |
IPV4 |
203.0.113.10 |
Adresse IPv4 publique |
IPV6 |
2001:db8::10 |
Adresse IPv6 publique, si fournie |
USER |
alix |
Votre futur compte d'administration |
VENDOR |
debian |
Compte créé par l'hébergeur, à neutraliser |
SSHPORT |
2222 |
Port SSH final, si vous le déplacez |
Ordre des opérations
L'ordre compte : le DNS se propage pendant que vous travaillez, et le pare-feu ne se ferme qu'une fois le nouvel accès SSH prouvé.
flowchart LR
A[DNS + reverse] --> B[Identité<br/>hostname, hosts]
B --> C[Système<br/>MAJ, paquets, locale]
C --> D[Compte admin<br/>+ sudo]
D --> E[SSH<br/>clés + durcissement]
E --> F[Pare-feu<br/>+ fail2ban]
F --> G[Automatismes<br/>MAJ, supervision]
G --> H[Sauvegarde<br/>+ instantané]
Étape 1 — DNS direct et reverse DNS
Faites cette étape en premier : la propagation DNS prend de quelques minutes à quelques heures, et elle tournera pendant que vous configurez le reste. Un reverse DNS cohérent conditionne l'acceptation de vos mails et la lisibilité de vos journaux.
1.1 Enregistrements directs (zone DNS du domaine)
Dans l'interface DNS de votre registrar ou de votre hébergeur, créez :
| Type | Nom | Valeur | TTL |
|---|---|---|---|
A |
web01 |
203.0.113.10 |
300 pendant la mise en place, puis 3600 |
AAAA |
web01 |
2001:db8::10 |
idem, si IPv6 fournie |
Un TTL court (300 s) pendant l'installation vous laisse corriger une erreur sans attendre. Remontez-le une fois le serveur stabilisé.
1.2 Reverse DNS (PTR)
Le PTR ne se déclare pas dans la zone du domaine, mais chez le propriétaire de l'adresse IP, c'est-à-dire votre hébergeur. Cherchez dans son panneau une entrée nommée « reverse », « rDNS » ou « PTR », sur la fiche de l'IP et non sur celle du domaine.
La valeur doit être exactement le FQDN déclaré en 1.1 : web01.exemple.fr. Faites-le pour l'IPv4 et pour l'IPv6 ; un reverse IPv6 manquant est une cause classique de mails rejetés.
1.3 Vérification
Depuis votre poste, une fois la propagation faite :
dig +short web01.exemple.fr A
dig +short web01.exemple.fr AAAA
dig +short -x 203.0.113.10
dig +short -x 2001:db8::10
Les quatre réponses doivent se refermer sur elles-mêmes : le A pointe vers l'IP, le PTR de cette IP renvoie le même FQDN. C'est ce qu'on appelle un FCrDNS valide (forward-confirmed reverse DNS).
Si
dign'est pas installé sur votre poste :sudo apt install dnsutilssous Debian/Ubuntu,brew install bindsous macOS.
Étape 2 — Première connexion
Connectez-vous avec le compte fourni par l'hébergeur. Sur les images Debian officielles, c'est debian ; d'autres utilisent admin, ubuntu ou root.
ssh debian@203.0.113.10
Utilisez l'IP et non le FQDN pour cette première connexion : le DNS n'est peut-être pas encore propagé.
Vérifier ce qu'on a vraiment reçu
Avant toute modification, prenez trois minutes pour constater l'état initial.
cat /etc/os-release # confirme Debian 13 (trixie)
uname -r # version du noyau
ip -brief address # interfaces et adresses
lsblk # disques et partitions
free -h # RAM et swap
sudo systemctl list-units --type=service --state=running
sudo ss -tulpn # ce qui écoute déjà sur le réseau
La dernière commande est la plus instructive : elle montre les services exposés dès la livraison. Tout ce qui écoute sur 0.0.0.0 ou :: et que vous ne reconnaissez pas mérite une décision explicite — désactiver ou garder.
Si la connexion est refusée
| Symptôme | Cause fréquente |
|---|---|
Permission denied (publickey) |
La clé publique n'a pas été injectée à la commande, ou mauvais nom d'utilisateur |
Connection refused |
Le serveur n'a pas fini de démarrer, ou SSH écoute sur un autre port |
Connection timed out |
Pare-feu amont côté hébergeur (security group, firewall network) |
REMOTE HOST IDENTIFICATION HAS CHANGED |
Réinstallation ou réattribution d'IP → ssh-keygen -R 203.0.113.10 |
Étape 3 — Nom d'hôte et /etc/hosts
3.1 Définir le nom d'hôte
Debian attend ici le nom court, pas le FQDN. Le FQDN est reconstitué via /etc/hosts.
sudo hostnamectl set-hostname web01
Vous pouvez aussi renseigner deux champs purement informatifs, utiles quand on gère plusieurs machines :
sudo hostnamectl set-hostname "Front web — Paris" --pretty
sudo hostnamectl set-chassis vm # ou: server, desktop
3.2 Corriger /etc/hosts
C'est ce fichier, et non hostnamectl, qui donne au système son FQDN. La règle : sur la ligne 127.0.1.1, le FQDN vient en premier, le nom court ensuite.
sudo nano /etc/hosts
Ciblez ce résultat :
127.0.0.1 localhost
127.0.1.1 web01.exemple.fr web01
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
Certains hébergeurs pré-remplissent la ligne 127.0.1.1 avec le nom d'usine de la machine. Deux options : le supprimer, ou le laisser après vos deux entrées — la ligne compte alors quatre références.
127.0.1.1 web01.exemple.fr web01 vps-1a2b3c.exemple-hebergeur.net vps-1a2b3c
Les deux formes fonctionnent. Supprimer l'ancien nom est plus propre ; le conserver évite de casser un script de l'hébergeur qui s'y référerait encore.
⚠️ Ne touchez jamais à la ligne
127.0.0.1 localhost. Et n'associez pas le FQDN à l'IP publique dans/etc/hosts: sur une machine à IP dynamique ou derrière NAT, cela crée des résolutions fantomatiques.
3.3 Vérifier
hostname # → web01
hostname -f # → web01.exemple.fr
hostname -d # → exemple.fr
hostnamectl status
Si hostname -f répond autre chose que le FQDN attendu, la ligne 127.0.1.1 est mal ordonnée. Le changement est immédiat, mais reconnectez-vous pour que l'invite de commande l'affiche.
Sur certaines images cloud,
cloud-initréécrit le nom d'hôte à chaque redémarrage. Si votre nom revient à sa valeur d'origine après unreboot:sudo sed -i 's/^preserve_hostname:.*/preserve_hostname: true/' /etc/cloud/cloud.cfg grep preserve_hostname /etc/cloud/cloud.cfgAjoutez la ligne si elle est absente.
Étape 4 — Sources APT et mise à jour
4.1 Passer les sources au format deb822
Debian 13 introduit le format deb822 pour les dépôts. L'ancien format une-ligne est officiellement déprécié, même s'il restera supporté par Debian 14 « Forky » (détail du changement). Autant partir sur le bon format tout de suite.
sudo apt modernize-sources
La commande convertit /etc/apt/sources.list vers /etc/apt/sources.list.d/debian.sources et laisse au passage des sauvegardes /etc/apt/sources.list.bak et /etc/apt/sources.list.save, ce qui permet de revenir en arrière (procédure détaillée).
Vérifiez le résultat :
cat /etc/apt/sources.list.d/debian.sources
Vous devez y trouver trois blocs : trixie, trixie-updates et trixie-security, chacun avec un champ Signed-By.
Types: deb
URIs: http://deb.debian.org/debian/
Suites: trixie trixie-updates
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb
URIs: http://security.debian.org/debian-security/
Suites: trixie-security
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Si le dépôt
trixie-securitymanque, ajoutez-le à la main : sans lui, vous ne recevez aucun correctif de sécurité. C'est l'erreur la plus coûteuse de toute cette procédure.
non-free-firmware est nécessaire sur matériel physique (cartes réseau, RAID). contrib et non-free sont optionnels ; ne les ajoutez que si un paquet précis l'exige.
4.2 Mettre à jour
sudo apt update
sudo apt full-upgrade -y
sudo apt autoremove --purge -y
sudo apt clean
full-upgrade et non upgrade : le premier accepte d'installer ou de retirer des paquets quand une dépendance a changé, ce qui est exactement ce qu'on veut sur une machine neuve. upgrade bloquerait sur ces cas.
4.3 Redémarrer si le noyau a changé
ls /var/run/reboot-required 2>/dev/null && echo "Redémarrage nécessaire"
sudo reboot
Après redémarrage, reconnectez-vous et vérifiez que tout est remonté :
systemctl is-system-running # attendu : running
systemctl --failed # attendu : 0 loaded units listed
uptime
Un état degraded signale au moins un service en échec ; systemctl --failed vous dit lequel.
Étape 5 — Paquets de base
sudo apt install -y \
vnstat iftop htop nano git \
curl wget ca-certificates gnupg \
dnsutils net-tools lsof tcpdump \
rsync tree zip unzip \
ncdu sysstat needrestart
| Paquet | À quoi il sert |
|---|---|
vnstat |
Compteur de trafic réseau historisé, léger (vnstat -d, vnstat -m) |
iftop |
Trafic en temps réel par connexion |
htop |
Processus, CPU, mémoire, en interactif |
ncdu |
Trouve ce qui remplit le disque, en navigation |
sysstat |
sar, iostat, pidstat : historique de charge et d'E/S |
needrestart |
Signale après chaque apt les services à redémarrer |
dnsutils |
dig, nslookup pour déboguer la résolution |
lsof / tcpdump |
Qui tient un fichier, que passe-t-il sur le réseau |
Activer vnstat
sudo systemctl enable --now vnstat
vnstat --iflist # vérifie l'interface détectée
Il faut attendre quelques heures avant que vnstat -d affiche des données utiles : le compteur démarre à zéro.
Ce que vous ne voulez probablement pas
Sur un serveur, allez au plus maigre. Chaque paquet installé est une surface d'attaque et une mise à jour de plus. Évitez les méta-paquets task-*, les environnements de bureau, et tout serveur (apache2, nginx, mariadb) tant que vous n'êtes pas à l'étape de déploiement des services.
sudo apt-mark showmanual | head -50 # ce qui a été installé explicitement
Étape 6 — Locale, fuseau horaire et clavier
6.1 Locale
Attention à l'orthographe : c'est fr_FR.UTF-8 avec un tiret, pas fr_FR.UTF_8. Un underscore produit une locale invalide, silencieusement ignorée.
Générez d'abord la locale, puis déclarez-la comme défaut système :
sudo sed -i 's/^# *\(fr_FR.UTF-8\)/\1/' /etc/locale.gen
sudo locale-gen
sudo localectl set-locale LANG=fr_FR.UTF-8
Variante non interactive, adaptée à un script :
echo "locales locales/locales_to_be_generated multiselect fr_FR.UTF-8 UTF-8, en_US.UTF-8 UTF-8" | sudo debconf-set-selections
echo "locales locales/default_environment_locale select fr_FR.UTF-8" | sudo debconf-set-selections
sudo rm -f /etc/locale.gen
sudo dpkg-reconfigure --frontend=noninteractive locales
LANG=fr_FR.UTF-8seul dans un terminal ne change que la session en cours.localectlécrit dans/etc/default/locale, lu par tous les services et toutes les futures connexions.
Recommandation : gardez les messages en anglais même avec une interface en français. Les messages d'erreur anglais sont cherchables et correspondent à la documentation ; les scripts qui parsent une sortie de commande cassent en français.
sudo localectl set-locale LANG=fr_FR.UTF-8 LC_MESSAGES=C.UTF-8
Reconnectez-vous, puis vérifiez :
locale # aucune ligne "Cannot set LC_*"
localectl status
6.2 Fuseau horaire
sudo timedatectl set-timezone Europe/Paris
timedatectl list-timezones | grep Paris # pour trouver un autre fuseau
Sur un parc international ou pour simplifier la lecture des journaux entre machines,
UTCreste un choix défendable. Le seul vrai critère : que toutes vos machines aient le même fuseau.
6.3 Synchronisation de l'heure
Debian 13 utilise systemd-timesyncd par défaut. Une heure décalée casse TLS, les tokens et la corrélation des journaux.
sudo timedatectl set-ntp true
timedatectl status
Attendu : System clock synchronized: yes et NTP service: active.
Pour utiliser les serveurs français :
sudo mkdir -p /etc/systemd/timesyncd.conf.d
printf '[Time]\nNTP=0.fr.pool.ntp.org 1.fr.pool.ntp.org\nFallbackNTP=2.fr.pool.ntp.org 3.fr.pool.ntp.org\n' \
| sudo tee /etc/systemd/timesyncd.conf.d/pool-fr.conf
sudo systemctl restart systemd-timesyncd
timedatectl timesync-status
6.4 Clavier (console uniquement)
Utile seulement si vous passez par la console KVM de l'hébergeur ; sans effet sur SSH.
sudo localectl set-keymap fr
sudo localectl set-x11-keymap fr
Étape 7 — Compte administrateur et sudo
7.1 Créer l'utilisateur
sudo adduser alix
Choisissez un mot de passe long et unique. Les champs « Nom complet », « N° de bureau », etc. peuvent rester vides : validez avec Entrée.
7.2 Accorder les droits sudo
Sous Debian, l'appartenance au groupe sudo suffit. Nul besoin d'éditer les sudoers pour un administrateur standard.
sudo usermod -aG sudo alix
id alix # vérifie que "sudo" apparaît dans les groupes
Le
-adeusermod -aGest obligatoire : sans lui, l'utilisateur est retiré de tous ses autres groupes. C'est une erreur classique et difficile à diagnostiquer.
Si vous avez besoin d'une règle spécifique — par exemple autoriser un redémarrage de service sans mot de passe — n'éditez jamais /etc/sudoers directement. Créez un fichier dédié dans /etc/sudoers.d/, ce qui isole la panne en cas d'erreur de syntaxe :
sudo visudo -f /etc/sudoers.d/10-alix
# Redémarrage de nginx sans mot de passe
alix ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
sudo chmod 0440 /etc/sudoers.d/10-alix
sudo visudo -c # contrôle global de la syntaxe
⚠️
visudovalide la syntaxe avant d'écrire, ce quenano /etc/sudoersne fait pas. Une erreur dans les sudoers non validée rendsudoinutilisable pour tout le monde, y compris vous.Évitez
NOPASSWD: ALL: cela transforme toute exécution de code sous votre compte en accès root immédiat.
7.3 Tester avant de couper quoi que ce soit
Depuis une nouvelle session (en gardant l'ancienne ouverte) :
ssh alix@203.0.113.10
sudo -v # doit demander VOTRE mot de passe et rendre la main
sudo whoami # → root
Tant que ces trois commandes ne fonctionnent pas, n'allez pas plus loin : les étapes suivantes retirent votre accès actuel.
7.4 Confort de connexion (optionnel)
# Depuis votre poste : alias de connexion
cat >> ~/.ssh/config <<'EOF'
Host web01
HostName web01.exemple.fr
User alix
Port 22
IdentityFile ~/.ssh/id_ed25519
EOF
Vous vous connecterez ensuite avec ssh web01. Adaptez Port après l'étape 8 si vous le déplacez.
Étape 8 — Clés SSH et durcissement
C'est l'étape la plus risquée. Gardez une session ouverte du début à la fin.
8.1 Créer une paire de clés (sur votre poste, pas sur le serveur)
ssh-keygen -t ed25519 -C "alix@poste-bureau" -f ~/.ssh/id_ed25519
Ed25519 est le bon choix par défaut : court, rapide, sûr. Mettez une passphrase — elle protège la clé si votre poste est compromis, et ssh-agent évite de la retaper.
8.2 Déposer la clé publique
ssh-copy-id -i ~/.ssh/id_ed25519.pub alix@203.0.113.10
En manuel si ssh-copy-id n'est pas disponible :
ssh alix@203.0.113.10 "mkdir -p ~/.ssh && chmod 700 ~/.ssh"
cat ~/.ssh/id_ed25519.pub | ssh alix@203.0.113.10 "cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Testez dans une nouvelle session : ssh alix@203.0.113.10 doit passer sans demander le mot de passe du compte.
8.3 Configurer sshd par fichier drop-in
Debian 13 lit /etc/ssh/sshd_config.d/*.conf. Écrivez votre configuration là plutôt que dans sshd_config : vos choix survivent aux mises à jour du paquet et se retirent en supprimant un seul fichier.
sudo nano /etc/ssh/sshd_config.d/99-durcissement.conf
# Authentification
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
# Limiter qui peut se connecter
AllowUsers alix
# Réduire la surface
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
PermitUserEnvironment no
# Sessions mortes
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers alixremplace avantageusement unDenyUsers debian: la liste blanche est explicite et couvre aussi les comptes que vous n'avez pas anticipés. Pensez à l'étendre si vous ajoutez un administrateur.
PasswordAuthentication novous verrouille dehors si la clé de 8.2 ne fonctionne pas. Ne l'activez qu'après avoir vérifié la connexion par clé.
Deux précautions : le premier fichier qui définit une directive gagne, et sshd_config charge souvent ses drop-ins en première ligne. Vérifiez donc la configuration réellement appliquée, jamais le fichier seul.
sudo sshd -t # syntaxe ; silence = OK
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauth|allowusers|^port'
Puis rechargez et testez depuis une nouvelle session :
sudo systemctl reload ssh
8.4 Changer le port : la spécificité Debian 13
Depuis Debian 12, SSH est démarré par activation à la demande via systemd (ssh.socket). Conséquence : une directive Port dans sshd_config peut rester sans effet, car c'est le socket systemd qui décide de l'écoute.
D'abord, constatez votre situation :
systemctl is-active ssh.socket # active → c'est le socket qui commande
systemctl is-active ssh.service
ss -tlpn | grep -E 'ssh|:22'
Si ssh.socket est actif, modifiez le socket :
sudo systemctl edit ssh.socket
[Socket]
ListenStream=
ListenStream=0.0.0.0:2222
ListenStream=[::]:2222
La ligne ListenStream= vide est indispensable : elle efface le port 22 hérité. Sans elle, vous ajoutez 2222 sans retirer 22.
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
ss -tlpn | grep 2222
Alternative — revenir au modèle classique, où sshd_config reprend la main :
sudo systemctl disable --now ssh.socket
sudo systemctl enable --now ssh.service
8.5 Faut-il vraiment changer de port ?
Ce n'est pas une mesure de sécurité, seulement de réduction du bruit : elle élimine les scans automatisés des journaux, pas un attaquant ciblé qui scannera vos 65 535 ports. Avec PasswordAuthentication no et fail2ban, le port 22 est déjà très défendable. Un port non standard complique en revanche le pare-feu, les sondes de supervision et les collaborateurs.
8.6 Vérification finale
Ouvrez une session neuve, sans fermer l'ancienne :
ssh -p 2222 alix@web01.exemple.fr
ssh -p 2222 debian@web01.exemple.fr # doit être refusé
ssh -p 2222 root@web01.exemple.fr # doit être refusé
Étape 9 — Neutraliser le compte fournisseur
Le compte debian livré par l'hébergeur est connu de tous et ciblé en premier par les scans. Une fois votre accès personnel prouvé, retirez-lui l'accès SSH.
9.1 Choisir le niveau de neutralisation
| Niveau | Commande | Effet | Quand |
|---|---|---|---|
| SSH bloqué | AllowUsers alix (étape 8.3) |
Le compte existe, ne peut plus se connecter par SSH | Cas général |
| Mot de passe verrouillé | sudo passwd -l debian |
Plus d'authentification par mot de passe | À combiner avec le précédent |
| Shell retiré | sudo usermod -s /usr/sbin/nologin debian |
Plus aucune session interactive | Compte purement technique |
| Compte supprimé | sudo deluser --remove-home debian |
Disparu | Seulement si rien ne dépend de lui |
La combinaison recommandée est la plus conservatrice :
sudo passwd -l debian
sudo usermod -s /usr/sbin/nologin debian
sudo rm -f /home/debian/.ssh/authorized_keys
⚠️ Ne supprimez pas le compte avant d'avoir vérifié qu'aucun processus ne tourne sous son identité (
ps -u debian) et qu'il n'est pas utilisé par l'outillage de l'hébergeur (agent de sauvegarde,cloud-init, console de secours). Certains hébergeurs recréent le compte à chaque redémarrage.
9.2 Le cas de root
sudo passwd -l root # verrouille le mot de passe root
sudo rm -f /root/.ssh/authorized_keys
Sur Debian, root n'a généralement pas de mot de passe utilisable en SSH dès lors que PermitRootLogin no est posé. Le verrouillage est une ceinture en plus des bretelles.
N'utilisez pas
usermod -s /usr/sbin/nologin root: vous perdriez l'accès par la console de secours de l'hébergeur, votre dernier recours.
9.3 Inventaire des comptes
# Comptes capables de se connecter
awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $7}' /etc/passwd
# Membres du groupe sudo
getent group sudo
# Comptes sans mot de passe (doit être vide)
sudo awk -F: '$2 == "" {print $1}' /etc/shadow
# Clés SSH autorisées sur toute la machine
sudo find /home /root -name authorized_keys -exec ls -l {} \; -exec cat {} \;
La dernière commande mérite un passage attentif : une clé publique inconnue dans un authorized_keys est une porte dérobée.
Étape 10 — Pare-feu
Debian 13 utilise nftables comme moteur de filtrage. Deux approches : nftables directement, ou ufw qui l'utilise en coulisse avec une syntaxe plus simple.
10.1 Option A — ufw (recommandé si vous débutez)
sudo apt install -y ufw
# Politique par défaut : tout entrant bloqué, tout sortant autorisé
sudo ufw default deny incoming
sudo ufw default allow outgoing
# SSH EN PREMIER — sinon vous vous coupez l'accès à l'activation
sudo ufw limit 2222/tcp comment 'SSH'
# Services web, à n'ouvrir que le jour où vous les déployez
# sudo ufw allow 80/tcp comment 'HTTP'
# sudo ufw allow 443/tcp comment 'HTTPS'
sudo ufw enable
sudo ufw status verbose
limit plutôt que allow pour SSH : ufw bloque une IP qui dépasse six tentatives de connexion en 30 secondes. C'est une première barrière avant fail2ban.
10.2 Option B — nftables directement
Plus verbeux, mais sans couche intermédiaire et lisible d'un seul coup d'œil.
sudo nano /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif lo accept
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
tcp dport 2222 ct state new limit rate 6/minute accept
# tcp dport { 80, 443 } accept
counter
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
sudo nft -c -f /etc/nftables.conf # vérifie la syntaxe sans appliquer
sudo systemctl enable --now nftables
sudo nft list ruleset
⚠️ Ne laissez jamais
policy dropsurinputsans règle SSH au-dessus. Testez toujours avecnft -c -favant d'appliquer.
10.3 Filet de sécurité pendant les tests
Programmez une désactivation automatique du pare-feu dans 10 minutes. Si vous vous verrouillez dehors, l'accès revient seul ; si tout va bien, vous annulez la tâche.
# Avant d'activer les règles
sudo systemd-run --on-active=10min --unit=ufw-panic systemctl stop ufw
# Une fois la connexion reconfirmée depuis une nouvelle session
sudo systemctl stop ufw-panic.timer
10.4 Le pare-feu de l'hébergeur
Beaucoup d'hébergeurs interposent leur propre filtrage en amont (security group, « firewall network »). Un port ouvert sur la machine et fermé chez eux reste inaccessible — et l'inverse est vrai. Tenez les deux couches alignées, et documentez-les ensemble.
# Depuis un autre poste : ce qui est réellement joignable
nmap -Pn -p- web01.exemple.fr
Étape 11 — fail2ban
fail2ban lit les journaux et bannit temporairement les IP qui échouent trop souvent. Avec PasswordAuthentication no, son utilité sur SSH est surtout de nettoyer les journaux ; elle redevient essentielle dès que vous exposez un service authentifié par mot de passe.
sudo apt install -y fail2ban
Configuration
Ne modifiez jamais jail.conf : il est écrasé aux mises à jour. Créez jail.local.
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 4
backend = systemd
banaction = nftables-multiport
# Ne bannissez jamais votre propre IP fixe
ignoreip = 127.0.0.1/8 ::1 198.51.100.7
[sshd]
enabled = true
port = 2222
mode = aggressive
backend = systemd est important sous Debian 13 : les journaux passent par journald, pas par un fichier /var/log/auth.log garanti. banaction = nftables-multiport évite un conflit avec le moteur nftables du système.
Si vous avez choisi ufw à l'étape 10, utilisez plutôt
banaction = ufw.
sudo systemctl enable --now fail2ban
sudo systemctl restart fail2ban
Exploitation au quotidien
sudo fail2ban-client status # prisons actives
sudo fail2ban-client status sshd # IP bannies
sudo fail2ban-client set sshd unbanip 198.51.100.7 # débannir
sudo fail2ban-client set sshd banip 203.0.113.99 # bannir à la main
⚠️ Mettez votre IP fixe dans
ignoreipavant de démarrer le service. Sinon, une passphrase mal tapée quatre fois vous bannit une heure de votre propre serveur.
Étape 12 — Mises à jour de sécurité automatiques
Un serveur non mis à jour devient vulnérable en quelques semaines. unattended-upgrades applique les correctifs de sécurité sans intervention.
sudo apt install -y unattended-upgrades apt-listchanges
sudo dpkg-reconfigure -plow unattended-upgrades
Configuration
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
Lignes à vérifier ou décommenter :
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename},label=Debian-Security";
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};
Unattended-Upgrade::Mail "alix@exemple.fr";
Unattended-Upgrade::MailReport "on-change";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Le choix structurant est Automatic-Reboot :
| Valeur | Conséquence |
|---|---|
"false" |
Les correctifs noyau attendent un redémarrage manuel. Le serveur reste vulnérable en attendant, mais ne tombe jamais seul. |
"true" |
Redémarrage automatique à 04:00 si nécessaire. Correctifs réellement actifs, au prix d'une coupure imprévisible. |
Pour un serveur de production isolé, "true" avec une heure creuse est souvent le meilleur compromis. Pour un serveur critique sans redondance, gardez "false" et redémarrez lors d'une fenêtre planifiée.
Vérifier que ça tourne
sudo unattended-upgrade --dry-run --debug # simulation détaillée
systemctl list-timers | grep -E 'apt|unattended'
cat /var/log/unattended-upgrades/unattended-upgrades.log
Deux minuteries doivent apparaître : apt-daily.timer (rafraîchissement des index) et apt-daily-upgrade.timer (installation).
Savoir quand redémarrer
cat /var/run/reboot-required.pkgs 2>/dev/null # paquets concernés
sudo needrestart -b # services à relancer
Pensez à intégrer cette vérification à votre routine hebdomadaire.
Étape 13 — Swap, /tmp et paramètres noyau
13.1 Swap
Beaucoup de VPS sont livrés sans swap. Un peu de swap évite qu'un pic de mémoire déclenche le tueur de processus du noyau (OOM killer).
free -h # vérifier ce qui existe déjà
swapon --show
Si la ligne Swap est à zéro :
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Dimensionnement raisonnable : 2 Go jusqu'à 4 Go de RAM, puis 4 Go au-delà. Inutile d'aller au-delà sur un serveur : si vous swappez beaucoup, il faut de la RAM, pas du disque.
# Réduire l'agressivité du swap sur un serveur
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf
Sur SSD NVMe, le swap est peu coûteux. Sur un disque réseau facturé aux IOPS, il peut coûter cher et ralentir fortement.
13.2 /tmp est désormais en RAM
Changement notable de Debian 13 : /tmp est monté en tmpfs, donc en mémoire, et vidé à chaque redémarrage (note de version).
df -h /tmp
findmnt /tmp
Deux conséquences à connaître :
- Écrire un gros fichier dans
/tmpconsomme de la RAM. Les traitements volumineux doivent utiliser/var/tmp, qui reste sur disque. - Sur une machine migrée depuis Debian 12, les anciens fichiers de
/tmpsont toujours sur le disque, masqués par le montage. Ils occupent de l'espace invisible.
# Retrouver et nettoyer l'ancien /tmp masqué
sudo mkdir -p /mnt/racine
sudo mount --bind / /mnt/racine
ls -lha /mnt/racine/tmp/
# ... supprimez ce qui doit l'être ...
sudo umount /mnt/racine && sudo rmdir /mnt/racine
Pour repasser /tmp sur disque si votre usage le demande :
sudo systemctl mask tmp.mount
sudo reboot
13.3 Paramètres réseau et noyau
sudo nano /etc/sysctl.d/99-durcissement.conf
# Anti-usurpation d'adresse
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignorer le routage par la source et les redirections ICMP
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
# Journaliser les paquets manifestement usurpés
net.ipv4.conf.all.log_martians = 1
# Résistance aux SYN flood
net.ipv4.tcp_syncookies = 1
# Limiter la lecture des informations noyau
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
# Protection des liens en répertoires partagés
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
sudo sysctl --system
sudo sysctl -a --pattern 'tcp_syncookies|rp_filter' # contrôle
N'appliquez pas
net.ipv4.ip_forward = 0sans réfléchir : Docker, les conteneurs et les VPN en ont besoin.
Étape 14 — Journaux, supervision et alertes
14.1 Plafonner les journaux
Par défaut, journald peut occuper jusqu'à 10 % du système de fichiers. Sur un petit VPS, c'est une cause classique de disque plein.
sudo nano /etc/systemd/journald.conf.d/00-limites.conf
[Journal]
Storage=persistent
SystemMaxUse=500M
SystemMaxFileSize=50M
MaxRetentionSec=1month
sudo mkdir -p /etc/systemd/journald.conf.d
sudo systemctl restart systemd-journald
journalctl --disk-usage
14.2 Commandes de lecture utiles
journalctl -p err -b # erreurs depuis le démarrage
journalctl -u ssh --since "1 hour ago" # un service, une fenêtre de temps
journalctl -f # en direct
journalctl --since yesterday | grep -i "failed password"
last -20 # dernières connexions réussies
lastb -20 # dernières tentatives échouées
14.3 Recevoir les alertes par mail
Sans MTA, les rapports de unattended-upgrades, cron et fail2ban sont écrits mais jamais lus. Un relais léger suffit — n'installez pas un serveur de mail complet.
sudo apt install -y msmtp-mta bsd-mailx
sudo nano /etc/msmtprc
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile /var/log/msmtp.log
account default
host smtp.exemple.fr
port 587
from web01@exemple.fr
user web01@exemple.fr
password VOTRE_MOT_DE_PASSE
sudo chmod 600 /etc/msmtprc
echo "Test depuis web01" | mail -s "Test alerte" alix@exemple.fr
Le fichier contient un mot de passe en clair :
chmod 600est obligatoire. Préférez un mot de passe d'application dédié, révocable, plutôt que votre mot de passe principal.
Redirigez ensuite le courrier de root vers vous :
echo "root: alix@exemple.fr" | sudo tee -a /etc/aliases
sudo newaliases 2>/dev/null || true
14.4 Surveiller l'essentiel
# Trafic réseau
vnstat -d # par jour
vnstat -m # par mois, utile face à un quota
# Disque
df -h
sudo ncdu / # exploration interactive
# Charge et E/S
uptime
iostat -x 2 5
sar -u 1 5
Une alerte simple sur le disque, via cron, vaut mieux qu'une supervision compliquée jamais installée :
sudo tee /etc/cron.daily/alerte-disque >/dev/null <<'EOF'
#!/bin/sh
SEUIL=85
USAGE=$(df / --output=pcent | tail -1 | tr -dc '0-9')
[ "$USAGE" -ge "$SEUIL" ] && echo "Disque / à ${USAGE}% sur $(hostname -f)" \
| mail -s "ALERTE disque $(hostname)" root
exit 0
EOF
sudo chmod +x /etc/cron.daily/alerte-disque
Étape 15 — Sauvegarde et instantané de référence
15.1 Instantané immédiat
Vous venez de construire une base propre. Prenez-en un instantané (snapshot) chez votre hébergeur maintenant, avant de déployer le moindre service. C'est votre point de retour : une mauvaise manipulation plus tard se résoudra en trois minutes au lieu d'une réinstallation complète.
15.2 Archiver la configuration
sudo tar czf /root/config-initiale-$(date +%F).tar.gz \
/etc/ssh/sshd_config.d /etc/hosts /etc/hostname \
/etc/apt/sources.list.d /etc/sudoers.d \
/etc/fail2ban/jail.local /etc/nftables.conf \
/etc/sysctl.d /etc/default/locale 2>/dev/null
Récupérez l'archive sur votre poste :
scp -P 2222 alix@web01.exemple.fr:/root/config-initiale-*.tar.gz ./
Mieux encore : versionnez ces fichiers dans un dépôt Git privé — sans les secrets.
gitest déjà installé à l'étape 5.
15.3 Mettre en place une vraie sauvegarde
Une sauvegarde qu'on n'a jamais restaurée n'est pas une sauvegarde. Principe des 3-2-1 : trois copies, deux supports, une hors site.
sudo apt install -y restic
# Initialisation vers un stockage distant (une seule fois)
export RESTIC_REPOSITORY="sftp:sauvegarde@stockage.exemple.fr:/backups/web01"
export RESTIC_PASSWORD_FILE="/root/.restic-pass"
sudo restic init
# Sauvegarde
sudo restic backup /etc /home /var/www --exclude-caches
# Vérification et purge
sudo restic snapshots
sudo restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
Automatisez avec une minuterie systemd plutôt qu'un cron : les échecs remontent dans journalctl.
sudo systemd-run --on-calendar="*-*-* 03:00:00" --unit=sauvegarde \
/usr/bin/restic backup /etc /home /var/www
15.4 Tester la restauration
À faire une fois, tout de suite, puis tous les trimestres :
sudo restic restore latest --target /tmp/test-restauration --include /etc/hosts
cat /tmp/test-restauration/etc/hosts
Notez la date du dernier test réussi quelque part de visible. C'est la seule métrique qui compte vraiment.
Checklist récapitulative
Préparatifs
- Accès de secours hors SSH vérifié (console KVM, mode rescue)
- Enregistrement A (et AAAA) créé vers le FQDN
- Reverse DNS (PTR) aligné sur le FQDN, IPv4 et IPv6
digconfirme la correspondance dans les deux sens
Identité et système
- Première connexion réussie, état initial relevé (
ss -tulpn) - Nom d'hôte court défini avec
hostnamectl set-hostname /etc/hostscorrigé : FQDN avant nom court sur la ligne127.0.1.1hostname -frenvoie bien le FQDN- Sources APT converties en deb822,
trixie-securityprésent apt full-upgradepassé, machine redémarrée si le noyau a changésystemctl --failedne renvoie rien- Paquets de base installés,
vnstatactivé - Locale
fr_FR.UTF-8générée et définie (avec un tiret) - Fuseau horaire posé,
timedatectlindique l'horloge synchronisée
Accès
- Compte administrateur créé et ajouté au groupe
sudo(avec-aG) sudo -vetsudo whoamifonctionnent depuis le nouveau compte- Clé Ed25519 générée et déposée, connexion par clé vérifiée
- Drop-in
sshd_config.dcréé : root refusé, mot de passe désactivé,AllowUsersposé sudo sshd -Tconfirme la configuration réellement appliquée- Port SSH vérifié côté
ssh.socketsi vous l'avez changé - Compte fournisseur verrouillé, ses clés SSH retirées
- Aucune clé inconnue dans les
authorized_keysde la machine
Protection
- Pare-feu actif, SSH autorisé avant activation
- Ports ouverts alignés entre la machine et le pare-feu de l'hébergeur
- fail2ban actif, votre IP fixe dans
ignoreip unattended-upgradesactif, politique de redémarrage décidée- Paramètres sysctl appliqués (
sysctl --system) - Swap présent si la RAM est limitée
Exploitation
- Journaux plafonnés (
journalctl --disk-usage) - Envoi de mail testé, courrier de root redirigé
- Sauvegarde configurée et restauration testée une fois
- Instantané de référence pris chez l'hébergeur
- Archive de configuration récupérée hors du serveur
- TTL DNS remonté à sa valeur normale
Vérification finale d'un seul bloc
hostname -f; echo '---'
localectl status | head -3; echo '---'
timedatectl | grep -E 'Time zone|synchronized'; echo '---'
sudo sshd -T | grep -E '^(port|permitrootlogin|passwordauthentication|allowusers)'; echo '---'
sudo ufw status verbose 2>/dev/null || sudo nft list ruleset | head -20; echo '---'
sudo fail2ban-client status; echo '---'
systemctl --failed; echo '---'
free -h; df -h /
Ce qui change par rapport à l'ancienne fiche Debian 11
L'ancienne fiche couvrait l'essentiel de la mise en route. Voici ce qui y était inexact ou incomplet, et pourquoi c'est corrigé ici.
| Ancienne fiche | Correction | Pourquoi |
|---|---|---|
LANG=fr_FR.UTF_8 |
fr_FR.UTF-8 |
Tiret et non underscore ; sinon la locale est invalide. Et LANG= seul ne dure que le temps de la session : utilisez localectl set-locale |
Ordre dans /etc/hosts « avant les noms déjà renseignés » |
FQDN puis nom court, dans cet ordre | hostname -f prend le premier nom de la ligne. Inversé, il renvoie le nom court |
adduser nom |
sudo adduser nom |
La création d'utilisateur demande les privilèges root |
Droits supplémentaires via visudo |
usermod -aG sudo d'abord ; visudo -f /etc/sudoers.d/10-nom si besoin spécifique |
L'appartenance au groupe suffit dans 95 % des cas. Un fichier dédié isole une erreur de syntaxe au lieu de casser sudo pour tout le monde |
apt upgrade -y |
apt full-upgrade -y |
upgrade refuse d'installer ou de retirer des paquets, donc bloque sur des transitions courantes d'une machine neuve |
« Désactiver ssh pour l'utilisateur debian » |
AllowUsers en liste blanche + verrouillage du compte |
Une liste blanche couvre aussi les comptes non anticipés ; le verrouillage ferme les autres voies que SSH |
Ce que l'ancienne fiche ne couvrait pas
- Les clés SSH avant la désactivation du mot de passe. C'est la seule séquence qui ne pardonne pas l'inversion.
- Le pare-feu. Sans lui, chaque service déployé est exposé par défaut.
- Les mises à jour automatiques. Un serveur à jour le jour de l'installation ne l'est plus trois semaines après.
- La sauvegarde. Y compris le test de restauration, qui est la partie qu'on saute toujours.
Pièges spécifiques à Debian 13
| Piège | Symptôme | Solution |
|---|---|---|
| SSH en activation par socket | Port 2222 dans sshd_config ignoré, SSH reste sur 22 |
systemctl edit ssh.socket avec un ListenStream= vide puis le nouveau port (étape 8.4) |
/tmp en tmpfs |
Espace disque « disparu » après migration ; gros fichiers qui saturent la RAM | Nettoyer l'ancien /tmp masqué, utiliser /var/tmp pour le volumineux (étape 13.2) |
| Sources au format deb822 | Un script qui écrit dans /etc/apt/sources.list semble sans effet |
Écrire dans /etc/apt/sources.list.d/*.sources |
cloud-init |
Le nom d'hôte revient à sa valeur d'origine après redémarrage | preserve_hostname: true (étape 3.3) |