Détecter une attaque DDoS : comment en avoir la certitude
Toute surcharge n'est pas une attaque. Voici comment distinguer sans ambiguïté une attaque DDoS d'un pic de charge ou d'un bug logiciel, avec ss, les compteurs de paquets, les messages du noyau et les logs du serveur web.
Le service ne répond plus, l'indicateur de charge est au plafond, et la question tombe dans le chat : est-ce qu'on nous attaque ? Cette question se mesure. Elle se mesure même assez vite, à condition de savoir quels quatre chiffres regarder, dans quel ordre, et quelle contre-vérification transforme un soupçon en diagnostic.
Cet article traite uniquement du diagnostic. Il ne s'agit pas de repousser une attaque, mais de la distinguer sans ambiguïté d'un problème de charge, d'un bug logiciel ou tout simplement du succès. Si vous voulez savoir ce qu'est techniquement une attaque DDoS, la réponse est dans Qu'est-ce qu'une attaque DDoS. Si vous voulez agir une fois le diagnostic posé, la suite se trouve dans Protéger son serveur des attaques DDoS.
Quatre suspects, un seul symptôme
« Le serveur est lent » n'est pas un diagnostic, mais un symptôme avec au moins quatre causes plausibles. Avant de taper la moindre commande, vous devriez savoir quels schémas vous cherchez réellement à distinguer.
| Observation | Attaque | Véritable afflux de visiteurs | Bug logiciel |
|---|---|---|---|
| Début | brutal, en quelques secondes | montée sur plusieurs minutes, souvent en courbe | juste après un déploiement, un cronjob ou une mise à jour |
| Charge réseau entrante | élevée à extrême, souvent beaucoup de petits paquets | modérée, le sortant nettement supérieur à l'entrant | rien de particulier |
| Connexions par IP source | très nombreuses depuis quelques IP, ou très peu depuis un très grand nombre d'IP | réparties uniformément, peu par IP | normal |
| Referrer dans le log | le plus souvent vide | sites d'actualité, réseaux sociaux, moteurs de recherche | normal |
| Réponse via 127.0.0.1 | rapide (le problème est le réseau) | lente (le problème est l'application) | lente ou en erreur |
| Après redémarrage du service | la charge revient aussitôt | la charge revient, le symptôme reste identique | le problème disparaît souvent pendant plusieurs minutes |
Le quatrième suspect ne figure pas dans ce tableau, parce qu'il n'a pas besoin de sa propre colonne : les travaux planifiés. Sauvegardes, réindexations, rotations de logs et mises à jour de paquets tournent à des horaires prévisibles. Un coup d'œil sur systemctl list-timers et sur les crontabs coûte dix secondes et clôt un nombre étonnant de suspicions d'attaque.
Les 60 premières secondes : quatre chiffres
Relevez quatre valeurs, dans cet ordre. C'est leur combinaison qui est parlante, aucune valeur prise isolément ne l'est.
cat /proc/loadavg
ss -s
cat /proc/net/dev
curl -o /dev/null -s -w '%{time_total}\n' http://127.0.0.1/
L'interprétation :
- Charge élevée, charge réseau élevée, beaucoup de connexions semi-ouvertes, mais 127.0.0.1 répond en quelques millisecondes : le problème se situe en amont de l'application, donc au niveau réseau. C'est l'image classique d'une attaque.
- Charge élevée, charge réseau normale, 127.0.0.1 répond lentement : application ou base de données. Pas d'attaque, mais du travail pour le développeur.
- Charge faible, charge réseau élevée : très suspect. Une tempête de paquets qui n'atteint même pas l'application consomme peu de CPU et beaucoup de bande passante.
- Tout est au plus bas et le service reste malgré tout injoignable depuis l'extérieur : voyez plus bas, la section sur les cas sans trace mesurable.
Une précision sur la valeur de charge elle-même : /proc/loadavg compte aussi les processus qui attendent des entrées et sorties. Une valeur de 40 sur quatre cœurs peut aussi bien être une attaque qu'un disque saturé. vmstat 1 5 fait proprement la part des choses : la colonne r montre les processus prêts à s'exécuter, b ceux qui sont bloqués et wa la part de temps d'attente.
Compter les connexions : ss plutôt que netstat
Presque tous les tutoriels que l'on trouve en ligne commencent par netstat. Sur un système récent, cela se termine ainsi :
Command 'netstat' not found, but can be installed with:
apt install net-tools
netstat fait partie du paquet net-tools, qui n'est plus inclus dans les installations standard de Debian 12, Debian 13, Ubuntu 22.04 et Ubuntu 24.04. Il ne l'est pas davantage dans la famille Red Hat (AlmaLinux, Rocky, RHEL, Oracle Linux). Vous pouvez l'installer après coup, mais ss, issu de iproute2, est plus judicieux : il est présent sur pratiquement tous les serveurs, nettement plus rapide quand les connexions sont nombreuses et fournit exactement la même information.
ss -s
Pour que toutes les commandes qui suivent fonctionnent, il vous faut une poignée de paquets, dont les noms changent selon la famille de distribution. C'est l'endroit où un tutoriel copié-collé bloque le plus souvent dès la première ligne sur AlmaLinux :
| Outil | Debian et Ubuntu | AlmaLinux, Rocky, Oracle Linux |
|---|---|---|
| ss, nstat, ip | iproute2 | iproute |
| netstat | net-tools | net-tools |
| vmstat, free, top | procps | procps-ng |
| sar | sysstat | sysstat |
| dig | dnsutils | bind-utils |
| tcpdump | tcpdump | tcpdump |
Sur Debian et Ubuntu, donc apt-get install -y iproute2 net-tools procps sysstat dnsutils tcpdump, et sur la famille Red Hat dnf -y install iproute net-tools procps-ng sysstat bind-utils tcpdump. Mieux vaut ne pas y ajouter curl : il est déjà installé sous la forme curl-minimal, et le paquet complet entre en conflit avec lui (curl-minimal ... conflicts with curl). Si vous en avez malgré tout besoin, dnf -y --allowerasing install curl règle la question.
La première ligne donne le nombre total de sockets, la ligne TCP détaille : estab, closed, orphaned, timewait. Une valeur timewait élevée n'est pas à elle seule un indice d'attaque, c'est la conséquence normale de nombreuses connexions HTTP courtes.
La répartition entre les états est plus instructive :
ss -Htan | awk '{print $1}' | sort | uniq -c | sort -rn
Ce qui doit vous alerter, c'est un gros bloc en SYN-RECV. Ces connexions ont été ouvertes mais jamais confirmées, et c'est exactement la signature d'un SYN flood. Le comptage est direct :
ss -Htn state syn-recv | wc -l
Le piège des colonnes, qui fait échouer la plupart des one-liners
Dès que vous passez un filtre d'état à ss, la colonne d'état disparaît de la sortie. L'adresse distante se retrouve alors en colonne 4 et non en colonne 5. C'est précisément pour cette raison que les one-liners copiés depuis les forums renvoient régulièrement n'importe quoi : ils comptent des ports au lieu d'adresses IP, ou produisent des lignes vides. À retenir : sans filtre, l'adresse distante est $5, avec filtre $4. Le -H supprime en plus la ligne d'en-tête, si bien que wc -l donne le bon compte sans correction.
Connexions par IP source, compatible IPv6 :
ss -Htn state established | awk '{print $4}' | sed 's/:[^:]*$//' | sort | uniq -c | sort -rn | head -20
Le sed ne coupe que le dernier deux-points et le port qui le suit. L'approche répandue avec cut -d: -f1 fonctionne en IPv4, mais découpe les adresses IPv6 après le premier bloc et rend l'analyse inutilisable. En IPv6, les crochets restent en place, ce qui ne gêne pas le comptage.
L'interprétation demande du discernement. Cent connexions depuis une seule IP peuvent être une attaque, mais tout aussi bien un NAT d'entreprise, un NAT d'opérateur mobile ou un reverse proxy placé devant votre serveur. Si un réseau de diffusion de contenu ou un load balancer se trouve en amont, vous ne voyez de toute façon que ses adresses et devez vous rabattre sur X-Forwarded-For dans le log du serveur web.
Pour l'UDP, le même schéma s'applique avec -u. Et pour savoir tout simplement quel service écoute sur quel port :
ss -tulnp
Mesurer correctement la charge réseau et le taux de paquets
La bande passante en mégabits est le chiffre que tout le monde cite. Le plus parlant est le taux de paquets. Une attaque à 200 000 paquets minuscules par seconde met un serveur à genoux alors que la bande passante paraît inoffensive.
D'abord le nom de l'interface, car eth0 est loin d'être la bonne réponse partout. Les noms courants sont ens3, enp1s0 ou eth0 :
ip -br link
Ensuite deux mesures espacées d'une seconde, sans aucun paquet supplémentaire. Le nom de l'interface est récupéré depuis la route par défaut, plutôt qu'écrit en dur :
IF=$(ip -o route get 1.1.1.1 | awk '{print $5}')
A=$(cat /sys/class/net/$IF/statistics/rx_packets) || exit 1; sleep 1; B=$(cat /sys/class/net/$IF/statistics/rx_packets); echo "$((B-A)) paquets/s entrants sur $IF"
Cette lourdeur a une raison très concrète. Si vous inscrivez eth0 en dur alors que l'interface s'appelle en réalité ens3 ou enp1s0, comme c'est le cas sur la plupart des serveurs KVM, la commande affiche certes deux fois cat: /sys/class/net/eth0/statistics/rx_packets: No such file or directory, mais annonce ensuite tranquillement 0 paquets/s entrants et se termine avec le code de retour 0. Dans un article sur la détection d'attaques, c'est la variante la plus dangereuse de toutes : vous lisez zéro paquet, vous donnez le feu vert, et l'attaque continue. Le || exit 1 évite précisément cela.
La même chose avec rx_bytes donne les octets par seconde. À partir des deux valeurs, vous calculez la taille moyenne des paquets, et celle-ci trahit le type d'attaque :
- moins de 100 octets en moyenne avec un taux de paquets très élevé : SYN flood, ACK flood ou UDP flood. La cible est le traitement des paquets, pas la bande passante.
- 1200 à 1500 octets avec une bande passante élevée, ports source 53, 123, 389 ou 11211 : attaque par réflexion et amplification. Les IP sources sont des serveurs tiers, pas les attaquants.
- répartition de tailles normale, requêtes HTTP propres : couche applicative. C'est alors le log du serveur web qui tranche, pas le compteur de paquets.
C'est plus confortable avec sar, issu du paquet sysstat :
sar -n DEV 1 3
Piège : la mesure en direct fonctionne dès l'installation, mais pas l'analyse historique avec sar -f. Sur Debian et Ubuntu, la collecte de données est désactivée d'usine, /etc/default/sysstat contient ENABLED="false". Si vous ne le remarquez qu'au moment de l'attaque, vous n'avez aucune valeur de comparaison de l'avant-veille. C'est la raison d'activer ce paquet par précaution, et non au moment critique.
Et la limite la plus importante de toutes : sur le serveur, vous ne mesurez que ce qui est passé. Si un filtrage se trouve en amont, vous ne voyez qu'une fraction du volume réel, voire rien du tout. Le chiffre fiable se trouve dans le graphique de trafic de l'espace client, pas dans /proc/net/dev.
Lire les messages du noyau
Le noyau (kernel) journalise des situations de surcharge qui restent invisibles au niveau applicatif. Sur Ubuntu et sur les versions récentes de Debian, le tampon circulaire est verrouillé pour les utilisateurs ordinaires, et sans sudo vous obtenez :
dmesg: read kernel buffer failed: Operation not permitted
Ce n'est pas un dysfonctionnement, mais kernel.dmesg_restrict=1. Avec les droits root et des horodatages :
dmesg -T | grep -Ei 'syn flood|conntrack|neighbour|drop'
Le message que la plupart des gens cherchent se présente littéralement ainsi :
TCP: request_sock_TCP: Possible SYN flooding on port 443. Sending cookies. Check SNMP counters.
Il en existe deux variantes, plus une différence de format qu'il vaut mieux connaître :
- Sending cookies signifie que les SYN cookies sont actifs et que les connexions continuent d'être servies. Dropping request apparaît à la place lorsque
net.ipv4.tcp_syncookiesest à 0. Les demandes sont alors rejetées, celles de vrais visiteurs comprises. - Les noyaux anciens n'indiquent que le numéro de port, les plus récents y ajoutent l'adresse d'écoute sous la forme
0.0.0.0:443. Sur Debian 12, vous voyez donc la forme courte, sur Debian 13 et Ubuntu 24.04 la forme longue. Si vous faites un grep sur l'ancienne formulation exacte, vous ne trouvez rien sur les systèmes récents.
Important pour l'honnêteté du diagnostic : ce message ne prouve aucune attaque. Il apparaît aussi quand une application travaille avec un backlog d'écoute trop petit et qu'une poussée de charge légitime la submerge. C'est un indice, qui doit être étayé par d'autres mesures.
Les compteurs correspondants sont fournis par nstat :
nstat -az TcpExtSyncookiesSent
nstat -az TcpExtListenDrops
nstat -az TcpExtListenOverflows
Là encore, un piège : nstat mémorise un état intermédiaire à chaque appel et n'affiche ensuite plus que la différence. C'est voulu, et même pratique pour la mesure, mais cela surprend quiconque voit soudain des zéros au deuxième appel. Avec -a, vous forcez les valeurs absolues, avec -s vous empêchez la mise à jour de l'état mémorisé.
Deux autres messages qui apparaissent régulièrement pendant une attaque et qui entraînent tous deux des pertes de paquets pour les visiteurs légitimes :
nf_conntrack: table full, dropping packet
neighbour: arp_cache: neighbor table overflow!
Vous vérifiez le niveau de remplissage du suivi de connexions avec cat /proc/sys/net/netfilter/nf_conntrack_count, comparé à nf_conntrack_max. Ces deux fichiers n'existent que si le module est chargé, donc si un pare-feu est actif. Sur un système sans règles, ils sont absents, et c'est normal.
Logs du serveur web : des schémas plutôt qu'une intuition
Sur Debian et Ubuntu, les logs se trouvent dans /var/log/nginx/access.log ou /var/log/apache2/access.log. Dans la famille Red Hat, le chemin Apache est /var/log/httpd/access_log, sans point avant le suffixe. Trois analyses suffisent dans un premier temps :
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
La distinction entre attaque et véritable afflux ressort du rapport entre ces trois listes :
- Rapport entre requêtes et IP distinctes. Dix mille requêtes venant de 6 000 adresses, c'est du public. Dix mille requêtes venant de 30 adresses, non.
- Requêtes secondaires. Un vrai visiteur charge, après la page HTML, des feuilles de style, des scripts et des images. Si la liste des chemins ne contient qu'une seule URL et pas le moindre fichier statique, aucun navigateur n'est passé par là.
- Provenance. En cas de succès publicitaire, le champ Referrer indique une source : un site d'actualité, un réseau social, un moteur de recherche. Lors d'une attaque, ce champ est typiquement vide ou rempli d'une adresse inventée.
- Identifiant du programme. Un seul User-Agent, strictement identique sur des dizaines de milliers de requêtes, c'est un outil. Les identifiants de versions de navigateur très anciennes sont également suspects.
- Codes de statut. Une majorité de 200 plaide pour du public, un mur de 404 ou de 499 pour un balayage automatisé dans le premier cas, pour des clients qui coupent avant la réponse dans le second.
Prudence avec les moteurs de recherche : une IP prétend volontiers, dans son User-Agent, être Googlebot. La seule façon de le vérifier est une résolution inverse suivie d'une résolution directe. Ce n'est que lorsque le nom inverse pointe vers un domaine du fournisseur et que ce nom se résout à nouveau vers la même IP que l'affirmation tient.
dig -x 66.249.66.1 +short
Pour une véritable adresse Googlebot, vous récupérez un nom du type crawl-66-249-66-1.googlebot.com., que vous faites ensuite résoudre vers la même IP avec dig +short crawl-66-249-66-1.googlebot.com. Si rien ne revient, cela peut aussi tenir à un serveur sans résolution DNS sortante, et cela ne prouve alors rien.
Et la phrase la plus importante au sujet des logs : une attaque au niveau réseau n'apparaît pas dans le log du serveur web. Un SYN flood n'atteint jamais l'application et n'y laisse pas la moindre ligne. Un log vide ne réfute donc rien du tout, il ne fait que délimiter la couche concernée.
La contre-vérification : comment un soupçon devient un diagnostic
Jusqu'ici, vous avez des indices. Ils deviennent solides grâce à des contre-vérifications, dont chacune est capable de réfuter exactement une hypothèse.
- Arrêter le service. Arrêtez le serveur web pendant 30 secondes. Si le taux de paquets entrants reste tout aussi élevé, l'attaque se situe en dessous de l'application. S'il s'effondre, il s'agissait de requêtes vers votre application, malveillantes ou non.
- L'intérieur contre l'extérieur. Si
curlrépond en quelques millisecondes via 127.0.0.1 alors que l'appel depuis l'extérieur part en dépassement de délai, l'application est saine et le problème vient de la liaison réseau. - Deuxième mesure. Répétez la mesure de paquets deux minutes plus tard. Les attaques durent ou reviennent par vagues. Un pic isolé n'était qu'un pic.
- Le regard extérieur. Un test d'accessibilité externe depuis plusieurs emplacements sépare « injoignable pour tout le monde » de « injoignable pour vous seul ». Le second cas est le plus souvent un problème de routage ou de fournisseur d'accès côté observateur, pas une attaque.
- Vérifier le sens. Comparez
rx_packetsettx_packets. Si c'est le taux sortant qui est anormal, vous n'êtes pas attaqué : c'est votre serveur qui attaque. Il est alors compromis ou détourné comme réflecteur, et l'urgence du dossier change aussitôt.
Voici comment savoir que le diagnostic tient : vous pouvez dire en une phrase quel protocole frappe quel port et à quel taux, dans quel sens, et vous disposez de deux mesures indépendantes qui montrent la même chose. Tout ce qui reste en deçà est une supposition.
Quand il n'y a aucune trace mesurable
Quatre situations qui sèment régulièrement la confusion :
- Vous n'accédez plus du tout au serveur. Quand la liaison est saturée, SSH ne passe plus lui non plus. L'accès se fait alors par la console de l'espace client, qui fonctionne indépendamment de l'uplink du système. C'est exactement à cela qu'elle sert.
- Le service a disparu, mais le serveur n'en garde aucune trace. Avec un filtrage en amont, c'est la règle et non l'exception. Si l'attaque est interceptée dans le réseau, le système ne voit qu'un bref creux. La preuve figure alors dans le graphique de trafic de l'espace client.
- Le log s'arrête au milieu de l'incident. Vérifiez si une rotation des logs a eu lieu entre-temps : le matériel plus ancien se trouve à côté, sous la forme
access.log.1ouaccess.log.2.gz. Siaccess_logest désactivé ou mis en tampon dans la configuration, les dernières lignes manquent tout simplement. - Le noyau reste muet. Sur les systèmes à base de conteneurs, le tampon circulaire appartient au système hôte et
dmesgn'y montre rien qui vous soit propre. Sur un serveur root KVM doté de son propre noyau, la question ne se pose pas.
Ce qu'il faut documenter avant de contacter votre hébergeur
Un signalement du type « le serveur était lent cet après-midi » allonge le traitement de plusieurs allers-retours. Un signalement accompagné de mesures est traité tout de suite. Collectez pendant que l'incident est en cours, car les compteurs de /proc sont remis à zéro au redémarrage.
mkdir -p /root/incident && cd /root/incident
date -u > 01-heure.txt
ss -s > 02-sockets.txt
ss -Htan | awk '{print $1}' | sort | uniq -c | sort -rn > 03-etats.txt
ss -Htn state established | awk '{print $4}' | sed 's/:[^:]*$//' | sort | uniq -c | sort -rn | head -50 > 04-top-ips.txt
ip -s link > 05-interfaces.txt
sar -n DEV 1 10 > 06-taux-paquets.txt
dmesg -T | tail -100 > 07-kernel.txt
nstat -az > 08-compteurs.txt
Si la liaison le permet, une courte capture de paquets fait partie du lot. Limitez-la, car une capture sans borne sur une liaison saturée remplit le stockage en quelques minutes et aggrave le problème :
tcpdump -D
tcpdump -ni "$IF" -s 96 -c 2000 -w /root/incident/capture.pcap
tcpdump -D liste les interfaces disponibles, au cas où la variable $IF de la section de mesure plus haut ne serait plus définie. Les deux appels demandent les droits root.
Le ticket doit ensuite contenir :
- L'heure de début et de fin, avec le fuseau horaire.
date -uévite toute discussion à ce sujet. - L'adresse IP et le port concernés.
- Le taux de paquets et la bande passante mesurés, avec le sens explicitement indiqué.
- La répartition par protocole et, si elle est identifiable, les ports source des machines distantes.
- Une poignée d'adresses sources en exemple, en précisant que les expéditeurs peuvent être falsifiés.
- Un extrait du log du serveur web avec le schéma de requêtes récurrent, trois à cinq lignes suffisent.
- Ce qui a été modifié juste avant : déploiement, changement DNS, campagne publicitaire, nouvelle ouverture de port.
- Le résultat de la contre-vérification : le service était-il joignable via 127.0.0.1 alors qu'il était hors service depuis l'extérieur ?
Erreurs de raisonnement fréquentes
- Prendre de nombreuses connexions TIME-WAIT pour une preuve d'attaque. Elles sont la conséquence normale de connexions HTTP courtes et disparaissent d'elles-mêmes.
- Prendre le message SYN flooding pour une preuve. Un backlog d'écoute configuré trop petit le produit aussi lors d'une poussée de charge légitime.
- Bloquer une IP qui a beaucoup de connexions sans rien vérifier. Derrière un NAT d'entreprise, un NAT mobile ou un proxy en amont, vous excluez ainsi des groupes entiers de clients.
- Conclure à une attaque à partir d'une bande passante élevée. Vérifiez d'abord le sens. Les pics sortants sont le plus souvent une sauvegarde ou un téléchargement populaire.
- Conclure à « aucune attaque » à partir de logs vides. Les attaques au niveau réseau n'atteignent jamais l'application.
- Redémarrer pendant que l'incident est en cours. Le redémarrage efface tous les compteurs dont vous auriez eu besoin pour le signalement, et l'attaque se poursuit ensuite sans changement.
Une fois cette séquence répétée, poser un diagnostic solide prend moins de cinq minutes. Et si vous joignez les mesures au ticket, vous évitez les allers-retours et arrivez directement à la solution. Dans le même esprit : la checklist pour un nouveau serveur root et configurer fail2ban pour le suivi au niveau applicatif.
Questions fréquentes
Comment distinguer une attaque DDoS d'un véritable afflux de visiteurs ?
Pourquoi netstat n'est-il pas installé sur mon serveur ?
Le message Possible SYN flooding on port 443 signifie-t-il toujours une attaque ?
Pourquoi n'y a-t-il aucune trace sur le serveur alors que le service a été brièvement injoignable ?
Comment accéder au serveur si SSH ne répond plus pendant l'incident ?
Pourquoi mon one-liner ss copié renvoie-t-il de mauvais chiffres ?
Que faire si le taux de paquets sortants est anormal ?
2026 KernelHost GmbH. Tous droits réservés. Ce guide est protégé par le droit d'auteur. Sa republication sur d'autres sites web, même partielle ou sous une forme modifiée, n'est pas autorisée sans notre accord écrit. Les citations accompagnées de la source et d'un lien sont expressément les bienvenues.

