alpinux-wiki/docs/technique/serveur-debian-13.md
Alpinux 51b34b38a7 Remplacer la fiche Debian 11 par une procédure Debian 13 (Trixie)
L'ancienne fiche datait de Debian 11 et s'arrêtait à une liste de
commandes : rien sur les clés SSH, le pare-feu, fail2ban, les mises à
jour automatiques ni la sauvegarde. Elle portait aussi deux erreurs qui
mordent — une locale écrite avec un underscore, et l'ordre des noms sur
la ligne 127.0.1.1 qui décide de ce que renvoie « hostname -f ».

La nouvelle page couvre les quinze étapes, du DNS à la sauvegarde
testée, avec les pièges propres à Trixie : sources deb822, SSH activé
par socket, /tmp en tmpfs. Elle reste générique — aucun service
particulier n'y est déployé.

Deux extensions Markdown sont activées pour elle : les listes de tâches
(la checklist récapitulative) et mermaid (l'ordre des opérations). Aucune
dépendance nouvelle : le thème Material embarque déjà mermaid. Les deux
sont documentées dans la page Markdown du wiki.

L'adresse change, donc l'ancienne reste servie par une page de renvoi.

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

43 KiB

description
Préparer un serveur Debian 13 « Trixie » livré par un hébergeur — DNS, nom d'hôte, sources deb822, comptes, SSH par clé, pare-feu, fail2ban, mises à jour automatiques, supervision et sauvegarde.

Préparation d'un serveur Debian 13 (Trixie)

!!! info "État du document" Rédigé le 19/09/2026 pour Debian 13 « Trixie ». Il remplace l'ancienne fiche Debian 11 : ce qui a changé est résumé en fin de page.

Avant de commencer

Cette procédure part d'une installation minimale de Debian 13 « Trixie » fraîchement livrée par un hébergeur, et s'arrête au moment où le serveur est prêt à recevoir ses services. Comptez 30 à 45 minutes.

Règle d'or : tant que vous touchez à SSH ou au pare-feu, gardez une deuxième session SSH ouverte sur le serveur et ne la fermez jamais. Elle est votre filet de sécurité si une modification vous verrouille dehors.

Vérifiez avant de commencer que vous disposez d'un accès de secours hors SSH : console KVM/VNC, console série ou mode rescue de l'hébergeur. Sans lui, une erreur de pare-feu signifie une réinstallation.

Variables utilisées dans ce document

Remplacez systématiquement ces valeurs par les vôtres.

Variable Exemple Signification
SRV web01 Nom court du serveur (sans domaine)
FQDN web01.exemple.fr Nom complet, celui du DNS et du reverse
IPV4 203.0.113.10 Adresse IPv4 publique
IPV6 2001:db8::10 Adresse IPv6 publique, si fournie
USER alix Votre futur compte d'administration
VENDOR debian Compte créé par l'hébergeur, à neutraliser
SSHPORT 2222 Port SSH final, si vous le déplacez

Ordre des opérations

L'ordre compte : le DNS se propage pendant que vous travaillez, et le pare-feu ne se ferme qu'une fois le nouvel accès SSH prouvé.

flowchart LR
  A[DNS + reverse] --> B[Identité<br/>hostname, hosts]
  B --> C[Système<br/>MAJ, paquets, locale]
  C --> D[Compte admin<br/>+ sudo]
  D --> E[SSH<br/>clés + durcissement]
  E --> F[Pare-feu<br/>+ fail2ban]
  F --> G[Automatismes<br/>MAJ, supervision]
  G --> H[Sauvegarde<br/>+ instantané]

Étape 1 — DNS direct et reverse DNS

Faites cette étape en premier : la propagation DNS prend de quelques minutes à quelques heures, et elle tournera pendant que vous configurez le reste. Un reverse DNS cohérent conditionne l'acceptation de vos mails et la lisibilité de vos journaux.

1.1 Enregistrements directs (zone DNS du domaine)

Dans l'interface DNS de votre registrar ou de votre hébergeur, créez :

Type Nom Valeur TTL
A web01 203.0.113.10 300 pendant la mise en place, puis 3600
AAAA web01 2001:db8::10 idem, si IPv6 fournie

Un TTL court (300 s) pendant l'installation vous laisse corriger une erreur sans attendre. Remontez-le une fois le serveur stabilisé.

1.2 Reverse DNS (PTR)

Le PTR ne se déclare pas dans la zone du domaine, mais chez le propriétaire de l'adresse IP, c'est-à-dire votre hébergeur. Cherchez dans son panneau une entrée nommée « reverse », « rDNS » ou « PTR », sur la fiche de l'IP et non sur celle du domaine.

La valeur doit être exactement le FQDN déclaré en 1.1 : web01.exemple.fr. Faites-le pour l'IPv4 et pour l'IPv6 ; un reverse IPv6 manquant est une cause classique de mails rejetés.

1.3 Vérification

Depuis votre poste, une fois la propagation faite :

dig +short web01.exemple.fr A
dig +short web01.exemple.fr AAAA
dig +short -x 203.0.113.10
dig +short -x 2001:db8::10

Les quatre réponses doivent se refermer sur elles-mêmes : le A pointe vers l'IP, le PTR de cette IP renvoie le même FQDN. C'est ce qu'on appelle un FCrDNS valide (forward-confirmed reverse DNS).

Si 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.

ssh debian@203.0.113.10

Utilisez l'IP et non le FQDN pour cette première connexion : le DNS n'est peut-être pas encore propagé.

Vérifier ce qu'on a vraiment reçu

Avant toute modification, prenez trois minutes pour constater l'état initial.

cat /etc/os-release            # confirme Debian 13 (trixie)
uname -r                       # version du noyau
ip -brief address              # interfaces et adresses
lsblk                          # disques et partitions
free -h                        # RAM et swap
sudo systemctl list-units --type=service --state=running
sudo ss -tulpn                 # ce qui écoute déjà sur le réseau

La dernière commande est la plus instructive : elle montre les services exposés dès la livraison. Tout ce qui écoute sur 0.0.0.0 ou :: et que vous ne reconnaissez pas mérite une décision explicite — désactiver ou garder.

Si la connexion est refusée

Symptôme Cause fréquente
Permission denied (publickey) La clé publique n'a pas été injectée à la commande, ou mauvais nom d'utilisateur
Connection refused Le serveur n'a pas fini de démarrer, ou SSH écoute sur un autre port
Connection timed out Pare-feu amont côté hébergeur (security group, firewall network)
REMOTE HOST IDENTIFICATION HAS CHANGED Réinstallation ou réattribution d'IP → ssh-keygen -R 203.0.113.10

Étape 3 — Nom d'hôte et /etc/hosts

3.1 Définir le nom d'hôte

Debian attend ici le nom court, pas le FQDN. Le FQDN est reconstitué via /etc/hosts.

sudo hostnamectl set-hostname web01

Vous pouvez aussi renseigner deux champs purement informatifs, utiles quand on gère plusieurs machines :

sudo hostnamectl set-hostname "Front web — Paris" --pretty
sudo hostnamectl set-chassis vm        # ou: server, desktop

3.2 Corriger /etc/hosts

C'est ce fichier, et non hostnamectl, qui donne au système son FQDN. La règle : sur la ligne 127.0.1.1, le FQDN vient en premier, le nom court ensuite.

sudo nano /etc/hosts

Ciblez ce résultat :

127.0.0.1       localhost
127.0.1.1       web01.exemple.fr web01

# The following lines are desirable for IPv6 capable hosts
::1             localhost ip6-localhost ip6-loopback
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters

Certains hébergeurs pré-remplissent la ligne 127.0.1.1 avec le nom d'usine de la machine. Deux options : le supprimer, ou le laisser après vos deux entrées — la ligne compte alors quatre références.

127.0.1.1       web01.exemple.fr web01 vps-1a2b3c.exemple-hebergeur.net vps-1a2b3c

Les deux formes fonctionnent. Supprimer l'ancien nom est plus propre ; le conserver évite de casser un script de l'hébergeur qui s'y référerait encore.

⚠️ Ne touchez jamais à la ligne 127.0.0.1 localhost. Et n'associez pas le FQDN à l'IP publique dans /etc/hosts : sur une machine à IP dynamique ou derrière NAT, cela crée des résolutions fantomatiques.

3.3 Vérifier

hostname          # → web01
hostname -f       # → web01.exemple.fr
hostname -d       # → exemple.fr
hostnamectl status

Si hostname -f répond autre chose que le FQDN attendu, la ligne 127.0.1.1 est mal ordonnée. Le changement est immédiat, mais reconnectez-vous pour que l'invite de commande l'affiche.

Sur certaines images cloud, cloud-init réécrit le nom d'hôte à chaque redémarrage. Si votre nom revient à sa valeur d'origine après un reboot :

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). Autant partir sur le bon format tout de suite.

sudo apt modernize-sources

La commande convertit /etc/apt/sources.list vers /etc/apt/sources.list.d/debian.sources et laisse au passage des sauvegardes /etc/apt/sources.list.bak et /etc/apt/sources.list.save, ce qui permet de revenir en arrière (procédure détaillée).

Vérifiez le résultat :

cat /etc/apt/sources.list.d/debian.sources

Vous devez y trouver trois blocs : trixie, trixie-updates et trixie-security, chacun avec un champ Signed-By.

Types: deb
URIs: http://deb.debian.org/debian/
Suites: trixie trixie-updates
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

Types: deb
URIs: http://security.debian.org/debian-security/
Suites: trixie-security
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

Si le dépôt trixie-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

sudo apt update
sudo apt full-upgrade -y
sudo apt autoremove --purge -y
sudo apt clean

full-upgrade et non upgrade : le premier accepte d'installer ou de retirer des paquets quand une dépendance a changé, ce qui est exactement ce qu'on veut sur une machine neuve. upgrade bloquerait sur ces cas.

4.3 Redémarrer si le noyau a changé

ls /var/run/reboot-required 2>/dev/null && echo "Redémarrage nécessaire"
sudo reboot

Après redémarrage, reconnectez-vous et vérifiez que tout est remonté :

systemctl is-system-running        # attendu : running
systemctl --failed                 # attendu : 0 loaded units listed
uptime

Un état degraded signale au moins un service en échec ; systemctl --failed vous dit lequel.

Étape 5 — Paquets de base

sudo apt install -y \
  vnstat iftop htop nano git \
  curl wget ca-certificates gnupg \
  dnsutils net-tools lsof tcpdump \
  rsync tree zip unzip \
  ncdu sysstat needrestart
Paquet À quoi il sert
vnstat Compteur de trafic réseau historisé, léger (vnstat -d, vnstat -m)
iftop Trafic en temps réel par connexion
htop Processus, CPU, mémoire, en interactif
ncdu Trouve ce qui remplit le disque, en navigation
sysstat sar, iostat, pidstat : historique de charge et d'E/S
needrestart Signale après chaque apt les services à redémarrer
dnsutils dig, nslookup pour déboguer la résolution
lsof / tcpdump Qui tient un fichier, que passe-t-il sur le réseau

Activer vnstat

sudo systemctl enable --now vnstat
vnstat --iflist            # vérifie l'interface détectée

Il faut attendre quelques heures avant que vnstat -d affiche des données utiles : le compteur démarre à zéro.

Ce que vous ne voulez probablement pas

Sur un serveur, allez au plus maigre. Chaque paquet installé est une surface d'attaque et une mise à jour de plus. Évitez les méta-paquets task-*, les environnements de bureau, et tout serveur (apache2, nginx, mariadb) tant que vous n'êtes pas à l'étape de déploiement des services.

sudo apt-mark showmanual | head -50    # ce qui a été installé explicitement

Étape 6 — Locale, fuseau horaire et clavier

6.1 Locale

Attention à l'orthographe : c'est fr_FR.UTF-8 avec un tiret, pas fr_FR.UTF_8. Un underscore produit une locale invalide, silencieusement ignorée.

Générez d'abord la locale, puis déclarez-la comme défaut système :

sudo sed -i 's/^# *\(fr_FR.UTF-8\)/\1/' /etc/locale.gen
sudo locale-gen
sudo localectl set-locale LANG=fr_FR.UTF-8

Variante non interactive, adaptée à un script :

echo "locales locales/locales_to_be_generated multiselect fr_FR.UTF-8 UTF-8, en_US.UTF-8 UTF-8" | sudo debconf-set-selections
echo "locales locales/default_environment_locale select fr_FR.UTF-8" | sudo debconf-set-selections
sudo rm -f /etc/locale.gen
sudo dpkg-reconfigure --frontend=noninteractive locales

LANG=fr_FR.UTF-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.

sudo localectl set-locale LANG=fr_FR.UTF-8 LC_MESSAGES=C.UTF-8

Reconnectez-vous, puis vérifiez :

locale              # aucune ligne "Cannot set LC_*"
localectl status

6.2 Fuseau horaire

sudo timedatectl set-timezone Europe/Paris
timedatectl list-timezones | grep Paris     # pour trouver un autre fuseau

Sur un parc international ou pour simplifier la lecture des journaux entre machines, 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.

sudo timedatectl set-ntp true
timedatectl status

Attendu : System clock synchronized: yes et NTP service: active.

Pour utiliser les serveurs français :

sudo mkdir -p /etc/systemd/timesyncd.conf.d
printf '[Time]\nNTP=0.fr.pool.ntp.org 1.fr.pool.ntp.org\nFallbackNTP=2.fr.pool.ntp.org 3.fr.pool.ntp.org\n' \
  | sudo tee /etc/systemd/timesyncd.conf.d/pool-fr.conf
sudo systemctl restart systemd-timesyncd
timedatectl timesync-status

6.4 Clavier (console uniquement)

Utile seulement si vous passez par la console KVM de l'hébergeur ; sans effet sur SSH.

sudo localectl set-keymap fr
sudo localectl set-x11-keymap fr

Étape 7 — Compte administrateur et sudo

7.1 Créer l'utilisateur

sudo adduser alix

Choisissez un mot de passe long et unique. Les champs « Nom complet », « N° de bureau », etc. peuvent rester vides : validez avec Entrée.

7.2 Accorder les droits sudo

Sous Debian, l'appartenance au groupe sudo suffit. Nul besoin d'éditer les sudoers pour un administrateur standard.

sudo usermod -aG sudo alix
id alix        # vérifie que "sudo" apparaît dans les groupes

Le -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 :

sudo visudo -f /etc/sudoers.d/10-alix
# Redémarrage de nginx sans mot de passe
alix ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
sudo chmod 0440 /etc/sudoers.d/10-alix
sudo visudo -c                  # contrôle global de la syntaxe

⚠️ 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) :

ssh alix@203.0.113.10
sudo -v              # doit demander VOTRE mot de passe et rendre la main
sudo whoami          # → root

Tant que ces trois commandes ne fonctionnent pas, n'allez pas plus loin : les étapes suivantes retirent votre accès actuel.

7.4 Confort de connexion (optionnel)

# Depuis votre poste : alias de connexion
cat >> ~/.ssh/config <<'EOF'
Host web01
    HostName web01.exemple.fr
    User alix
    Port 22
    IdentityFile ~/.ssh/id_ed25519
EOF

Vous vous connecterez ensuite avec ssh web01. Adaptez Port après l'étape 8 si vous le déplacez.

Étape 8 — Clés SSH et durcissement

C'est l'étape la plus risquée. Gardez une session ouverte du début à la fin.

8.1 Créer une paire de clés (sur votre poste, pas sur le serveur)

ssh-keygen -t ed25519 -C "alix@poste-bureau" -f ~/.ssh/id_ed25519

Ed25519 est le bon choix par défaut : court, rapide, sûr. Mettez une passphrase — elle protège la clé si votre poste est compromis, et ssh-agent évite de la retaper.

8.2 Déposer la clé publique

ssh-copy-id -i ~/.ssh/id_ed25519.pub alix@203.0.113.10

En manuel si ssh-copy-id n'est pas disponible :

ssh alix@203.0.113.10 "mkdir -p ~/.ssh && chmod 700 ~/.ssh"
cat ~/.ssh/id_ed25519.pub | ssh alix@203.0.113.10 "cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

Testez dans une nouvelle session : ssh alix@203.0.113.10 doit passer sans demander le mot de passe du compte.

8.3 Configurer sshd par fichier drop-in

Debian 13 lit /etc/ssh/sshd_config.d/*.conf. Écrivez votre configuration là plutôt que dans sshd_config : vos choix survivent aux mises à jour du paquet et se retirent en supprimant un seul fichier.

sudo nano /etc/ssh/sshd_config.d/99-durcissement.conf
# Authentification
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30

# Limiter qui peut se connecter
AllowUsers alix

# Réduire la surface
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
PermitUserEnvironment no

# Sessions mortes
ClientAliveInterval 300
ClientAliveCountMax 2

AllowUsers 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.

sudo sshd -t                         # syntaxe ; silence = OK
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauth|allowusers|^port'

Puis rechargez et testez depuis une nouvelle session :

sudo systemctl reload ssh

8.4 Changer le port : la spécificité Debian 13

Depuis Debian 12, SSH est démarré par activation à la demande via systemd (ssh.socket). Conséquence : une directive Port dans sshd_config peut rester sans effet, car c'est le socket systemd qui décide de l'écoute.

D'abord, constatez votre situation :

systemctl is-active ssh.socket       # active → c'est le socket qui commande
systemctl is-active ssh.service
ss -tlpn | grep -E 'ssh|:22'

Si ssh.socket est actif, modifiez le socket :

sudo systemctl edit ssh.socket
[Socket]
ListenStream=
ListenStream=0.0.0.0:2222
ListenStream=[::]:2222

La ligne ListenStream= vide est indispensable : elle efface le port 22 hérité. Sans elle, vous ajoutez 2222 sans retirer 22.

sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
ss -tlpn | grep 2222

Alternative — revenir au modèle classique, où sshd_config reprend la main :

sudo systemctl disable --now ssh.socket
sudo systemctl enable --now ssh.service

8.5 Faut-il vraiment changer de port ?

Ce n'est pas une mesure de sécurité, seulement de réduction du bruit : elle élimine les scans automatisés des journaux, pas un attaquant ciblé qui scannera vos 65 535 ports. Avec PasswordAuthentication no et fail2ban, le port 22 est déjà très défendable. Un port non standard complique en revanche le pare-feu, les sondes de supervision et les collaborateurs.

8.6 Vérification finale

Ouvrez une session neuve, sans fermer l'ancienne :

ssh -p 2222 alix@web01.exemple.fr
ssh -p 2222 debian@web01.exemple.fr     # doit être refusé
ssh -p 2222 root@web01.exemple.fr       # doit être refusé

Étape 9 — Neutraliser le compte fournisseur

Le compte debian livré par l'hébergeur est connu de tous et ciblé en premier par les scans. Une fois votre accès personnel prouvé, retirez-lui l'accès SSH.

9.1 Choisir le niveau de neutralisation

Niveau Commande Effet Quand
SSH bloqué AllowUsers alix (étape 8.3) Le compte existe, ne peut plus se connecter par SSH Cas général
Mot de passe verrouillé sudo passwd -l debian Plus d'authentification par mot de passe À combiner avec le précédent
Shell retiré sudo usermod -s /usr/sbin/nologin debian Plus aucune session interactive Compte purement technique
Compte supprimé sudo deluser --remove-home debian Disparu Seulement si rien ne dépend de lui

La combinaison recommandée est la plus conservatrice :

sudo passwd -l debian
sudo usermod -s /usr/sbin/nologin debian
sudo rm -f /home/debian/.ssh/authorized_keys

⚠️ Ne supprimez pas le compte avant d'avoir vérifié qu'aucun processus ne tourne sous son identité (ps -u debian) et qu'il n'est pas utilisé par l'outillage de l'hébergeur (agent de sauvegarde, cloud-init, console de secours). Certains hébergeurs recréent le compte à chaque redémarrage.

9.2 Le cas de root

sudo passwd -l root                 # verrouille le mot de passe root
sudo rm -f /root/.ssh/authorized_keys

Sur Debian, root n'a généralement pas de mot de passe utilisable en SSH dès lors que PermitRootLogin no est posé. Le verrouillage est une ceinture en plus des bretelles.

N'utilisez pas usermod -s /usr/sbin/nologin root : vous perdriez l'accès par la console de secours de l'hébergeur, votre dernier recours.

9.3 Inventaire des comptes

# Comptes capables de se connecter
awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $7}' /etc/passwd

# Membres du groupe sudo
getent group sudo

# Comptes sans mot de passe (doit être vide)
sudo awk -F: '$2 == "" {print $1}' /etc/shadow

# Clés SSH autorisées sur toute la machine
sudo find /home /root -name authorized_keys -exec ls -l {} \; -exec cat {} \;

La dernière commande mérite un passage attentif : une clé publique inconnue dans un authorized_keys est une porte dérobée.

Étape 10 — Pare-feu

Debian 13 utilise nftables comme moteur de filtrage. Deux approches : nftables directement, ou ufw qui l'utilise en coulisse avec une syntaxe plus simple.

10.1 Option A — ufw (recommandé si vous débutez)

sudo apt install -y ufw

# Politique par défaut : tout entrant bloqué, tout sortant autorisé
sudo ufw default deny incoming
sudo ufw default allow outgoing

# SSH EN PREMIER — sinon vous vous coupez l'accès à l'activation
sudo ufw limit 2222/tcp comment 'SSH'

# Services web, à n'ouvrir que le jour où vous les déployez
# sudo ufw allow 80/tcp comment 'HTTP'
# sudo ufw allow 443/tcp comment 'HTTPS'

sudo ufw enable
sudo ufw status verbose

limit plutôt que allow pour SSH : ufw bloque une IP qui dépasse six tentatives de connexion en 30 secondes. C'est une première barrière avant fail2ban.

10.2 Option B — nftables directement

Plus verbeux, mais sans couche intermédiaire et lisible d'un seul coup d'œil.

sudo nano /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset

table inet filter {
    chain input {
        type filter hook input priority filter; policy drop;

        ct state established,related accept
        ct state invalid drop
        iif lo accept

        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept

        tcp dport 2222 ct state new limit rate 6/minute accept
        # tcp dport { 80, 443 } accept

        counter
    }

    chain forward {
        type filter hook forward priority filter; policy drop;
    }

    chain output {
        type filter hook output priority filter; policy accept;
    }
}
sudo nft -c -f /etc/nftables.conf          # vérifie la syntaxe sans appliquer
sudo systemctl enable --now nftables
sudo nft list ruleset

⚠️ Ne laissez jamais policy 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.

# Avant d'activer les règles
sudo systemd-run --on-active=10min --unit=ufw-panic systemctl stop ufw

# Une fois la connexion reconfirmée depuis une nouvelle session
sudo systemctl stop ufw-panic.timer

10.4 Le pare-feu de l'hébergeur

Beaucoup d'hébergeurs interposent leur propre filtrage en amont (security group, « firewall network »). Un port ouvert sur la machine et fermé chez eux reste inaccessible — et l'inverse est vrai. Tenez les deux couches alignées, et documentez-les ensemble.

# Depuis un autre poste : ce qui est réellement joignable
nmap -Pn -p- web01.exemple.fr

Étape 11 — fail2ban

fail2ban lit les journaux et bannit temporairement les IP qui échouent trop souvent. Avec PasswordAuthentication no, son utilité sur SSH est surtout de nettoyer les journaux ; elle redevient essentielle dès que vous exposez un service authentifié par mot de passe.

sudo apt install -y fail2ban

Configuration

Ne modifiez jamais jail.conf : il est écrasé aux mises à jour. Créez jail.local.

sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 4
backend  = systemd
banaction = nftables-multiport
# Ne bannissez jamais votre propre IP fixe
ignoreip = 127.0.0.1/8 ::1 198.51.100.7

[sshd]
enabled = true
port    = 2222
mode    = aggressive

backend = systemd est important sous Debian 13 : les journaux passent par journald, pas par un fichier /var/log/auth.log garanti. banaction = nftables-multiport évite un conflit avec le moteur nftables du système.

Si vous avez choisi ufw à l'étape 10, utilisez plutôt banaction = ufw.

sudo systemctl enable --now fail2ban
sudo systemctl restart fail2ban

Exploitation au quotidien

sudo fail2ban-client status              # prisons actives
sudo fail2ban-client status sshd         # IP bannies
sudo fail2ban-client set sshd unbanip 198.51.100.7   # débannir
sudo fail2ban-client set sshd banip 203.0.113.99     # bannir à la main

⚠️ Mettez votre IP fixe dans 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.

sudo apt install -y unattended-upgrades apt-listchanges
sudo dpkg-reconfigure -plow unattended-upgrades

Configuration

sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

Lignes à vérifier ou décommenter :

Unattended-Upgrade::Origins-Pattern {
        "origin=Debian,codename=${distro_codename},label=Debian-Security";
        "origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};

Unattended-Upgrade::Mail "alix@exemple.fr";
Unattended-Upgrade::MailReport "on-change";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

Le choix structurant est Automatic-Reboot :

Valeur Conséquence
"false" Les correctifs noyau attendent un redémarrage manuel. Le serveur reste vulnérable en attendant, mais ne tombe jamais seul.
"true" Redémarrage automatique à 04:00 si nécessaire. Correctifs réellement actifs, au prix d'une coupure imprévisible.

Pour un serveur de production isolé, "true" avec une heure creuse est souvent le meilleur compromis. Pour un serveur critique sans redondance, gardez "false" et redémarrez lors d'une fenêtre planifiée.

Vérifier que ça tourne

sudo unattended-upgrade --dry-run --debug    # simulation détaillée
systemctl list-timers | grep -E 'apt|unattended'
cat /var/log/unattended-upgrades/unattended-upgrades.log

Deux minuteries doivent apparaître : apt-daily.timer (rafraîchissement des index) et apt-daily-upgrade.timer (installation).

Savoir quand redémarrer

cat /var/run/reboot-required.pkgs 2>/dev/null    # paquets concernés
sudo needrestart -b                              # services à relancer

Pensez à intégrer cette vérification à votre routine hebdomadaire.

Étape 13 — Swap, /tmp et paramètres noyau

13.1 Swap

Beaucoup de VPS sont livrés sans swap. Un peu de swap évite qu'un pic de mémoire déclenche le tueur de processus du noyau (OOM killer).

free -h                    # vérifier ce qui existe déjà
swapon --show

Si la ligne Swap est à zéro :

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Dimensionnement raisonnable : 2 Go jusqu'à 4 Go de RAM, puis 4 Go au-delà. Inutile d'aller au-delà sur un serveur : si vous swappez beaucoup, il faut de la RAM, pas du disque.

# Réduire l'agressivité du swap sur un serveur
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf

Sur SSD NVMe, le swap est peu coûteux. Sur un disque réseau facturé aux IOPS, il peut coûter cher et ralentir fortement.

13.2 /tmp est désormais en RAM

Changement notable de Debian 13 : /tmp est monté en tmpfs, donc en mémoire, et vidé à chaque redémarrage (note de version).

df -h /tmp
findmnt /tmp

Deux conséquences à connaître :

  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.
# Retrouver et nettoyer l'ancien /tmp masqué
sudo mkdir -p /mnt/racine
sudo mount --bind / /mnt/racine
ls -lha /mnt/racine/tmp/
# ... supprimez ce qui doit l'être ...
sudo umount /mnt/racine && sudo rmdir /mnt/racine

Pour repasser /tmp sur disque si votre usage le demande :

sudo systemctl mask tmp.mount
sudo reboot

13.3 Paramètres réseau et noyau

sudo nano /etc/sysctl.d/99-durcissement.conf
# Anti-usurpation d'adresse
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignorer le routage par la source et les redirections ICMP
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0

# Journaliser les paquets manifestement usurpés
net.ipv4.conf.all.log_martians = 1

# Résistance aux SYN flood
net.ipv4.tcp_syncookies = 1

# Limiter la lecture des informations noyau
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2

# Protection des liens en répertoires partagés
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
sudo sysctl --system
sudo sysctl -a --pattern 'tcp_syncookies|rp_filter'   # contrôle

N'appliquez pas net.ipv4.ip_forward = 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.

sudo nano /etc/systemd/journald.conf.d/00-limites.conf
[Journal]
Storage=persistent
SystemMaxUse=500M
SystemMaxFileSize=50M
MaxRetentionSec=1month
sudo mkdir -p /etc/systemd/journald.conf.d
sudo systemctl restart systemd-journald
journalctl --disk-usage

14.2 Commandes de lecture utiles

journalctl -p err -b                     # erreurs depuis le démarrage
journalctl -u ssh --since "1 hour ago"   # un service, une fenêtre de temps
journalctl -f                            # en direct
journalctl --since yesterday | grep -i "failed password"
last -20                                 # dernières connexions réussies
lastb -20                                # dernières tentatives échouées

14.3 Recevoir les alertes par mail

Sans MTA, les rapports de unattended-upgrades, cron et fail2ban sont écrits mais jamais lus. Un relais léger suffit — n'installez pas un serveur de mail complet.

sudo apt install -y msmtp-mta bsd-mailx
sudo nano /etc/msmtprc
defaults
auth           on
tls            on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile        /var/log/msmtp.log

account        default
host           smtp.exemple.fr
port            587
from           web01@exemple.fr
user           web01@exemple.fr
password       VOTRE_MOT_DE_PASSE
sudo chmod 600 /etc/msmtprc
echo "Test depuis web01" | mail -s "Test alerte" alix@exemple.fr

Le fichier contient un mot de passe en clair : chmod 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 :

echo "root: alix@exemple.fr" | sudo tee -a /etc/aliases
sudo newaliases 2>/dev/null || true

14.4 Surveiller l'essentiel

# Trafic réseau
vnstat -d                    # par jour
vnstat -m                    # par mois, utile face à un quota

# Disque
df -h
sudo ncdu /                  # exploration interactive

# Charge et E/S
uptime
iostat -x 2 5
sar -u 1 5

Une alerte simple sur le disque, via cron, vaut mieux qu'une supervision compliquée jamais installée :

sudo tee /etc/cron.daily/alerte-disque >/dev/null <<'EOF'
#!/bin/sh
SEUIL=85
USAGE=$(df / --output=pcent | tail -1 | tr -dc '0-9')
[ "$USAGE" -ge "$SEUIL" ] && echo "Disque / à ${USAGE}% sur $(hostname -f)" \
  | mail -s "ALERTE disque $(hostname)" root
exit 0
EOF
sudo chmod +x /etc/cron.daily/alerte-disque

Étape 15 — Sauvegarde et instantané de référence

15.1 Instantané immédiat

Vous venez de construire une base propre. Prenez-en un instantané (snapshot) chez votre hébergeur maintenant, avant de déployer le moindre service. C'est votre point de retour : une mauvaise manipulation plus tard se résoudra en trois minutes au lieu d'une réinstallation complète.

15.2 Archiver la configuration

sudo tar czf /root/config-initiale-$(date +%F).tar.gz \
  /etc/ssh/sshd_config.d /etc/hosts /etc/hostname \
  /etc/apt/sources.list.d /etc/sudoers.d \
  /etc/fail2ban/jail.local /etc/nftables.conf \
  /etc/sysctl.d /etc/default/locale 2>/dev/null

Récupérez l'archive sur votre poste :

scp -P 2222 alix@web01.exemple.fr:/root/config-initiale-*.tar.gz ./

Mieux encore : versionnez ces fichiers dans un dépôt Git privé — sans les secrets. 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.

sudo apt install -y restic
# Initialisation vers un stockage distant (une seule fois)
export RESTIC_REPOSITORY="sftp:sauvegarde@stockage.exemple.fr:/backups/web01"
export RESTIC_PASSWORD_FILE="/root/.restic-pass"
sudo restic init

# Sauvegarde
sudo restic backup /etc /home /var/www --exclude-caches

# Vérification et purge
sudo restic snapshots
sudo restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

Automatisez avec une minuterie systemd plutôt qu'un cron : les échecs remontent dans journalctl.

sudo systemd-run --on-calendar="*-*-* 03:00:00" --unit=sauvegarde \
  /usr/bin/restic backup /etc /home /var/www

15.4 Tester la restauration

À faire une fois, tout de suite, puis tous les trimestres :

sudo restic restore latest --target /tmp/test-restauration --include /etc/hosts
cat /tmp/test-restauration/etc/hosts

Notez la date du dernier test réussi quelque part de visible. C'est la seule métrique qui compte vraiment.

Checklist récapitulative

Préparatifs

  • Accès de secours hors SSH vérifié (console KVM, mode rescue)
  • Enregistrement A (et AAAA) créé vers le FQDN
  • Reverse DNS (PTR) aligné sur le FQDN, IPv4 et IPv6
  • 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

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