Administration Linux

Logs et dépannage sous Linux : journalctl, rotation, OOM, charge et strace

Avancé12 min de lectureMis à jour

Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise

Sous pression, la plupart des gens dépannent au hasard et redémarrent. Ce guide remplace ces réflexes par une méthode : établir ce qui a changé, lire le bon log, puis mesurer une ressource à la fois. Il couvre journald et les fichiers de /var/log, la rotation et la rétention des logs, ainsi que les pannes récurrentes — disque plein, OOM killer, charge élevée avec des CPU au repos, processus bloqués et DNS — avec, pour chacune, la commande qui permet de l'identifier.

Ce que vous allez apprendre

  • Appliquer une séquence de tri reproductible au lieu de redémarrer les services en croisant les doigts
  • Interroger le journal par unité, priorité, boot et plage horaire, et savoir ce qui écrit encore dans /var/log
  • Configurer la rotation des logs et la rétention du journal pour que l'historique ne remplisse jamais le disque
  • Distinguer saturation CPU, attente d'I/O, pression mémoire et OOM kills
  • Inspecter un processus actif ou bloqué avec lsof, strace, /proc et dmesg

Le dépannage sous Linux est une compétence qui obéit à une structure. Ceux qui vont vite ne connaissent pas plus de commandes que les autres : ils suivent une séquence, écartent une couche après l'autre et lisent le log avant d'échafauder une théorie. Ceux qui vont lentement redémarrent le service, voient le symptôme revenir, redémarrent encore, et ne regardent les logs qu'au bout d'une heure.

Ce guide porte sur cette séquence. Il explique où partent réellement les logs sur un hôte systemd, comment les interroger, comment les empêcher de remplir le disque, puis passe en revue les pannes qui reviennent sur tous les serveurs : plus d'espace, plus de mémoire, de la charge sans CPU, un processus qui ne répond plus et une dépendance qui a disparu. Il suppose que vous connaissez déjà les processus et systemd ainsi que l'ordre de diagnostic décrit dans les bases du réseau.

Trier avant de sortir les outils

Avant de taper quoi que ce soit, répondez à trois questions. Quel est le symptôme réel — une erreur précise, un timeout, un résultat faux — plutôt que « le site est en panne » ? Quand a-t-il commencé, et coïncide-t-il avec un déploiement, un changement de configuration, l'expiration d'un certificat, une tâche planifiée ou une hausse du trafic ? Quelle est l'étendue des dégâts — un endpoint, un hôte, un client, tout ?

Parcourez ensuite les couches dans un ordre fixe, en vous arrêtant à la première qui pose problème :

  1. Le service tourne-t-il, et a-t-il redémarré récemment ? systemctl status, systemctl show -p NRestarts.
  2. Que dit son log autour de l'heure de début ? journalctl -u … --since.
  3. Une ressource est-elle épuisée ? Disque, inodes, mémoire, descripteurs de fichiers, connexions.
  4. Ses dépendances sont-elles en bonne santé ? Base de données, cache, API en amont, DNS.
  5. Quelque chose a-t-il changé ? journalctl -u … --since, historique des paquets, configuration sous contrôle de version.

Notez au fil de l'eau ce que vous vérifiez et ce que vous trouvez. Dix minutes après le début d'un incident, plus personne ne se souvient si le disque a déjà été regardé, et une note partagée fait toute la différence entre deux personnes qui enquêtent et deux personnes qui font le même travail.

Où partent les logs

Sur un hôte systemd coexistent deux mondes de logs, et vous avez besoin des deux.

Le journal recueille tout ce qu'une unité écrit sur sa sortie standard et sa sortie d'erreur, les messages du noyau et tout ce qui passe par l'API syslog. C'est un stockage binaire structuré : chaque entrée porte des champs comme _SYSTEMD_UNIT, _PID, _UID, PRIORITY, _BOOT_ID et MESSAGE, ce qui rend possible un filtrage précis. Il réside dans /run/log/journal (volatil, perdu au redémarrage) ou dans /var/log/journal (persistant) selon la valeur de Storage= dans /etc/systemd/journald.conf. Si journalctl -b -1 ne renvoie aucune entrée, le stockage persistant est désactivé ; créez /var/log/journal et redémarrez systemd-journald pour l'activer.

Les fichiers texte sous /var/log sont écrits par les programmes qui gèrent eux-mêmes leur journalisation : /var/log/apache2/, /var/log/nginx/, /var/log/mysql/, /var/log/audit/audit.log. Les distributions qui font encore tourner rsyslog reçoivent aussi une copie du journal, ce qui produit /var/log/syslog et /var/log/messages, plus /var/log/auth.log ou /var/log/secure pour les événements d'authentification.

Savoir ce qui va où fait gagner du temps. Une page PHP en erreur se lit dans le log d'erreurs du pool FPM et dans celui du virtual host Apache, pas dans le journal ; un service qui refuse de démarrer se lit dans le journal, pas dans un fichier que contrôle l'application.

Interroger le journal

journalctl -u nginx.service -n 100 --no-pager
journalctl -u orders-api -f                       # follow
journalctl -u orders-api --since "2026-09-07 14:00" --until "2026-09-07 14:30"
journalctl -p err -b                              # this boot, error and worse
journalctl -b -1 -p warning                       # previous boot
journalctl -k --since "30 min ago"                # kernel ring buffer, time-filtered
journalctl _UID=33 --since today                  # everything by one user id
journalctl _PID=1842
journalctl -u orders-api -o json-pretty | head -40
journalctl --list-boots

Les priorités vont de emerg (0) à debug (7), et -p err signifie « err et plus grave ». Les expressions temporelles acceptent "10 min ago", yesterday, today et des horodatages absolus. -o cat réduit la sortie aux seuls messages, sans métadonnées, ce qui est exactement ce qu'il faut avant de l'envoyer dans les pipelines présentés dans grep, sed et awk :

journalctl -u orders-api --since today -o cat \
  | grep -oE 'status=[0-9]{3}' | sort | uniq -c | sort -rn

Deux options méritent d'être connues en situation d'incident. journalctl -u a -u b --since … fusionne plusieurs unités en un seul flux ordonné dans le temps : c'est ainsi que l'on voit, dans une même vue, une requête arriver au proxy puis échouer sur le backend. Et journalctl -f -u a -u b -p warning fournit un flux filtré en direct pendant un déploiement.

Les utilisateurs non root ne voient que leurs propres entrées, sauf s'ils appartiennent au groupe systemd-journal — à prévoir pour toute personne censée déboguer sans sudo complet.

Rotation et rétention

Des logs sans limite remplissent les disques, et un disque plein fait tomber d'un coup tous les services de la machine. Les deux mondes de logs ont besoin d'un plafond.

Pour le journal, fixez-le dans /etc/systemd/journald.conf :

[Journal]
Storage=persistent
SystemMaxUse=2G
SystemKeepFree=1G
MaxRetentionSec=30day
Compress=yes
sudo systemctl restart systemd-journald
journalctl --disk-usage
sudo journalctl --vacuum-size=1G       # reclaim now
sudo journalctl --vacuum-time=14d

Pour les fichiers, logrotate est lancé par un timer systemd et traite les configurations déposées dans /etc/logrotate.d/ :

/var/log/myapp/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    create 0640 myapp adm
    sharedscripts
    postrotate
        systemctl reload myapp.service > /dev/null 2>&1 || true
    endscript
}

Le mécanisme compte. logrotate renomme le fichier courant et en crée un nouveau, mais un processus qui a déjà ouvert l'ancien fichier continue d'y écrire : le fichier renommé grossit pendant que le nouveau reste vide. Il existe deux solutions : postrotate signale au service de rouvrir ses fichiers (c'est le rôle du reload ci-dessus), ou bien vous utilisez copytruncate, qui copie le contenu puis tronque l'original sur place. copytruncate perd les lignes écrites pendant la copie ; préférez donc le signal quand l'application le prend en charge.

Testez une configuration avant de lui faire confiance :

sudo logrotate -d /etc/logrotate.d/myapp     # debug: say what would happen
sudo logrotate -f /etc/logrotate.d/myapp     # force a rotation now
cat /var/lib/logrotate/status | grep myapp

Les soixante premières secondes sur une machine malade

Un balayage rapide qui ne coûte presque rien et clôt souvent l'enquête :

uptime                                   # load average: 1, 5, 15 minutes
free -h                                  # available memory, swap in use
df -h; df -i                             # space and inodes
systemctl --failed                       # units that gave up
journalctl -p err -b --since "1 hour ago" | tail -50
dmesg -T --level=err,warn | tail -30     # kernel complaints, human timestamps
sudo ss -s                               # socket counts by state
vmstat 1 5                               # r, b, si/so, us/sy/id/wa over five seconds

Les colonnes de vmstat constituent à elles seules un diagnostic : un r élevé signifie des processus en file d'attente pour le CPU, un b élevé des processus bloqués sur des I/O, des si/so non nuls un swap actif, et un wa élevé dans le groupe CPU signale des processeurs inactifs parce qu'ils attendent le stockage.

Disque et inodes épuisés

df -h affiche les octets et df -i les inodes ; un système de fichiers peut être plein dans l'une ou l'autre dimension, et une partition à 100 % d'inodes avec 40 % d'espace libre déroute tout le monde à chaque fois. La cause habituelle : des millions de petits fichiers de session, de cache ou de file d'attente de messagerie.

Quand ce sont les octets qui manquent, cherchez ce qui grossit plutôt que ce qui est gros — l'essentiel du disque d'un système est occupé à juste titre, et ce qui vous intéresse, c'est ce qui a changé :

sudo du -x --max-depth=2 /var 2>/dev/null | sort -n | tail -15
sudo find /var/log -type f -size +200M -printf '%s\t%p\n' | sort -rn | head
sudo find / -xdev -type f -mtime -1 -size +100M -printf '%TF %s %p\n' 2>/dev/null | sort -rn | head

Pour récupérer de l'espace sans risque, tronquez au lieu de supprimer tout fichier qu'un service garde ouvert : sudo truncate -s 0 /var/log/myapp/huge.log rend les blocs immédiatement et laisse le descripteur valide. Supprimez ensuite la cause : un niveau de log debug oublié après un déploiement, une règle de rotation jamais écrite, un motif de core dump qui écrit des gigaoctets dans le répertoire de travail, ou un journal sans SystemMaxUse.

Gardez délibérément quelques pour cent de marge. Les bases de données, les gestionnaires de paquets et le journal se comportent tous mal à zéro octet libre, et remettre sur pied une machine qui n'a même plus la place d'écrire un fichier temporaire est bien plus difficile qu'il n'y paraît.

Pression mémoire et OOM killer

Dans free -h, une seule colonne compte : available. Elle estime la quantité de mémoire qu'un nouveau processus pourrait obtenir, cache de pages récupérable compris. Linux utilise volontairement toute la mémoire disponible comme cache : une valeur « free » basse avec une valeur « available » confortable est donc le signe d'un système qui fonctionne normalement, pas d'un problème.

Lorsque la mémoire disponible s'épuise vraiment et que le swap ne peut plus absorber la demande, le noyau fait appel à l'OOM killer, qui choisit une victime d'après un score de « badness » qui pénalise les processus volumineux, récemment lancés et non privilégiés, puis la tue avec un signal impossible à intercepter. La preuve se trouve dans le log du noyau :

$ sudo dmesg -T | grep -i -A2 'out of memory'
[Sun Sep  7 03:14:22 2026] Out of memory: Killed process 4821 (java) total-vm:6291456kB,
  anon-rss:3874112kB, file-rss:0kB, shmem-rss:0kB, UID:1001 pgtables:8192kB oom_score_adj:0
journalctl -k --since today | grep -i 'killed process'
systemctl status orders-api | grep -E 'Active|Main PID|status'
cat /sys/fs/cgroup/system.slice/orders-api.service/memory.peak

La signature ne trompe pas : un service qui disparaît sans le moindre message d'arrêt dans son propre log, et une ligne du noyau qui le nomme. systemctl status indique que l'unité a échoué avec le signal KILL.

Les correctifs sont classés par efficacité. Plafonnez le coupable avec une limite au niveau de l'unité — MemoryMax=2G et MemoryHigh=1600M dans le fichier de service — pour que le noyau freine et tue à l'intérieur de ce cgroup au lieu de choisir une victime à l'échelle du système. Corrigez les limites propres à l'application, comme un heap JVM plus grand que la machine ou un pool FPM avec trop de workers. Ajoutez du swap, qui transforme un kill brutal en dégradation progressive et laisse le temps de réagir. Ajustez oom_score_adj pour un processus qui doit survivre, comme la base de données sur un serveur de bases de données. Ajouter de la RAM sans comprendre lequel de ces cas s'applique ne fait généralement que repousser la panne.

Charge, CPU et I/O

Le load average compte les processus exécutables et ceux en sommeil non interruptible. C'est ce second groupe qui fait que, sous Linux, la charge n'est pas une métrique CPU.

uptime
nproc                                    # compare load against this
top -b -n1 | head -15
pidstat -u -p ALL 2 3                    # per-process CPU over three samples
iostat -xz 2 3                           # per-device utilisation, queue depth, await
ps -eo pid,stat,wchan:24,cmd | awk '$2 ~ /^D/'   # what the blocked processes are waiting on

Interprétez la combinaison :

  • Charge élevée, us élevé, wa bas : vraie saturation CPU. Trouvez le processus avec pidstat ; profilez ou ajoutez des instances.
  • Charge élevée, wa élevé, processus en D : stockage ou système de fichiers réseau. iostat -x affiche await et %util par périphérique ; un disque saturé ou un montage NFS bloqué produit exactement ce tableau.
  • Charge élevée, sy élevé : temps noyau — souvent une tempête d'interruptions, un nombre aberrant de changements de contexte ou un programme qui sollicite le noyau beaucoup trop souvent. vmstat 1 affiche cs et in.
  • Charge basse, application lente : le goulot d'étranglement est ailleurs. Un verrou, une requête en base, un service en amont. Ce sont les métriques applicatives qui répondent à cette question, pas celles de l'hôte — c'est tout l'argument en faveur de l'instrumentation décrite dans actuator et observabilité.

Regarder à l'intérieur d'un processus

Quand un processus précis se comporte mal, quatre outils révèlent ce qu'il fait sans débogueur.

sudo lsof -p 4821 | head -30              # open files, sockets, libraries
sudo lsof -i :8080                        # who holds a port
sudo lsof /var/lib/mysql                  # who is using a path
ls -l /proc/4821/fd | wc -l               # file descriptor count
cat /proc/4821/limits | grep 'open files' # against the limit
cat /proc/4821/status | grep -E 'State|Threads|VmRSS'
sudo cat /proc/4821/stack                 # kernel stack of a hung task

L'épuisement des descripteurs est une panne fréquente et déroutante : l'application écrit « too many open files » dans ses logs, n'accepte plus de nouvelles connexions et paraît en parfaite santé selon toutes les métriques de l'hôte. Comparez le nombre de descripteurs à la limite, puis relevez celle-ci dans l'unité avec LimitNOFILE= plutôt que dans un profil shell, qu'un service ne lit jamais.

strace montre les appels système effectués par un processus. Attachez-vous à un processus en cours d'exécution, et commencez par le résumé, car la sortie brute est écrasante :

sudo strace -c -p 4821                    # counts and time per syscall; Ctrl-C to report
sudo strace -f -e trace=openat,connect -p 4821
sudo strace -f -T -o /tmp/trace.txt -p 4821

Un résumé dominé par futex évoque une contention sur des verrous ; un résumé dominé par des openat qui renvoient ENOENT signale généralement un chemin de recherche erroné ; des appels connect répétés qui renvoient ECONNREFUSED ou ETIMEDOUT désignent la dépendance qui a disparu. S'attacher met brièvement la cible en pause et la ralentit nettement : soyez prudent sur un processus de production chargé et préférez une capture courte. ltrace fait la même chose pour les appels de bibliothèque ; perf top échantillonne l'endroit où le temps CPU est réellement dépensé.

Messages du noyau

dmesg lit le ring buffer du noyau, où atterrissent les événements matériels et ceux des pilotes avant qu'un démon de logs ne les voie. dmesg -T ajoute des horodatages lisibles et dmesg -w suit le flux en direct.

Quelques messages à savoir reconnaître : EXT4-fs error et I/O error, dev sda signalent un stockage défaillant — vérifiez immédiatement avec smartctl -a /dev/sda. nf_conntrack: table full, dropping packet indique que la table de suivi des connexions est pleine, ce qui se manifeste par des pannes réseau aléatoires sous charge. TCP: request_sock_TCP: Possible SYN flooding signifie que la file d'attente d'accept a débordé. segfault at … ip … consigne le plantage d'un binaire. Under memory pressure et oom-kill relèvent du problème de mémoire décrit plus haut.

Le buffer ayant une taille fixe, les anciens messages sont écrasés. journalctl -k -b -1 récupère les messages du noyau du boot précédent quand le ring buffer ne les contient plus, ce qui est indispensable après un redémarrage inexpliqué.

Vérifier un service de bout en bout

Un passage structuré sur un service, de l'extérieur vers l'intérieur :

systemctl status orders-api                        # running? restarts? since when?
systemctl show orders-api -p NRestarts -p ExecMainStartTimestamp
journalctl -u orders-api -p warning --since "1 hour ago"
sudo ss -ltnp | grep 8080                          # listening, and on what address
curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' http://127.0.0.1:8080/healthz
getent hosts db.internal && nc -vz db.internal 5432 # dependency reachable
sudo lsof -p "$(systemctl show -p MainPID --value orders-api)" | wc -l
journalctl -u orders-api --since "1 hour ago" -o cat | grep -c 'ERROR'

Chaque ligne écarte une hypothèse. Un NRestarts qui augmente indique que l'unité redémarre en boucle et que le log intéressant se situe juste avant chaque redémarrage. Un curl local qui répond correctement alors que les requêtes externes échouent déplace l'enquête vers le proxy ou le pare-feu. Une dépendance dont le nom ne se résout pas ou qui refuse la connexion met immédiatement fin à la recherche.

Après l'incident

Deux habitudes sont vite rentabilisées. D'abord, capturez les preuves avant de redémarrer : un thread dump, les quelques centaines de dernières lignes du journal, la sortie de ps, free, df. Un redémarrage fait généralement disparaître le symptôme et détruit l'explication, et la prochaine occurrence reste alors tout aussi mystérieuse.

Ensuite, rédigez une courte note sans recherche de coupable : chronologie, symptôme, cause racine, ce qui a rendu le diagnostic difficile, et une action concrète. Le résultat le plus précieux d'un incident n'est souvent pas le correctif, mais l'alerte qui manquait, la ligne de log qui aurait dû exister ou le réglage de rétention qui avait déjà effacé les preuves de la veille.

Erreurs courantes

  • Redémarrer d'abord. Vous perdez les preuves sans rien apprendre ; la panne revient à 3 h du matin.
  • Ne lire que la dernière ligne du log. L'entrée intéressante est généralement la première erreur de la séquence, avant la cascade de conséquences.
  • Supprimer un gros fichier de log ouvert. Aucun espace n'est rendu. Tronquez, puis corrigez la rotation.
  • Confondre load average et utilisation CPU. Regardez wa et les processus en état D avant de conclure qu'il vous faut plus de cœurs.
  • Paniquer devant une mémoire « free » basse. Lisez la colonne available ; le cache fait son travail.
  • Laisser les logs en debug après une investigation. Ils rempliront discrètement le disque une semaine plus tard.
  • Ne pas activer le stockage persistant du journal. Tout ce qui concernait la panne à l'origine du redémarrage a disparu.
  • Laisser tourner strace -f pendant des minutes sur un processus de production chargé. Le ralentissement devient le nouvel incident.

À retenir

  • Suivez un ordre fixe — symptôme, moment, étendue, puis service, log, ressource, dépendance, changement — et notez ce que vous avez écarté.
  • Le journal contient la sortie des unités et du noyau ; les applications qui gèrent leurs propres fichiers écrivent toujours sous /var/log. Vérifiez les deux.
  • journalctl -u … -p err --since, en fusionnant plusieurs unités et avec -o cat pour chaîner les commandes, répond à la plupart des questions sur les logs.
  • Plafonnez les deux stockages de logs : SystemMaxUse pour le journal, une règle logrotate avec un reload dans postrotate pour les fichiers.
  • Distinguez les octets épuisés des inodes épuisés, et tronquez les fichiers ouverts au lieu de les supprimer.
  • Un OOM kill se voit dans dmesg ; contenez-le avec des limites de cgroup sur l'unité plutôt qu'en ajoutant de la RAM à l'aveugle.
  • Une charge élevée avec des CPU inactifs et des processus en état D trahit un problème de stockage ou de réseau — iostat -x et wchan le désigneront.
  • lsof, /proc/<pid>/, strace -c et dmesg -T expliquent ce que fait un processus ; capturez les preuves avant de redémarrer quoi que ce soit.

Sources et lectures complémentaires

Questions fréquentes

Sur un système moderne, où se trouvent vraiment les logs : dans le journal ou dans /var/log ?
Aux deux endroits. Tout ce que lance systemd envoie sa sortie standard et sa sortie d'erreur au journal, un stockage binaire indexé que l'on lit avec journalctl. Les applications qui ouvrent leurs propres fichiers de log écrivent toujours du texte brut sous /var/log : c'est typiquement le cas des serveurs web, des bases de données et des serveurs de messagerie. Beaucoup de distributions transfèrent aussi le journal à rsyslog, si bien que le même message apparaît dans /var/log/syslog. Vérifiez les deux avant de conclure qu'un message n'existe pas.
Pourquoi mon disque se remplit-il de nouveau le lendemain de la suppression d'un gros fichier de log ?
Supprimer un fichier qu'un processus garde ouvert ne libère aucun espace tant que ce processus ne l'a pas fermé, et le service continue d'écrire dans le même descripteur. La bonne démarche consiste à tronquer le fichier sur place, puis à traiter la cause avec une rotation des logs et une limite de rétention. Si c'est le journal qui grossit, définissez SystemMaxUse dans journald.conf et récupérez l'espace immédiatement avec journalctl --vacuum-size.
Le load average est à 20 mais top montre des CPU presque inactifs. Qu'est-ce que cela signifie ?
Sous Linux, le load average compte les processus en sommeil non interruptible en plus des processus exécutables : des I/O disque ou réseau bloquées le font donc grimper sans consommer de CPU. Regardez la colonne wa dans top ou iostat, cherchez les processus en état D et mesurez la latence des périphériques avec iostat -x. Une charge de 20 avec 90 % de CPU inactif relève d'un problème de stockage ou de système de fichiers réseau, pas d'un manque de puissance de calcul.
Comment savoir si le noyau a tué mon processus ou s'il s'est arrêté de lui-même ?
Cherchez une ligne Out of memory dans dmesg ou journalctl -k autour du moment de sa disparition : le noyau y indique la victime choisie, son score et un tableau des plus gros consommateurs de mémoire. systemctl status donne aussi la raison de l'arrêt et affiche le signal 9 lorsque le processus principal d'une unité a été tué par l'OOM killer. Un code de sortie non nul propre, sans message du noyau, signifie que l'application a décidé de s'arrêter : c'est alors son propre log qui détient la réponse.