alpinux-wiki/docs/technique/serveur-debian-13.md
Alpinux 08ba3a7dfd Serveur Debian 13 : corriger les commandes fautives, et expliquer
Revue croisée avec les guides de référence du domaine. Quatre commandes
échouaient ou donnaient l'illusion de fonctionner :

- journald : le drop-in était édité avant que son répertoire existe,
  nano refusait d'enregistrer ;
- restic : les variables posées par « export » ne passent pas sudo, qui
  les efface — le dépôt n'était pas celui qu'on croyait ;
- la sauvegarde reposait sur « systemd-run », dont l'unité transitoire
  disparaît au redémarrage : c'est la sauvegarde qui s'arrête sans que
  personne ne le voie. Remplacée par un .service + .timer persistant ;
- le filet de sécurité du pare-feu n'existait que pour ufw ; l'équivalent
  nftables manquait.

Ajouts de fond, par ordre d'importance : vérifier l'empreinte de la clé
d'hôte à la première connexion — le seul moment où une interception est
possible —, régénérer les clés des images clonées, AppArmor, l'audit
Lynis en fin de parcours, la sonde de supervision externe (une alerte
émise par la machine ne dit rien quand la machine est morte), la
journalisation distante, le fichier 20auto-upgrades qui commande
réellement l'automatisme, needrestart non interactif, growpart, la jail
recidive, les clés FIDO2, et AllowGroups à la place d'AllowUsers pour
qu'un second administrateur existe.

Pédagogie : le modèle de menace en ouverture, un niveau par étape avec un
parcours court de quinze minutes, des variables qu'on exporte au lieu de
les remplacer à la main, quatre encadrés « pour comprendre » (FQDN/PTR,
clé publique, activation par socket, tmpfs), et sur les trois étapes où
l'on peut se verrouiller dehors : résultat attendu, si ça échoue, et
comment revenir en arrière. Plus une annexe pour le jour où c'est raté —
console, mode rescue, chroot.

Les avertissements passent en admonitions typées : la couleur distingue
enfin l'information du danger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BE4rvHVETRoWnYNDTGssdo
2026-09-20 00:13:05 +02:00

1926 lines
74 KiB
Markdown

---
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, AppArmor, mises à jour automatiques, supervision, sauvegarde restaurable et audit Lynis.
---
# Préparation d'un serveur Debian 13 (Trixie)
!!! info "État du document"
Rédigé le **19/09/2026** pour **Debian 13 « Trixie »**, révisé le **20/09/2026** après
relecture croisée avec les guides de référence du domaine. 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 une heure** en lisant vraiment, une demi-heure si vous connaissez déjà le
terrain. Rien ici ne se rattrape en vitesse : les quatre étapes qui touchent à l'accès
peuvent vous enfermer dehors.
### Contre quoi vous vous protégez
Un serveur qui vient d'être livré n'est pas tranquille : son adresse IP appartient à des
plages publiques que des robots balaient en permanence. Les **premières tentatives de
connexion arrivent dans les minutes qui suivent la mise en ligne**, avant même que vous
ayez fini de le configurer. Elles ne vous visent pas : elles essaient `root`, `admin`,
`test`, avec les mots de passe des fuites connues, sur des millions d'adresses.
Trois familles d'ennuis, et ce que cette fiche oppose à chacune :
| Ce qui arrive vraiment | Ce qui l'en empêche |
| --- | --- |
| Un robot devine un mot de passe faible et ouvre une session | Authentification par clé seule, root refusé, comptes nominatifs (étapes 7 à 9) |
| Un service oublié écoute sur le réseau et se fait exploiter | Inventaire de ce qui écoute, pare-feu fermé par défaut, AppArmor (étapes 2, 10, 13) |
| Une faille publiée reste non corrigée pendant des semaines | Correctifs de sécurité automatiques, alerte de fin de support (étape 12) |
Et quand tout cela échoue quand même : un instantané, une sauvegarde restaurable et des
journaux qui existent ailleurs (étapes 14 et 15).
!!! danger "Règle d'or : ne fermez jamais la seconde session"
Tant que vous touchez à SSH ou au pare-feu, gardez une **deuxième session SSH
ouverte** sur le serveur. Chaque modification se teste depuis une **troisième**
session, jamais en refermant celle qui marche.
Vérifiez aussi, 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. En cas de problème, la marche à suivre
est en fin de page : [Si vous êtes verrouillé dehors](#si-vous-etes-verrouille-dehors).
### Les niveaux, et le parcours court
Chaque étape porte un niveau :
| Niveau | Ce que cela veut dire |
| --- | --- |
| **Essentiel** | À faire toujours, même en urgence. Sans cela, la machine est exposée ou inexploitable. |
| **Recommandé** | Ce qui distingue un serveur tenu d'un serveur qui tient par chance. À faire le jour même. |
| **Avancé** | Utile selon l'usage et l'hébergeur. À lire, à appliquer si le contexte s'y prête. |
**Vous devez sécuriser un serveur ce soir, en un quart d'heure ?** Faites, dans l'ordre :
[2](#etape-2-premiere-connexion) (constat et empreinte),
[4](#etape-4-sources-apt-et-mise-a-jour) (mise à jour),
[7](#etape-7-compte-administrateur-et-sudo) (compte admin),
[8](#etape-8-cles-ssh-et-durcissement) (clés SSH),
[10](#etape-10-pare-feu) (pare-feu),
[12](#etape-12-mises-a-jour-de-securite-automatiques) (mises à jour automatiques).
Le reste peut attendre le lendemain — mais pas la semaine suivante.
### 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) |
| `DOMAINE` | `exemple.fr` | Le domaine qui porte le serveur |
| `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 |
Plutôt que de remplacer `web01` à la main dans quarante blocs — c'est là que naissent les
erreurs — posez-les une fois en début de session, sur le serveur comme sur votre poste :
```bash
export SRV=web01 DOMAINE=exemple.fr USER=alix SSHPORT=2222
export FQDN="$SRV.$DOMAINE"
export IPV4=203.0.113.10
```
Les commandes de cette page s'écrivent alors telles quelles, et `echo $FQDN` vérifie ce
que le shell a vraiment compris.
!!! warning "Deux pièges des variables d'environnement"
Elles **disparaissent à la déconnexion** : reposez-les à chaque nouvelle session, ou
écrivez-les dans votre `~/.bashrc` le temps de l'installation.
Et **`sudo` ne les transmet pas** (`env_reset` est le défaut Debian) : une commande
comme `sudo echo $FQDN` fonctionne — le shell remplace avant d'appeler sudo — mais
`sudo sh -c 'echo $FQDN'` renvoie du vide. Utilisez `sudo -E` pour conserver
l'environnement, ou passez par `sudo tee` avec un *heredoc*.
### 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é<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
**Recommandé** — le serveur tourne sans, mais ses mails partent en indésirables et ses journaux sont illisibles.
!!! info "Pour comprendre : FQDN, PTR et FCrDNS"
Le **FQDN** est le nom complet de la machine, `web01.exemple.fr` : un nom court
(`web01`) suivi du domaine. C'est lui, et lui seul, qui identifie le serveur pour le
reste d'Internet.
L'enregistrement **A** traduit ce nom en adresse IP — c'est l'aller. L'enregistrement
**PTR**, le *reverse*, fait le chemin retour : il traduit l'adresse IP en nom. Les
deux sont gérés à des endroits différents : le A chez le gestionnaire du domaine, le
PTR chez l'hébergeur qui possède l'adresse IP.
```
web01.exemple.fr ──(A)──▶ 203.0.113.10 ──(PTR)──▶ web01.exemple.fr
└──────────── la boucle doit se refermer ────────────┘
```
Quand la boucle se referme sur le même nom, on parle de **FCrDNS** valide. Les
serveurs de messagerie s'en servent comme premier filtre anti-spam : un expéditeur
dont le reverse ne correspond à rien est rejeté ou classé indésirable avant même
d'être lu.
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*).
!!! note "Si `dig` manque sur votre poste"
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
**Essentiel** — constater l'état de livraison et valider l'identité de la machine.
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 l'empreinte avant de répondre « yes »
SSH affiche l'empreinte de la clé d'hôte et demande confirmation. **C'est le seul moment
de toute la vie du serveur où une interception est possible** : une fois l'empreinte
enregistrée dans votre `known_hosts`, toute substitution déclenche une alerte. Répondre
« yes » sans regarder, c'est accepter n'importe quel serveur qui se présente à cette
adresse.
Comparez l'empreinte affichée avec celle que l'hébergeur publie — panneau d'administration,
courriel de livraison, ou console KVM :
```bash
# Sur la console KVM de l'hébergeur, ou après une première connexion de confiance
for f in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf "$f"; done
```
Les deux empreintes doivent être identiques, caractère pour caractère. Si l'hébergeur ne
publie rien et que vous avez une console, relevez-la là : c'est le seul canal qui ne passe
pas par le réseau que vous cherchez à valider.
### Régénérer les clés d'hôte d'une image clonée
Certains fournisseurs distribuent des images où les clés SSH ont été générées **une seule
fois, avant le clonage** : des milliers de machines partagent alors la même clé privée, et
qui possède cette image peut se faire passer pour votre serveur.
```bash
ls -l --time-style=full-iso /etc/ssh/ssh_host_*_key
```
Si la date est antérieure à la livraison de votre machine, régénérez-les :
```bash
sudo rm /etc/ssh/ssh_host_*
sudo dpkg-reconfigure openssh-server
sudo systemctl restart ssh
```
Votre prochaine connexion signalera un changement de clé, ce qui est normal ici :
`ssh-keygen -R 203.0.113.10` sur votre poste, puis revérifiez la nouvelle empreinte.
Le même raisonnement vaut pour l'identifiant de la machine, qui sert de base au DUID
DHCPv6 et à la corrélation des journaux :
```bash
cat /etc/machine-id
# S'il est identique sur deux machines issues de la même image :
sudo rm -f /etc/machine-id && sudo systemd-machine-id-setup
```
### 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
```
!!! tip "Le disque est-il entier ?"
Beaucoup de VPS démarrent sur une image dont la partition est plus petite que le
volume facturé : `lsblk` montre alors un disque de 80 Go dont la partition n'en fait
que 20. L'espace est payé, mais inutilisable tant qu'on ne l'a pas réclamé.
```bash
sudo apt install -y cloud-guest-utils # fournit growpart
sudo growpart /dev/vda 1 # adaptez le disque et le numéro
sudo resize2fs /dev/vda1 # xfs_growfs si le système est en XFS
df -h /
```
Faites-le maintenant, avant d'avoir des données : l'opération est sûre mais se
déroule mieux sur une machine vide.
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
**Recommandé** — une identité stable, sur laquelle s'appuient mail, journaux et certificats.
### 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.
!!! warning "Ne touchez pas à `localhost`"
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.
!!! warning "Le nom d'hôte revient après un redémarrage ?"
Sur certaines images cloud, `cloud-init` le réécrit à chaque démarrage. Dites-lui de
respecter le vôtre :
```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
**Essentiel** — une machine livrée n'est jamais à 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
```
!!! danger "Sans `trixie-security`, aucun correctif"
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
**Recommandé** — de quoi diagnostiquer sans improviser.
```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
**Recommandé** — une heure juste est la condition d'un journal exploitable.
### 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
```
!!! note "Pourquoi `localectl` et pas `LANG=`"
`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
```
!!! note "UTC ou heure locale ?"
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
**Essentiel** — ne plus travailler avec le compte du fournisseur.
### 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
```
!!! danger "Le `-a` n'est pas facultatif"
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
```
!!! danger "Toujours `visudo`, jamais l'éditeur seul"
`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`.
!!! note "Le port change en cours de route"
À ce stade, SSH écoute encore sur **22** : c'est le port à écrire ici. Si vous le
déplacez à l'étape 8.4, revenez mettre `$SSHPORT` — la suite de cette page emploie
`2222` comme exemple de port final, y compris dans les règles de pare-feu de l'étape
10. Tant que la bascule n'est pas faite et **vérifiée**, gardez les deux ports
ouverts.
## Étape 8 — Clés SSH et durcissement
**Essentiel** — c'est l'étape qui ferme la porte la plus attaquée.
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.
!!! info "Pour comprendre : pourquoi une clé plutôt qu'un mot de passe"
`ssh-keygen` produit deux fichiers : une **clé privée**, qui ne quitte jamais votre
poste, et une **clé publique**, que vous déposez sur tous les serveurs où vous allez.
À la connexion, le serveur envoie un défi que seule la clé privée sait résoudre — la
clé privée elle-même ne transite jamais.
Un mot de passe de douze caractères se devine en quelques milliards d'essais, et les
robots qui scannent le port 22 en font des milliers par heure. Une clé Ed25519 ne se
devine pas : il n'y a rien à essayer. C'est pour cela que l'on peut couper
l'authentification par mot de passe sans rien perdre.
!!! tip "Option avancée : une clé sur support matériel"
Avec une clé de sécurité FIDO2 (YubiKey, Nitrokey, Token2), le secret ne quitte jamais
le support physique — voler votre ordinateur ne suffit plus, il faut aussi la clé
dans votre poche :
```bash
ssh-keygen -t ed25519-sk -C "alix@yubikey"
```
Le suffixe `-sk` (*security key*) demande une confirmation physique à chaque
connexion. Gardez une seconde clé matérielle, ou une clé logicielle de secours dans
`authorized_keys` : une clé FIDO2 perdue ne se duplique pas.
### 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.
Regardez d'abord ce qui s'y trouve déjà :
```bash
ls -l /etc/ssh/sshd_config.d/
```
!!! warning "Le premier fichier qui parle a raison"
Pour une directive donnée, **la première valeur rencontrée l'emporte**, et les
fichiers sont lus dans l'ordre alphabétique. Sous Debian, l'`Include` est en tête de
`sshd_config`, donc n'importe quel drop-in gagne contre le corps du fichier — mais un
`50-cloud-init.conf`, fréquent sur les images d'hébergeurs, gagne contre votre
`99-durcissement.conf`. Or il force souvent `PasswordAuthentication yes`.
Si un tel fichier est présent : nommez le vôtre `00-durcissement.conf`, ou corrigez
directement le fichier fautif. Dans tous les cas, c'est `sshd -T` qui tranche, pas la
lecture des fichiers.
```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
AllowGroups ssh-users
# Réduire la surface
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
PermitUserEnvironment no
# Sessions mortes
ClientAliveInterval 300
ClientAliveCountMax 2
```
Le groupe doit exister **avant** le rechargement, sans quoi plus personne ne peut se
connecter :
```bash
sudo groupadd -f ssh-users
sudo usermod -aG ssh-users alix
id alix # ssh-users doit apparaître
```
!!! note "Pourquoi un groupe plutôt qu'une liste de noms"
Une liste blanche vaut mieux qu'un `DenyUsers debian` : elle couvre aussi les comptes
que vous n'avez pas anticipés. Mais `AllowUsers alix` désigne une personne, et un
serveur dont une seule personne détient l'accès est un serveur perdu le jour où cette
personne ne répond plus.
Avec un groupe, ouvrir l'accès à quelqu'un devient `sudo usermod -aG ssh-users bea`,
et le fermer `sudo gpasswd -d bea ssh-users` — sans jamais rouvrir la configuration de
sshd. **Prévoyez un second administrateur** dès maintenant : c'est le moment où cela
coûte le moins cher.
!!! danger "L'ordre des opérations n'est pas négociable"
`PasswordAuthentication no` vous verrouille dehors si la clé de l'étape 8.2 ne
fonctionne pas. Vérifiez la connexion par clé dans une session séparée **avant** de
recharger sshd, et gardez la session courante ouverte jusqu'au succès du test.
Vérifiez maintenant la configuration **réellement appliquée**, jamais le fichier seul :
```bash
sudo sshd -t # syntaxe ; silence = OK
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauth|allowgroups|^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.
!!! info "Pour comprendre : l'activation par socket"
Dans le modèle classique, `sshd` démarre au boot, ouvre lui-même le port 22 et attend.
Avec l'activation par socket, c'est **systemd** qui ouvre le port et écoute à sa
place ; `sshd` ne démarre qu'à l'arrivée d'une connexion, et systemd lui passe la
main.
Le port n'appartient donc plus à `sshd` mais à l'unité `ssh.socket`. C'est pourquoi
`Port 2222` dans `sshd_config` est lu, accepté, et sans effet : au moment où `sshd`
s'exécute, l'écoute est déjà établie par quelqu'un d'autre. Le gain — quelques
mégaoctets de mémoire tant que personne ne se connecte — explique ce choix par
défaut ; c'est aussi le piège le plus déroutant de Debian 13.
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é
```
**Résultat attendu** — la première commande ouvre une session sans demander de mot de
passe ; les deux autres échouent en `Permission denied (publickey)`. Et la configuration
appliquée confirme les quatre directives qui comptent :
```bash
sudo sshd -T | grep -Ei '^port|permitrootlogin|passwordauthentication|allowgroups'
```
**Si ça échoue**
| Symptôme | Cause la plus fréquente |
| --- | --- |
| `Permission denied (publickey)` sur **votre** compte | La clé n'est pas dans `authorized_keys`, ou les droits sont trop larges (`chmod 700 ~/.ssh`, `600 authorized_keys`) |
| `Connection refused` après un changement de port | `ssh.socket` n'a pas été rechargé, ou le pare-feu n'ouvre pas le nouveau port |
| Le mot de passe est toujours demandé | Un drop-in antérieur gagne : relisez `ls /etc/ssh/sshd_config.d/` et tranchez avec `sshd -T` |
| Plus personne ne peut entrer | `AllowGroups` pointe un groupe vide ou inexistant |
**Revenir en arrière**, depuis la session restée ouverte :
```bash
sudo rm /etc/ssh/sshd_config.d/99-durcissement.conf
sudo systemctl reload ssh
# Si le port avait été déplacé :
sudo rm -f /etc/systemd/system/ssh.socket.d/override.conf
sudo systemctl daemon-reload && sudo systemctl restart ssh.socket
```
## Étape 9 — Neutraliser le compte fournisseur
**Essentiel** — un compte connu de tous, au nom prévisible.
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é | `AllowGroups ssh-users` (é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
```
!!! warning "Ne supprimez pas le compte du fournisseur trop vite"
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.
!!! danger "Ne retirez pas le shell de root"
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
**Essentiel** — tout ce qui écoute est joignable tant que rien ne filtre.
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' # $SSHPORT : le port réellement en écoute
# Pendant une bascule de port, autorisez les deux le temps de vérifier :
# sudo ufw limit 22/tcp comment 'SSH ancien port'
# 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
```
!!! danger "Jamais de `policy drop` sans règle SSH"
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.
**Si vous avez choisi ufw :**
```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
```
**Si vous avez choisi nftables** — `systemctl stop ufw` n'existe pas, il faut vider le
jeu de règles :
```bash
# Avant d'activer les règles
sudo systemd-run --on-active=10min --unit=nft-panic nft flush ruleset
# Une fois la connexion reconfirmée depuis une nouvelle session
sudo systemctl stop nft-panic.timer
```
Ici, l'unité transitoire est exactement ce qu'on veut : elle doit disparaître d'elle-même.
### 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
```
### 10.5 Contrôler, réparer, annuler
**Résultat attendu** — la politique d'entrée est en refus, SSH est la seule exception, et
une nouvelle session s'ouvre toujours :
```bash
sudo ufw status verbose # ou : sudo nft list ruleset
ss -tlpn # ce qui écoute, à comparer avec ce qui est ouvert
```
**Si ça échoue**
| Symptôme | Cause la plus fréquente |
| --- | --- |
| Session en cours figée juste après l'activation | Règle SSH absente ou placée après la politique de refus |
| Le port est ouvert, le service reste injoignable | Le pare-feu de l'hébergeur ne l'ouvre pas de son côté (§10.4) |
| `ufw` semble sans effet | Des règles nftables concurrentes existent : `sudo nft list ruleset` pour voir qui filtre réellement |
| Tout tombe après un redémarrage | Le pare-feu n'est pas activé au démarrage : `systemctl is-enabled ufw` ou `nftables` |
**Revenir en arrière** — depuis la session restée ouverte, ou par la console de
l'hébergeur :
```bash
sudo ufw disable # option A
sudo nft flush ruleset # option B, vide tout le filtrage
sudo systemctl disable --now nftables # et empêche son retour au redémarrage
```
!!! danger "Un pare-feu vidé est un serveur nu"
`nft flush ruleset` laisse la machine sans aucun filtrage. C'est une manœuvre de
dépannage, pas un état dans lequel on laisse un serveur : remettez les règles dans la
foulée, une fois l'accès retrouvé.
## Étape 11 — fail2ban
**Recommandé** — écarte le bruit de fond avant qu'il ne remplisse vos journaux.
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
# Bannit longuement ceux qui reviennent après chaque bannissement
[recidive]
enabled = true
bantime = 1w
findtime = 1d
maxretry = 3
```
La jail `recidive` lit les bannissements de fail2ban lui-même : une adresse bannie trois
fois en vingt-quatre heures l'est ensuite pour une semaine. C'est ce qui sépare le robot
opportuniste, qui repasse toutes les heures, du visiteur qui s'est trompé de mot de passe.
`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.
!!! note "Avec ufw plutôt que nftables"
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
```
!!! warning "Votre IP dans `ignoreip`, avant de démarrer"
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
**Essentiel** — un serveur à jour aujourd'hui ne l'est plus dans trois semaines.
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.
### Le fichier qui décide vraiment
`dpkg-reconfigure` écrit dans `/etc/apt/apt.conf.d/20auto-upgrades`, et c'est **ce
fichier** qui commande l'automatisation — pas `50unattended-upgrades`, qui n'en règle que
le détail. Vérifiez-le explicitement :
```bash
cat /etc/apt/apt.conf.d/20auto-upgrades
```
Il doit contenir ces quatre directives :
```
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
```
Sans la dernière à `"1"`, tout le reste est configuré pour rien : les index se
rafraîchissent, les paquets se téléchargent, et rien ne s'installe.
### Rendre `needrestart` non interactif
Installé à l'étape 5, `needrestart` pose une question quand des services doivent être
relancés. Sur un serveur, cette question bloque `unattended-upgrades` au lieu d'être
posée à quelqu'un :
```bash
echo "\$nrconf{restart} = 'a';" | sudo tee /etc/needrestart/conf.d/50-auto.conf
```
`'a'` redémarre automatiquement les services concernés. Gardez `'l'` (lister seulement)
si vous préférez décider vous-même — mais alors n'activez pas le redémarrage automatique
d'`unattended-upgrades`, vous cumuleriez le pire des deux.
### Savoir ce qui n'est plus suivi
Tous les paquets d'une version stable ne sont pas couverts par l'équipe de sécurité
jusqu'au bout — certains sortent du périmètre en cours de vie.
```bash
sudo apt install -y debian-security-support
check-support-status
```
À relancer après chaque gros `apt` : la commande liste ce qui est installé chez vous et
n'est plus, ou bientôt plus, suivi.
### 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.
### Contrôler, réparer, annuler
**Résultat attendu** — les deux minuteries APT sont actives, et le journal montre des
installations réelles et non un simulacre :
```bash
systemctl list-timers apt-daily.timer apt-daily-upgrade.timer
grep -c "Packages that will be upgraded" /var/log/unattended-upgrades/unattended-upgrades.log
```
**Si ça échoue**
| Symptôme | Cause la plus fréquente |
| --- | --- |
| Le journal reste vide des jours durant | `APT::Periodic::Unattended-Upgrade` est à `"0"` dans `20auto-upgrades` |
| « No packages found that can be upgraded unattended » en permanence | `Origins-Pattern` ne couvre pas `trixie-security`, ou le dépôt de sécurité manque (étape 4) |
| Les mises à jour s'installent mais rien ne redémarre | `needrestart` en mode interactif : il attend une réponse que personne ne donnera |
| Le serveur redémarre à des heures imprévues | `Automatic-Reboot "true"` sans `Automatic-Reboot-Time` adapté |
**Revenir en arrière** — pour suspendre l'automatisme sans désinstaller :
```bash
sudo sed -i 's/^APT::Periodic::Unattended-Upgrade.*/APT::Periodic::Unattended-Upgrade "0";/' \
/etc/apt/apt.conf.d/20auto-upgrades
sudo systemctl disable --now apt-daily-upgrade.timer
```
!!! warning "Suspendre, c'est accepter de faire à la main"
Un serveur sans mises à jour automatiques redevient vulnérable en quelques semaines.
Si vous coupez l'automatisme, inscrivez la mise à jour manuelle dans une routine
datée — pas dans vos bonnes intentions.
## Étape 13 — Swap, /tmp et paramètres noyau
**Recommandé** — la stabilité au quotidien, et quelques durcissements 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
```
!!! note "Le coût du swap dépend du stockage"
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)).
!!! info "Pour comprendre : un système de fichiers en mémoire"
Un **tmpfs** ressemble à un répertoire ordinaire, mais son contenu n'est jamais écrit
sur le disque : il vit dans la RAM. D'où ses deux propriétés, qui expliquent tout le
reste — il est très rapide, et il disparaît intégralement à l'extinction.
Cela veut aussi dire qu'un fichier de 3 Go déposé dans `/tmp` occupe 3 Go de mémoire
vive, au détriment des services. Sur un serveur à 2 Go de RAM, quelques fichiers
temporaires volumineux suffisent à provoquer un arrêt par manque de mémoire.
```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
```
!!! warning "Ne coupez pas le routage à l'aveugle"
N'appliquez pas `net.ipv4.ip_forward = 0` sans réfléchir : Docker, les conteneurs et
les VPN en ont besoin.
### 13.4 AppArmor : vérifier qu'il travaille
AppArmor est actif par défaut depuis Debian 12, et personne ne le regarde jamais. Il
confine chaque programme profilé à ce dont il a besoin : un nginx compromis ne peut pas
lire `/home`, même en tant que root, parce que le noyau le lui refuse.
```bash
sudo apt install -y apparmor-utils
sudo aa-status
```
**Résultat attendu** — des profils chargés, et surtout des profils *en mode enforce* :
```
apparmor module is loaded.
XX profiles are loaded.
XX profiles are in enforce mode.
0 profiles are in complain mode.
```
Un profil en *complain* journalise les violations sans les bloquer : c'est le mode de mise
au point, pas un mode d'exploitation. Repassez-le en application avec
`sudo aa-enforce /etc/apparmor.d/<profil>`.
Retenez surtout que les services installés plus tard apportent leurs profils : après avoir
déployé nginx ou MariaDB, un `aa-status` confirme qu'ils sont bien confinés.
## Étape 14 — Journaux, supervision et alertes
**Recommandé** — savoir ce qui se passe, et être prévenu quand plus rien ne se passe.
### 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.
Le répertoire des drop-ins n'existe pas par défaut : créez-le **avant** d'ouvrir
l'éditeur, sinon l'enregistrement échoue.
```bash
sudo mkdir -p /etc/systemd/journald.conf.d
sudo nano /etc/systemd/journald.conf.d/00-limites.conf
```
```
[Journal]
Storage=persistent
SystemMaxUse=500M
SystemMaxFileSize=50M
MaxRetentionSec=1month
```
```bash
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
```
!!! warning "Un mot de passe en clair sur le disque"
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
```
### 14.5 Sortir les journaux de la machine
Tout ce qui précède repose sur des journaux locaux. Or un attaquant qui obtient root
efface `/var/log` et l'historique de `journald` : il ne reste rien pour comprendre ce qui
s'est passé. Un journal n'a de valeur d'enquête que s'il existe **ailleurs**.
Le principe : `journald` recopie vers syslog, et rsyslog expédie vers un collecteur
distant.
```bash
# Sur la machine : autoriser le relais vers syslog
sudo mkdir -p /etc/systemd/journald.conf.d
echo -e "[Journal]\nForwardToSyslog=yes" \
| sudo tee /etc/systemd/journald.conf.d/10-syslog.conf
sudo systemctl restart systemd-journald
# Puis, dans rsyslog, une ligne de destination (TCP, port 514 par défaut)
# *.* @@collecteur.exemple.fr:514
```
La mise en place complète — collecteur, chiffrement TLS, rétention — dépasse cette fiche.
Le minimum utile, si vous n'avez pas de collecteur : envoyer les alertes par courriel
(§14.3), dont la copie sort de la machine.
### 14.6 Une sonde depuis l'extérieur
L'alerte disque ci-dessus part **de la machine**. Le jour où celle-ci est éteinte, saturée
ou injoignable, elle n'envoie plus rien — et le silence ressemble à s'y méprendre au bon
fonctionnement.
C'est la brique qui manque à presque tous les petits serveurs : une sonde externe, qui
teste depuis ailleurs et vous prévient quand la réponse ne vient pas.
- **Uptime Kuma**, auto-hébergé sur une autre machine — idéalement chez un autre
hébergeur, sans quoi une panne de datacentre emporte la sonde avec le serveur ;
- ou un service de supervision gratuit, si vous n'avez qu'une machine.
Surveillez au minimum le port SSH et, dès qu'ils existent, les ports 80 et 443 avec la
date d'expiration du certificat.
## Étape 15 — Sauvegarde et instantané de référence
**Essentiel** — la seule étape qui rattrape l'échec de toutes les autres.
### 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 ./
```
!!! tip "Mieux : versionner la configuration"
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
```
Les paramètres de restic se passent par variables d'environnement. Comme `sudo` les
efface (`env_reset` est le défaut sous Debian), déposez-les dans un fichier lu par root
plutôt que de compter sur un `export` dans votre session :
```bash
sudo install -m 600 /dev/null /root/.restic-env
sudo tee /root/.restic-env >/dev/null <<'EOF'
RESTIC_REPOSITORY=sftp:sauvegarde@stockage.exemple.fr:/backups/web01
RESTIC_PASSWORD_FILE=/root/.restic-pass
EOF
sudo install -m 600 /dev/null /root/.restic-pass
sudo nano /root/.restic-pass # la phrase de passe du dépôt, seule sur sa ligne
```
!!! danger "Cette phrase de passe n'est récupérable nulle part"
Un dépôt restic dont on a perdu la phrase de passe est définitivement illisible.
Recopiez-la hors du serveur — gestionnaire de mots de passe ou coffre de
l'association — avant d'aller plus loin.
Initialisation et premières commandes, en chargeant explicitement ce fichier :
```bash
# Initialisation du dépôt distant (une seule fois)
sudo env $(cat /root/.restic-env | xargs) restic init
# Sauvegarde
sudo env $(cat /root/.restic-env | xargs) restic backup /etc /home /var/www --exclude-caches
# Vérification et purge
sudo env $(cat /root/.restic-env | xargs) restic snapshots
sudo env $(cat /root/.restic-env | xargs) 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`.
!!! warning "N'utilisez pas `systemd-run` pour une sauvegarde"
`systemd-run --on-calendar` crée une unité **transitoire** : elle disparaît au
redémarrage, et la sauvegarde cesse silencieusement. On ne s'en aperçoit que le jour
où l'on en a besoin. Écrivez de vraies unités.
```bash
sudo tee /etc/systemd/system/sauvegarde.service >/dev/null <<'EOF'
[Unit]
Description=Sauvegarde restic
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/root/.restic-env
ExecStart=/usr/bin/restic backup /etc /home /var/www --exclude-caches
ExecStartPost=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
EOF
sudo tee /etc/systemd/system/sauvegarde.timer >/dev/null <<'EOF'
[Unit]
Description=Sauvegarde restic quotidienne
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=30m
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now sauvegarde.timer
```
`Persistent=true` rattrape l'exécution manquée si la machine était éteinte à 3 h.
`EnvironmentFile` règle le problème des variables : le service les lit lui-même, sans
dépendre de votre session.
**Résultat attendu** — la minuterie apparaît avec sa prochaine échéance, et la première
exécution se déclenche à la demande :
```bash
systemctl list-timers sauvegarde.timer
sudo systemctl start sauvegarde.service
journalctl -u sauvegarde.service -n 20
```
### 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.
## Étape 16 — Auditer ce qui a été fait
**Recommandé** — vérifier que ce qui est écrit ici a réellement pris.
Vous avez appliqué quinze étapes ; un outil d'audit vous dira lesquelles ont réellement
pris. **Lynis** parcourt la machine et note ce qu'il trouve.
```bash
sudo apt install -y lynis
sudo lynis audit system
```
Le rapport se termine par un **index de durcissement** sur 100, et surtout par une liste
de suggestions numérotées, chacune avec sa justification.
**Résultat attendu** — sur une Debian fraîchement installée, l'index tourne autour de
**55 à 60** ; après un parcours comme celui-ci, il monte typiquement vers **70 à 75**.
!!! warning "Ne courez pas après le 100"
Les points restants dépendent de ce que la machine fait : durcir un noyau au-delà du
raisonnable, ou désactiver des modules dont vos services auront besoin, se paie en
pannes incompréhensibles six mois plus tard. Lisez les suggestions, appliquez celles
qui ont un sens pour votre usage, et notez pourquoi vous écartez les autres.
Le rapport complet reste dans `/var/log/lynis-report.dat`. Relancez l'audit après chaque
déploiement de service : c'est la meilleure façon de voir ce qu'un `apt install` a ouvert.
## 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`)
- [ ] **Empreinte de la clé d'hôte comparée** avec celle publiée par l'hébergeur
- [ ] Clés d'hôte régénérées si l'image était clonée, `machine-id` vérifié
- [ ] Partition étendue à tout le volume facturé (`lsblk`, `growpart`)
- [ ] 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é, `AllowGroups` posé
- [ ] Groupe `ssh-users` créé et peuplé — **avec un second administrateur**
- [ ] `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`)
- [ ] `20auto-upgrades` contrôlé : `Unattended-Upgrade "1"`
- [ ] `needrestart` passé en mode non interactif
- [ ] `check-support-status` consulté (paquets hors support sécurité)
- [ ] `aa-status` : profils AppArmor chargés et en mode *enforce*
- [ ] 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
- [ ] Minuterie de sauvegarde **persistante** (`.timer` + `Persistent=true`), pas un `systemd-run`
- [ ] Sonde externe en place, sur une autre machine que celle-ci
- [ ] `lynis audit system` passé, suggestions lues et arbitrées
- [ ] 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|allowgroups)'; 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 /
```
## Si vous êtes verrouillé dehors
Cela arrive, malgré la session de secours — elle finit par expirer, ou l'on referme la
mauvaise fenêtre. Voici l'ordre dans lequel chercher.
**1. Est-ce vraiment le serveur ?** Depuis un autre réseau (partage de connexion du
téléphone), testez le port :
```bash
nc -vz 203.0.113.10 2222
```
Si la connexion passe d'ailleurs mais pas de chez vous, c'est fail2ban ou le pare-feu de
l'hébergeur qui a banni votre adresse, pas la configuration du serveur.
**2. La console de l'hébergeur.** KVM, VNC ou console série : elle donne un accès clavier
indépendant de SSH et du réseau. C'est la voie la plus rapide, et la raison pour laquelle
cette fiche demande de vérifier son existence **avant** de commencer.
Une fois connecté, retirez ce qui bloque :
```bash
sudo rm /etc/ssh/sshd_config.d/99-durcissement.conf
sudo systemctl restart ssh
sudo ufw disable # ou : sudo nft flush ruleset
sudo fail2ban-client unban --all
```
**3. Le mode rescue.** Si même la console ne répond plus, l'hébergeur propose un démarrage
sur un système de secours. Le disque de la machine s'y monte, et l'on répare de
l'extérieur :
```bash
lsblk # identifier la partition racine
sudo mount /dev/vda1 /mnt
sudo mount --bind /dev /mnt/dev
sudo mount --bind /proc /mnt/proc
sudo mount --bind /sys /mnt/sys
sudo chroot /mnt /bin/bash
# Dans le chroot : annuler la modification fautive
rm /etc/ssh/sshd_config.d/99-durcissement.conf
sed -i 's/^Port .*/Port 22/' /etc/ssh/sshd_config
systemctl disable nftables
exit
sudo umount -R /mnt
```
Redémarrez sur le disque, reconnectez-vous, et reprenez la configuration en gardant cette
fois deux sessions ouvertes.
!!! tip "Ce qui verrouille, dans l'ordre de fréquence"
Un port SSH changé sans ouvrir le nouveau dans le pare-feu ; un `AllowGroups` posé
avant que le groupe n'existe ; `PasswordAuthentication no` avec une clé qui n'a jamais
été testée ; une politique `drop` activée sans règle SSH au-dessus. Les quatre se
préviennent en testant depuis une **seconde** session avant de fermer la première.
## Et après ?
Le serveur est prêt à recevoir ses services — c'est là que cette fiche s'arrête et que les
autres commencent :
- [Docker](../guides/docker.md) — installer le moteur de conteneurs sur cette base ;
- [Nextcloud](nextcloud.md) et [Matrix](matrix.md) — deux services que l'association
héberge ;
- [Déploiement du wiki](deploiement-wiki.md) — l'exemple d'un service statique, webhook
compris.
Ouvrez les ports au moment du déploiement, pas avant, et repassez un `lynis audit system`
derrière.
!!! tip "Le deuxième serveur ne se prépare pas à la main"
Cette checklist a exactement la forme d'un playbook. Le jour où vous installez une
deuxième machine, transposez-la dans **Ansible** : chaque étape devient une tâche
idempotente, l'ordre est écrit noir sur blanc, et la configuration cesse de vivre dans
la mémoire de celui qui l'a faite. Des rôles publics existent pour Debian 13 —
lisez-les avant de les appliquer, ils durcissent parfois plus que vous ne le souhaitez.
## Ce que vous avez construit
En cinq lignes, pour consolider ce que la checklist vérifie point par point :
1. **Une identité cohérente** — un FQDN qui se résout dans les deux sens, un nom d'hôte
stable, une heure juste. Sans cela, les journaux mentent et les mails partent en
indésirables.
2. **Un accès nominatif et robuste** — une clé par personne, un groupe qui décide qui
entre, le compte du fournisseur neutralisé, root inaccessible depuis le réseau.
3. **Une surface réduite et surveillée** — un pare-feu qui ferme par défaut, fail2ban qui
écarte les robots, AppArmor qui confine les services, des journaux plafonnés et lisibles.
4. **Une machine qui se maintient seule** — les correctifs de sécurité s'installent, ce qui
n'est plus suivi se signale, et vous êtes prévenu de ce qui demande un redémarrage.
5. **Une porte de sortie** — un instantané de référence, une sauvegarde dont la
restauration a été testée, et une sonde extérieure pour vous dire que la machine vit.
Le reste — les services, leurs ports, leurs bases de données — se construit par-dessus.
## 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` » | `AllowGroups ssh-users` + verrouillage du compte | Une liste blanche couvre aussi les comptes non anticipés ; un groupe survit à l'arrivée d'un second administrateur ; 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)