---
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é.
```mermaid
flowchart LR
A[DNS + reverse] --> B[Identité
hostname, hosts]
B --> C[Système
MAJ, paquets, locale]
C --> D[Compte admin
+ sudo]
D --> E[SSH
clés + durcissement]
E --> F[Pare-feu
+ fail2ban]
F --> G[Automatismes
MAJ, supervision]
G --> H[Sauvegarde
+ 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 :
```bash
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 `dig` n'est pas installé sur votre poste : `sudo apt install dnsutils` sous Debian/Ubuntu, `brew install bind` sous 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`.
```bash
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.
```bash
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`.
```bash
sudo hostnamectl set-hostname web01
```
Vous pouvez aussi renseigner deux champs purement informatifs, utiles quand on gère plusieurs machines :
```bash
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**.
```bash
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
```bash
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-init` réécrit le nom d'hôte à chaque redémarrage. Si votre nom revient à sa valeur d'origine après un `reboot` :
>
> ```bash
> sudo sed -i 's/^preserve_hostname:.*/preserve_hostname: true/' /etc/cloud/cloud.cfg
> grep preserve_hostname /etc/cloud/cloud.cfg
> ```
>
> Ajoutez 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](https://dev.to/r3m8/debian-13-trixie-adoptez-le-nouveau-format-deb822-1ml6)). Autant partir sur le bon format tout de suite.
```bash
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](https://ostechnix.com/migrate-to-deb822-format-debian-13-trixie/)).
Vérifiez le résultat :
```bash
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-security` manque, 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
```bash
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é
```bash
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é :
```bash
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
```bash
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
```bash
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.
```bash
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 :
```bash
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 :
```bash
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-8` seul 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.
```bash
sudo localectl set-locale LANG=fr_FR.UTF-8 LC_MESSAGES=C.UTF-8
```
Reconnectez-vous, puis vérifiez :
```bash
locale # aucune ligne "Cannot set LC_*"
localectl status
```
### 6.2 Fuseau horaire
```bash
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, `UTC` reste 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.
```bash
sudo timedatectl set-ntp true
timedatectl status
```
Attendu : `System clock synchronized: yes` et `NTP service: active`.
Pour utiliser les serveurs français :
```bash
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.
```bash
sudo localectl set-keymap fr
sudo localectl set-x11-keymap fr
```
## Étape 7 — Compte administrateur et sudo
### 7.1 Créer l'utilisateur
```bash
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.
```bash
sudo usermod -aG sudo alix
id alix # vérifie que "sudo" apparaît dans les groupes
```
> Le `-a` de `usermod -aG` est 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 :
```bash
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
```
```bash
sudo chmod 0440 /etc/sudoers.d/10-alix
sudo visudo -c # contrôle global de la syntaxe
```
> ⚠️ `visudo` valide la syntaxe avant d'écrire, ce que `nano /etc/sudoers` ne fait pas. Une erreur dans les sudoers non validée rend `sudo` inutilisable 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) :
```bash
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)
```bash
# 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)
```bash
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
```bash
ssh-copy-id -i ~/.ssh/id_ed25519.pub alix@203.0.113.10
```
En manuel si `ssh-copy-id` n'est pas disponible :
```bash
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.
```bash
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 alix` remplace avantageusement un `DenyUsers 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 no` vous 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.
```bash
sudo sshd -t # syntaxe ; silence = OK
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauth|allowusers|^port'
```
Puis rechargez et testez depuis une nouvelle session :
```bash
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 :
```bash
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 :
```bash
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.
```bash
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 :
```bash
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 :
```bash
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 :
```bash
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
```bash
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
```bash
# 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)
```bash
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.
```bash
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;
}
}
```
```bash
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 drop` sur `input` sans règle SSH au-dessus. Testez toujours avec `nft -c -f` avant 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.
```bash
# 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.
```bash
# 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.
```bash
sudo apt install -y fail2ban
```
### Configuration
Ne modifiez jamais `jail.conf` : il est écrasé aux mises à jour. Créez `jail.local`.
```bash
sudo nano /etc/fail2ban/jail.local
```
```ini
[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`.
```bash
sudo systemctl enable --now fail2ban
sudo systemctl restart fail2ban
```
### Exploitation au quotidien
```bash
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 `ignoreip` **avant** 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.
```bash
sudo apt install -y unattended-upgrades apt-listchanges
sudo dpkg-reconfigure -plow unattended-upgrades
```
### Configuration
```bash
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
```bash
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
```bash
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).
```bash
free -h # vérifier ce qui existe déjà
swapon --show
```
Si la ligne Swap est à zéro :
```bash
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.
```bash
# 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](https://www.debian.org/releases/trixie/release-notes/issues.en.html)).
```bash
df -h /tmp
findmnt /tmp
```
Deux conséquences à connaître :
1. Écrire un gros fichier dans `/tmp` consomme de la RAM. Les traitements volumineux doivent utiliser `/var/tmp`, qui reste sur disque.
2. Sur une machine migrée depuis Debian 12, les anciens fichiers de `/tmp` sont toujours sur le disque, masqués par le montage. Ils occupent de l'espace invisible.
```bash
# 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 :
```bash
sudo systemctl mask tmp.mount
sudo reboot
```
### 13.3 Paramètres réseau et noyau
```bash
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
```
```bash
sudo sysctl --system
sudo sysctl -a --pattern 'tcp_syncookies|rp_filter' # contrôle
```
> N'appliquez pas `net.ipv4.ip_forward = 0` sans 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.
```bash
sudo nano /etc/systemd/journald.conf.d/00-limites.conf
```
```
[Journal]
Storage=persistent
SystemMaxUse=500M
SystemMaxFileSize=50M
MaxRetentionSec=1month
```
```bash
sudo mkdir -p /etc/systemd/journald.conf.d
sudo systemctl restart systemd-journald
journalctl --disk-usage
```
### 14.2 Commandes de lecture utiles
```bash
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.
```bash
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
```
```bash
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 600` est 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 :
```bash
echo "root: alix@exemple.fr" | sudo tee -a /etc/aliases
sudo newaliases 2>/dev/null || true
```
### 14.4 Surveiller l'essentiel
```bash
# 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 :
```bash
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
```bash
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 :
```bash
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. `git` est 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.
```bash
sudo apt install -y restic
```
```bash
# 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`.
```bash
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 :
```bash
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
- [ ] `dig` confirme 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/hosts` corrigé : FQDN avant nom court sur la ligne `127.0.1.1`
- [ ] `hostname -f` renvoie bien le FQDN
- [ ] Sources APT converties en deb822, `trixie-security` présent
- [ ] `apt full-upgrade` passé, machine redémarrée si le noyau a changé
- [ ] `systemctl --failed` ne renvoie rien
- [ ] Paquets de base installés, `vnstat` activé
- [ ] Locale `fr_FR.UTF-8` générée et définie (avec un tiret)
- [ ] Fuseau horaire posé, `timedatectl` indique l'horloge synchronisée
### Accès
- [ ] Compte administrateur créé et ajouté au groupe `sudo` (avec `-aG`)
- [ ] `sudo -v` et `sudo whoami` fonctionnent depuis le nouveau compte
- [ ] Clé Ed25519 générée et déposée, connexion par clé vérifiée
- [ ] Drop-in `sshd_config.d` créé : root refusé, mot de passe désactivé, `AllowUsers` posé
- [ ] `sudo sshd -T` confirme la configuration réellement appliquée
- [ ] Port SSH vérifié côté `ssh.socket` si vous l'avez changé
- [ ] Compte fournisseur verrouillé, ses clés SSH retirées
- [ ] Aucune clé inconnue dans les `authorized_keys` de 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-upgrades` actif, 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
```bash
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
1. **Les clés SSH avant la désactivation du mot de passe.** C'est la seule séquence qui ne pardonne pas l'inversion.
2. **Le pare-feu.** Sans lui, chaque service déployé est exposé par défaut.
3. **Les mises à jour automatiques.** Un serveur à jour le jour de l'installation ne l'est plus trois semaines après.
4. **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) |
## Sources
- [Format deb822 sous Trixie — dev.to](https://dev.to/r3m8/debian-13-trixie-adoptez-le-nouveau-format-deb822-1ml6)
- [Migration vers deb822 — OSTechNix](https://ostechnix.com/migrate-to-deb822-format-debian-13-trixie/)
- [Changer le port SSH sous Debian 13 — IONOS](https://www.ionos.com/help/server-cloud-infrastructure/getting-started/important-security-information-for-your-server/changing-the-ssh-port/)
- [Notes de publication de Debian 13 — problèmes connus](https://www.debian.org/releases/trixie/release-notes/issues.en.html)