systemd : afficher la raison du redémarrage d'un service
Affichez la raison d'un redémarrage systemd : status hausse les épaules, le journal avoue. Cinq codes de mort du banc réel — et la mort qu'il nomme succès.
Nous avons tué un service de cinq façons différentes sur une VM Arch jetable et laissé systemd s'interroger lui-même. Chaque ligne ci-dessous est la sortie du journal lui-même, imprimée sans retouche — quatre aveux, un silence, et un « abandon » que la sortie de status ne mentionne jamais. Si vous arrivez de notre autopsie des services qui refusent de démarrer, vous êtes dans la pièce après le claquement de la porte : le service a démarré, tourné, est mort — et vous voulez savoir qui l'a fait.
Comment afficher la raison du redémarrage d'un service systemd ?
Trois commandes, dans cet ordre. La première lit la bande, la deuxième lit le compteur, la troisième filtre la bande pour n'y garder que les morts.
# the confession, most recent first
journalctl -u myservice.service -n 50 --no-pager
# the counter and the last death, machine-readable
systemctl show myservice.service -p NRestarts -p ExecMainStatus \
-p ExecMainCode -p Result
# every death, timestamped to the millisecond
journalctl -u myservice.service --grep 'Main process exited' \
-o short-precise | tail -10
Vérifié : sur notre VM de banc, la deuxième commande a répondu Result=exit-code, NRestarts=3, ExecMainStatus=1, ExecMainCode=1 pour un service mort trois fois quelques secondes plus tôt. Last tested 2026-11-02.
Celui que presque tout le monde manque, c'est NRestarts. systemctl status remet son histoire à zéro à chaque redémarrage, si bien que le service semble toujours « venir tout juste de démarrer ». Le compteur, lui, ne remet rien à zéro. Sur le banc, status affichait active (running), 10 ms old — pendant que le compteur, une ligne en dessous, disait restart counter is at 3. Ce décalage, c'est toute la session de débogage : le service a un casier judiciaire, et status montre son acte de naissance.
Pourquoi systemctl status ne montre-t-il pas pourquoi le service a redémarré ?
Parce que status est une photo de l'instant présent, et le journal la bande de vidéosurveillance. Voici la photo, imprimée depuis le banc pendant que le service mourait toutes les ~1.2 secondes :
● flaky.service - dies on purpose for the journal
Loaded: loaded (/etc/systemd/system/flaky.service; static)
Active: active (running) since Mon 2026-11-02 05:05:46 UTC; 10ms ago
Main PID: 627 ((die.sh))
CPU: 3ms
Nov 02 05:05:46 bench-arch systemd[1]: flaky.service: Scheduled restart
job, restart counter is at 3.
status est une photo de l'instant ; le journal est la bande de vidéosurveillance.
Que signifient code=exited et status=11/SEGV dans le journal ?
Chaque mort produit deux lignes de journal — la ligne du code et le verdict. Nous en avons déclenché cinq types volontairement. Voici les lignes d'aveu, sans retouche :
la galerie des aveux — une par mort, sortie droite du banc# death 1: the app exits nonzero
flaky.service: Main process exited, code=exited, status=1/FAILURE
flaky.service: Failed with result 'exit-code'.
# death 2: the app segfaults (kill -SEGV)
flaky.service: Main process exited, code=dumped, status=11/SEGV
# death 3: the app aborts (kill -ABRT) — assert died, or a
# watchdog inside the app pulled the plug
flaky.service: Main process exited, code=dumped, status=6/ABRT
# death 4: ExecStart points at a file that does not exist —
# the app never ran
(nothere)[1260]: flaky.service: Failed at step EXEC spawning
/usr/local/bin/nothere: No such file or directory
flaky.service: Main process exited, code=exited, status=203/EXEC
# death 5: the app kills itself with SIGTERM (kill -TERM)
flaky.service: Deactivated successfully.
Règle de décodage : code= dit comment il est sorti (exited = est sorti par la porte, dumped = a été porté dehors), status= nomme les circonstances de la mort, et Failed with result est le résumé systemd en une ligne. Le tableau, c'est le décodeur complet que nous y avons trouvé :
Le journal dit Ce qui s'est réellement passé Où regarder ensuite
code=exited, status=1/FAILURE l'app a choisi de mourir — mauvaise config, port occupé, variable d'env manquante les logs de l'app, au-dessus de la sortie, dans journalctl -u NAME
code=dumped, status=11/SEGV segfault — bug mémoire dans l'app ou une dépendance native coredumpctl list pour la backtrace
code=dumped, status=6/ABRT abort — un assert qui a échoué, ou un watchdog interne qui a abandonné le log de l'app elle-même, une ligne avant l'abort
status=203/EXEC chemin ExecStart faux ou non exécutable — l'app n'a jamais démarré vérifiez le chemin, chmod +x, le shebang de l'interpréteur
Deactivated successfully (pas de ligne d'échec) mort par SIGTERM — comptée comme une sortie propre par défaut § plus bas ; c'est le fantôme
start request repeated too quickly / start-limit-hit systemd a abandonné les redémarrages — la limite de rafale a sauté réparez d'abord la mort ; ne touchez à la limite qu'ensuite
Pourquoi mon service systemd redémarre-t-il toutes les quelques secondes ?
Parce que Restart= fait son travail et que la mort n'a jamais cessé. Le rythme est fixé par RestartSec (100 ms par défaut) et la patience par StartLimitIntervalSec/StartLimitBurst (5 échecs en 10 s par défaut). Sur le banc, avec RestartSec=1, trois morts sont arrivées ainsi :
cadence — RestartSec=1, trois morts en trois secondes05:05:43 flaky.service: Scheduled restart job, restart counter is at 1.
05:05:44 flaky.service: Scheduled restart job, restart counter is at 2.
05:05:46 flaky.service: Scheduled restart job, restart counter is at 3.
# and when the burst limit applies, the loop ends like this:
flaky.service: Start request repeated too quickly.
flaky.service: Failed with result 'start-limit-hit'.
Comptez le rythme de votre propre boucle en une ligne :
# restarts in the last hour, with timestamps
journalctl -u myservice.service --since "1 hour ago" \
--grep 'Scheduled restart' --no-pager | tail -20
Vérifié : sur le banc, cela a imprimé les trois lignes ci-dessus ; le compteur est repassé à 1 quand la fenêtre de limite de 10 s a tourné. Last tested 2026-11-02.
Le piège du bâillon mérite son propre avertissement : monter StartLimitBurst ne répare pas un service qui meurt, ça cache le corps plus souvent. Réparez la mort — le tableau décodeur ci-dessus nomme où regarder — et traitez la limite comme ce qu'elle est : un détecteur de fumée.
Pourquoi mon service s'est-il arrêté, et le journal dit-il succès ?
C'est celle que nous n'attendions pas, et c'est la raison pour laquelle l'article existe. La mort 5, c'était le processus se tuant lui-même avec SIGTERM. Aucun redémarrage n'a suivi. Aucune ligne d'échec n'est apparue. Tout le commentaire du journal a été Deactivated successfully, et systemctl show a répondu Result=success, ExecMainCode=0, ExecMainStatus=0 — un meurtre enregistré comme une retraite propre.
La cause est dans la page de manuel, pas dans le journal : la liste SuccessExitStatus par défaut inclut SIGTERM (et SIGINT, SIGPIPE, status 0). Tout processus qui meurt par SIGTERM est, pour la politique de redémarrage, un succès — donc Restart=on-failure ne se déclenche jamais, et rien dans le log ne l'appelle une mort. Si votre service est supervisé ou se fait terminer par un watchdog externe, c'est ce silence-là que vous fixez depuis le début.
Deux sorties honnêtes, selon ce que vous cherchez :
La méthode du banc, pour refaire les cinq morts vous-même
VM Arch jetable (quickemu, 2 vCPU, aucune modification de l'hôte). Une unité, un script : /usr/local/bin/die.sh prend un type de mort en argv — exit 1, kill -SEGV $$, kill -ABRT $$, kill -TERM $$, ou un ExecStart pointé vers un chemin inexistant pour le 203. Changez l'ExecStart de l'unité entre deux morts, daemon-reload, démarrez, laissez-la mourir trois fois, stoppez, capturez. Log de reçus complet, sans retouche : cinq aveux, le haussement d'épaules, la cadence, l'abandon et le fantôme — les lignes de journal imprimées ci-dessus en sont extraites mot pour mot. Machine en forme de CPU, rien d'à côté du GPU, rien de conservé : le clone a été détruit dans la même session.
Le registre honnête : nous n'avons pas décodé les coredumps (coredumpctl mérite sa propre soirée), et les noms du banc sont faux — vos lignes journalctl nommeront vos unités. Ce qui a tenu : quatre morts sur cinq avouent avec code=, status= et une ligne de résultat, le compteur n'oublie jamais, et la cinquième mort — le SIGTERM — le journal l'appelle un succès. Le silence du journal est lui-même un aveu. status continuera de hausser les épaules ; lisez la bande.