Linux : réparer un service systemd qui redémarre en boucle
Un service qui alterne entre activating et failed, remplit le journal puis finit sur Start request repeated too quickly n’a pas besoin d’un énième redémarrage. Il faut retrouver sa première erreur, vérifier ce que systemd exécute réellement, corriger cette cause puis prouver que le service tient.
Le fil rouge utilise l’unité système neutre mon-app.service. Remplacez ce nom seulement après avoir confirmé l’unité concernée. Les commandes d’observation sont sans danger ; les commandes précédées de sudo modifient l’état du gestionnaire système. Pour une unité utilisateur, utilisez la variante systemctl --user présentée plus loin, sans sudo.
1. Stabiliser sans perdre les preuves
Confirmer l’unité et figer l’instant
Question diagnostique : est-ce bien mon-app.service, et dans quel état précis se trouve-t-elle maintenant ?
$ date --iso-8601=seconds
$ systemctl status mon-app.service --no-pager --full
$ systemctl show mon-app.service -p Id -p Names -p LoadState -p ActiveState -p SubState -p Result -p MainPID -p ExecMainCode -p ExecMainStatus -p NRestarts -p ActiveEnterTimestamp -p InactiveEnterTimestamp
Ces lectures se lancent en utilisateur normal ; selon la politique locale, certaines lignes du journal incluses dans status peuvent être masquées. Relevez Loaded, Active, Result, le PID principal, code=/status=, NRestarts et l’heure. LoadState=not-found signifie que le nom est faux ou que l’unité n’est pas chargée : arrêtez ici et identifiez son vrai nom avec systemctl list-units --type=service --all. Si l’unité existe, poursuivez.
Risque : aucun changement d’état, mais la sortie peut contenir chemins, arguments ou secrets. Ne la publiez pas brute. Retour arrière : aucun. Point d’arrêt : ne diagnostiquez pas un alias ou une unité utilisateur comme s’il s’agissait d’une unité système.
Illustration pédagogique : les six indices à noter avant toute action. Crédit : La Revue Geek, d’après la documentation systemd.
Arrêter une boucle coûteuse, sans la désactiver par réflexe
Question diagnostique : la boucle consomme-t-elle CPU, mémoire, disque, quotas d’API ou connexions au point de gêner l’enquête ? Si non, laissez-la tourner le temps de capturer une occurrence. Si oui :
$ sudo systemctl stop mon-app.service
$ systemctl status mon-app.service --no-pager --full
stop arrête l’unité maintenant et un arrêt explicite n’est pas contré par Restart=. disable retire ses liens de démarrage automatique mais n’arrête pas forcément le processus courant. mask lie l’unité à /dev/null et interdit tout démarrage, manuel ou indirect : gardez-le pour un besoin explicite de confinement, pas pour un diagnostic courant.
Privilèges : sudo pour agir sur l’unité système. Résultat attendu : inactive (dead) ou un échec de stop à analyser. Risque : interruption réelle du service. Retour arrière : sudo systemctl start mon-app.service, mais seulement après correction ou pendant un test contrôlé. Critère : une fois la consommation stabilisée et les horodatages notés, passez au journal.
2. Lire le journal ciblé, pas tout le système
Retrouver la première erreur du démarrage courant
Question diagnostique : quel événement précède le premier code de sortie, timeout ou redémarrage ?
$ sudo journalctl -u mon-app.service -b --since "2026-08-12 10:05:00" --until "2026-08-12 10:20:00" --no-pager -o short-precise
$ sudo journalctl -u mon-app.service -b -n 200 --no-pager
Adaptez la fenêtre à l’heure relevée ; -b limite au démarrage courant. Lisez de bas en haut jusqu’à Scheduled restart job, puis remontez à la première erreur applicative. Si l’incident a commencé au boot précédent, utilisez d’abord journalctl --list-boots, puis sudo journalctl -u mon-app.service -b -1. N’utilisez pas journalctl -xe seul : il mélange des événements sans rapport.
Privilèges : sudo garantit l’accès au journal système ; un membre des groupes autorisés peut s’en passer. Risque : lecture seule, mais exposition possible de secrets. Retour arrière : aucun. Critère : classez la première erreur avec le tableau suivant ; si le journal ne contient rien d’applicatif, inspectez l’unité effective.
| Symptôme | Preuve à chercher | Cause probable | Étape sûre |
|---|---|---|---|
| Sortie immédiate | status=1/FAILURE ou autre code | erreur applicative, argument ou configuration | lire la ligne précédente et la documentation du programme |
| Permission refusée | EACCES, Permission denied, refus SELinux/AppArmor | User=, Group=, droits, ACL ou politique MAC | vérifier chaque composant du chemin et le refus d’audit ; ne pas faire chmod 777 |
| Fichier absent | ENOENT, No such file | binaire, WorkingDirectory= ou EnvironmentFile= absent | vérifier le chemin exact et si le préfixe - rend le fichier d’environnement optionnel |
| Port occupé | EADDRINUSE, Address already in use | autre processus ou double instance | sudo ss -ltnp 'sport = :8080', puis identifier le propriétaire |
| Dépendance absente | refus de connexion, DNS, dependency | réseau, base, montage ou service non prêt | tester la dépendance depuis le même contexte ; distinguer ordre et disponibilité |
| Timeout | start operation timed out | initialisation trop lente, blocage ou protocole Type=notify incomplet | mesurer ce qui bloque avant de modifier TimeoutStartSec= |
Vérifier un port sans tuer ni supprimer à l’aveugle
Question diagnostique : qui écoute déjà sur le port attendu, ici 8080 ?
$ sudo ss -ltnp 'sport = :8080'
$ sudo systemctl status PID_OBSERVE --no-pager
Remplacez PID_OBSERVE par le PID lu, si systemctl status accepte un PID sur votre version. Sinon, examinez ps -fp PID_OBSERVE. Risque : lecture seule. Ne tuez pas le processus et ne supprimez ni PID file ni socket avant d’avoir confirmé son propriétaire et son état. Critère : si une instance légitime écoute, corrigez le conflit de port ou la double activation ; si le port est libre, poursuivez.
3. Inspecter la configuration réellement chargée
Distinguer paquet, unité administrateur et drop-in
Question diagnostique : quels fichiers composent réellement mon-app.service ?
$ systemctl cat mon-app.service
$ systemctl show mon-app.service -p FragmentPath -p DropInPaths -p UnitFileState
FragmentPath peut pointer vers l’unité fournie par un paquet dans /usr/lib/systemd/system/mon-app.service ou, selon la distribution et son organisation usr, /lib/systemd/system/mon-app.service. Une unité créée par l’administrateur se trouve dans /etc/systemd/system/mon-app.service et a priorité. Les fragments de /etc/systemd/system/mon-app.service.d/*.conf, souvent créés par systemctl edit, complètent ou remplacent des directives.
Ne modifiez jamais directement l’unité d’un paquet : une mise à jour pourrait l’écraser. Pour une directive à valeur unique, le drop-in prioritaire gagne. Pour certaines listes, les valeurs se cumulent ; ExecStart= doit être vidé par une ligne ExecStart= sans valeur avant d’être redéfini dans un service qui n’est pas Type=oneshot.
Privilèges : lecture en utilisateur normal. Risque : aucun. Retour arrière : aucun. Critère : si un drop-in inattendu explique l’écart, notez son chemin et corrigez-le à la phase 5 ; sinon inspectez les propriétés.
Illustration pédagogique : la configuration effective peut différer du fichier du paquet. Crédit : La Revue Geek, d’après systemd.unit et la documentation Red Hat.
Lire les propriétés qui changent le contexte
Question diagnostique : quelle commande, quel utilisateur, quel répertoire, quel environnement et quelles dépendances systemd applique-t-il ?
$ systemctl show mon-app.service \
-p Type -p ExecStart -p User -p Group -p WorkingDirectory \
-p Environment -p EnvironmentFiles -p Requires -p Wants -p After \
-p Conditions -p AssertResult -p ConditionResult \
-p Restart -p RestartUSec -p TimeoutStartUSec \
-p StartLimitIntervalUSec -p StartLimitBurst
Lisez d’abord ExecStart, User, Group, WorkingDirectory et EnvironmentFiles. Une variable disponible dans votre terminal n’est pas automatiquement transmise au service. Le préfixe - devant un EnvironmentFile= signifie que son absence est tolérée ; sans ce préfixe, elle fait échouer le démarrage.
After= impose un ordre, pas la santé ni même toujours le démarrage de la dépendance. Requires= ajoute une dépendance forte, Wants= une dépendance plus faible. network.target ne garantit pas qu’Internet, le DNS ou une base distante répond ; l’application doit gérer les indisponibilités transitoires, éventuellement avec une dépendance adaptée documentée par la distribution.
ExecStart= n’est pas exécuté par un shell : pas de pipe, redirection, glob, &&, substitution $(...) ni expansion shell implicite. Sur les versions systemd actuelles, le premier mot peut être un chemin absolu ou un nom simple sans slash recherché dans un chemin fixe compilé ; un chemin absolu reste le choix le plus prévisible et il est requis dès que le nom contient un slash. Si un shell est réellement nécessaire, rendez-le explicite et documenté, par exemple /bin/sh -c 'exec ...', en maîtrisant l’échappement.
Risque : lecture seule ; Environment peut révéler des secrets. Critère : toute différence avec le lancement manuel devient une hypothèse testable à la phase 4.
Vérifier la syntaxe et les chemins connus par systemd
Question diagnostique : le fichier d’unité contient-il une directive inconnue, une section incorrecte, une commande introuvable ou une dépendance manquante ?
$ sudo systemd-analyze verify mon-app.service
L’outil charge l’unité depuis les chemins de recherche et signale des erreurs. Pour une nouvelle unité encore hors de ces chemins, passez son chemin absolu et gardez à l’esprit que la vérification peut charger d’autres unités. Une absence de sortie avec code 0 signifie « aucune erreur détectée par ce vérificateur », pas « application fonctionnelle ».
Risque : pas de démarrage du service, mais lecture de fichiers associés. Retour arrière : aucun. Point d’arrêt : corrigez toute erreur de syntaxe ou de chemin avant de tester l’application.
4. Reproduire sans tricher
Lancer /usr/local/bin/mon-app comme root depuis votre terminal n’est pas un test équivalent : root peut lire des fichiers interdits au compte de service, votre shell apporte un PATH, un HOME, des variables, un répertoire courant et parfois un terminal que l’unité n’a pas.
Tester le programme avec l’identité et le répertoire de l’unité
Question diagnostique : le programme échoue-t-il encore avec l’utilisateur mon-app, le groupe et le répertoire effectifs ?
$ sudo -u mon-app -g mon-app -- sh -c 'cd /srv/mon-app && exec /usr/local/bin/mon-app --config /etc/mon-app/config.toml'
Cette commande est un exemple à adapter exactement aux valeurs de systemctl show. Le shell sert ici seulement à reproduire le changement de répertoire ; il ne prétend pas reproduire les namespaces, capacités, restrictions de fichiers, variables ou le protocole de notification de systemd. Pour l’environnement, lisez d’abord le fichier déclaré, vérifiez ses permissions et fournissez seulement les variables non secrètes nécessaires avec une méthode adaptée à votre outil ; ne faites pas un source aveugle d’un EnvironmentFile=, dont la syntaxe n’est pas celle d’un script shell.
Résultat attendu : même erreur, donc cause reproductible, ou fonctionnement manuel sous le bon compte, ce qui recentre l’enquête sur l’environnement, le sandboxing, Type= ou les dépendances de l’unité. Risque : le programme peut ouvrir un port ou modifier ses données ; arrêtez l’unité avant le test et utilisez un environnement de test si l’écriture est risquée. Retour arrière : interrompez avec Ctrl+C et vérifiez qu’aucun processus ne reste. Critère : ne poursuivez que lorsque la différence est nommée.
Limite du test manuel : n’inventez pas de commande universelle pour un programme exigeant terminal, session graphique, périphérique, credentials, namespace ou capacités Linux. Dans ce cas, testez via une copie d’unité contrôlée ou escaladez avec l’unité effective, le journal ciblé et la documentation du programme.
Vérifier le modèle de processus et Type=
Question diagnostique : l’application reste-t-elle au premier plan, se démonise-t-elle, ou notifie-t-elle systemd ?
Avec Type=simple ou Type=exec, le processus lancé doit généralement rester au premier plan. Si l’application fork puis quitte alors que systemd suit le parent, le service peut sembler mort et être relancé. Pour un ancien démon qui fork réellement, Type=forking peut convenir, souvent avec un PIDFile= fiable ; préférez toutefois l’option « foreground/no-daemon » du programme quand elle existe. Type=notify exige une notification de disponibilité conforme. Type=oneshot convient à une tâche qui termine, pas à un démon.
Preuve : documentation officielle du programme, arbre de processus observé et journal. Risque : changer Type= au hasard peut déclarer le service prêt trop tôt ou suivre le mauvais PID. Critère : choisissez le type qui décrit le comportement réel, puis appliquez-le en drop-in.
5. Corriger la cause, pas le compteur
Choisir la correction minimale selon la preuve
- Code de sortie immédiat : corrigez l’argument, le fichier de configuration ou l’erreur applicative indiqué juste avant
status=. - Permission ou mauvais utilisateur : donnez au compte de service le droit minimal sur le fichier ou répertoire voulu, corrigez
User=/Group=si l’identité est réellement erronée, et examinez les journaux SELinux/AppArmor. Ne passez pas le service en root et ne désactivez pas ces protections par réflexe. - Fichier d’environnement absent : restaurez le fichier depuis une source de configuration sûre, avec propriétaire et permissions adaptés ; n’ajoutez le préfixe
-que si son absence est réellement acceptable. - Port occupé : arrêtez proprement l’instance concurrente si elle est indésirable, ou attribuez un port distinct. Ne supprimez pas socket ou PID file avant d’avoir vérifié le processus propriétaire.
- Réseau ou base indisponible : corrigez l’adresse, le DNS, les credentials ou la dépendance ; ajoutez un ordre systemd seulement s’il représente une dépendance locale réelle. Une reprise applicative avec temporisation est souvent plus robuste qu’une supposition sur le réseau.
- Timeout : débloquez l’initialisation. Augmentez
TimeoutStartSec=uniquement si un démarrage sain mais mesuré a légitimement besoin de plus de temps. - Mauvais chemin ou syntaxe : corrigez l’unité locale et relancez
systemd-analyze verify. - Démonisation incohérente : forcez le premier plan ou adaptez
Type=au modèle documenté.
Créer une modification locale réversible
Question diagnostique : quelle directive minimale doit remplacer la valeur du paquet ?
$ sudo systemctl edit mon-app.service
[Service]
User=mon-app
Group=mon-app
WorkingDirectory=/srv/mon-app
ExecStart=
ExecStart=/usr/local/bin/mon-app --config /etc/mon-app/config.toml
N’insérez que les directives justifiées ; l’exemple montre notamment la remise à zéro nécessaire avant de remplacer ExecStart=. Sur les versions actuelles, systemctl edit recharge normalement la configuration après une sauvegarde réussie. Après une modification manuelle d’un fichier dans /etc/systemd/system, exécutez explicitement :
$ sudo systemctl daemon-reload
$ sudo systemd-analyze verify mon-app.service
$ systemctl cat mon-app.service
Risque : un drop-in erroné empêche le démarrage. Retour arrière : sudo systemctl revert mon-app.service supprime les surcharges locales gérées pour revenir à la version du fournisseur ; relisez sa portée avant confirmation. Pour retirer seulement votre fragment, utilisez sudo systemctl edit mon-app.service, supprimez son contenu de façon contrôlée puis rechargez. Gardez une copie de toute configuration locale importante avant modification. Critère : le vérificateur ne signale plus d’erreur et systemctl cat affiche exactement la surcharge attendue.
Comprendre la politique de redémarrage
Restart= décide après quels types de sortie systemd relance le service ; on-failure est généralement adapté à un service long. RestartSec= impose l’attente avant une relance. StartLimitIntervalSec= définit la fenêtre de comptage et StartLimitBurst= le nombre de démarrages admis dans cette fenêtre. Atteindre la limite provoque le refus start-limit-hit / « Start request repeated too quickly » : ce message est la conséquence des échecs rapides, pas leur cause initiale.
Question diagnostique : la politique actuelle transforme-t-elle une erreur transitoire en rafale ? Après avoir corrigé la panne initiale, un drop-in raisonnable peut par exemple ralentir les reprises :
$ sudo systemctl edit mon-app.service
[Service]
Restart=on-failure
RestartSec=10s
[Unit]
StartLimitIntervalSec=5min
StartLimitBurst=5
Ces nombres illustrent une intention, pas une valeur universelle. Ne désactivez pas les limites et ne les augmentez pas pour cacher une application toujours cassée. Les options plus récentes de délai progressif, comme RestartSteps= et RestartMaxDelaySec= ajoutées en systemd 254, exigent de vérifier systemctl --version et le manuel installé avant usage.
Risque : une temporisation trop longue retarde une reprise ; une politique trop agressive consomme des ressources et masque l’incident. Retour arrière : retirez les directives du drop-in et rechargez. Critère : la politique correspond au coût et à la nature des pannes, sans remplacer la correction.
Utiliser reset-failed au bon moment
Question diagnostique : la cause est-elle corrigée, mais le démarrage est-il encore bloqué par l’état failed ou la limite atteinte ? Alors seulement :
$ sudo systemctl reset-failed mon-app.service
$ systemctl show mon-app.service -p Result -p NRestarts
La commande efface l’état failed ainsi que les compteurs de limite de démarrage et de redémarrages pour l’unité. Elle ne répare ni binaire, ni permission, ni port, ni dépendance. Risque : faible, mais elle efface des compteurs utiles ; conservez auparavant les preuves. Retour arrière : impossible pour les compteurs, d’où la capture initiale. Critère : poursuivez vers un démarrage contrôlé ; si la cause n’est pas corrigée, arrêtez.
6. Valider la remise en service
Démarrer tout en observant une seule tentative
Question diagnostique : le service atteint-il réellement son état prêt sans nouveau redémarrage ? Ouvrez deux terminaux.
# Terminal 1 — observation, Ctrl+C pour quitter
$ sudo journalctl -u mon-app.service -b -f -n 30 -o short-precise
# Terminal 2 — action contrôlée
$ sudo systemctl start mon-app.service
$ systemctl status mon-app.service --no-pager --full
$ systemctl show mon-app.service -p ActiveState -p SubState -p Result -p MainPID -p NRestarts
Résultat attendu : ActiveState=active, un SubState cohérent, Result=success, un PID stable pour un démon et aucune nouvelle séquence de relance. Risque : le service reprend ses effets réels ; préparez un sudo systemctl stop mon-app.service si le test touche des données ou des utilisateurs. Retour arrière : stoppez, restaurez le drop-in ou la configuration sauvegardée, rechargez, puis gardez le service arrêté. Critère : si NRestarts augmente ou si l’état retombe, revenez à la première nouvelle erreur du journal, pas à reset-failed.
Prouver la fonction, pas seulement le voyant vert
Question diagnostique : la fonction attendue répond-elle ? Pour un service HTTP local sur le port 8080 :
$ ss -ltn 'sport = :8080'
$ curl --fail --silent --show-error http://127.0.0.1:8080/health
Adaptez le test à la fonction documentée : requête de santé, connexion à une socket, travail traité, fichier produit ou requête métier sans effet destructif. Risque : le test peut déclencher une action applicative ; choisissez un point de santé sûr. Retour arrière : arrêtez le service si le résultat est incohérent. Point d’arrêt réussi : fonction disponible, journaux propres et compteur stable pendant une durée supérieure à la fenêtre habituelle de panne.
Vérifier le prochain démarrage
Question diagnostique : le service doit-il démarrer automatiquement, et est-il configuré pour le faire ?
$ systemctl is-enabled mon-app.service
$ systemctl list-dependencies --reverse mon-app.service
Si le service doit être autonome au boot et que is-enabled répond disabled, activez-le explicitement avec sudo systemctl enable mon-app.service. Cette action ne prouve pas qu’il fonctionne aujourd’hui et, sans --now, ne le démarre pas immédiatement. Retour arrière : sudo systemctl disable mon-app.service. Ne redémarrez pas la machine comme solution magique. Un reboot n’est pertinent qu’après validation à chaud, pour prouver une dépendance au boot ou le comportement d’activation ; planifiez-le, surveillez journalctl -b -u mon-app.service après reprise et prévoyez un accès de secours.
Unité système ou unité utilisateur : ne mélangez pas les gestionnaires
Si systemctl status mon-app.service répond « Unit not found » mais que l’application démarre dans une session utilisateur, testez sans sudo :
$ systemctl --user status mon-app.service --no-pager --full
$ journalctl --user -u mon-app.service -b --no-pager
$ systemctl --user cat mon-app.service
$ systemctl --user show mon-app.service -p FragmentPath -p DropInPaths -p ActiveState -p Result -p NRestarts
Une unité utilisateur vit notamment sous ~/.config/systemd/user ou dans les chemins utilisateur fournis par les paquets, et dépend du gestionnaire de l’utilisateur. Utilisez systemctl --user edit, daemon-reload, reset-failed et start dans ce même contexte. sudo systemctl --user vise souvent le mauvais bus et ne constitue pas une élévation utile. Pour un service utilisateur attendu sans session ouverte, vérifiez la politique de lingering avec l’administrateur plutôt que de convertir l’unité en service root.
Quand s’arrêter et escalader proprement
Arrêtez les essais si la panne implique corruption de données, crash natif répété, refus SELinux/AppArmor incompris, secret manquant, capacité spéciale, dépendance externe hors de votre contrôle ou unité de paquet manifestement défectueuse. Préparez alors un dossier minimal et expurgé :
- distribution,
systemctl --versionet version du paquet ; - heure exacte et impact ;
systemctl status, propriétés ciblées desystemctl showetsystemctl cat;- journal de l’unité dans une fenêtre courte, secrets masqués ;
- première erreur, test dans le bon contexte et correction déjà tentée ;
- procédure de retour arrière et état actuel, idéalement service stoppé si sa boucle est dangereuse.
Transmettez ce dossier au mainteneur du paquet ou de l’application. Ne joignez pas tout le journal système et ne modifiez pas l’unité du paquet pour fabriquer un état impossible à reproduire.
Conclusion
Un service est remis en ligne seulement lorsque trois preuves concordent : systemd le maintient actif sans nouveau redémarrage, son journal ne répète plus la première erreur et sa fonction répond réellement. Start request repeated too quickly disparaît alors parce que la panne a disparu — pas parce que le compteur a été maquillé.
Conservez le drop-in minimal, la raison de chaque directive et le test de santé qui a validé la reprise. Au prochain incident, ces éléments raccourciront le diagnostic sans sacrifier les preuves ni la sécurité.
FAQ
Les réponses courtes aux questions qui bloquent souvent la remise en service.
reset-failed redémarre-t-il le service ?
Non. Il efface l’état failed et des compteurs, puis vous devez lancer start. Si la cause subsiste, le service échouera encore.
Pourquoi la commande marche-t-elle dans mon terminal ?
Votre utilisateur, répertoire, variables, droits et terminal diffèrent. Comparez-les aux propriétés effectives de l’unité et reproduisez seulement dans un contexte contrôlé.
Faut-il augmenter StartLimitBurst ?
Pas avant d’avoir corrigé la première panne. La limite protège la machine contre une rafale ; l’augmenter ne répare rien.
After=network.target garantit-il le réseau ?
Non. Il impose un ordre relatif. Il ne garantit ni DNS, ni Internet, ni disponibilité d’une base distante.
Dois-je modifier le fichier dans /usr/lib/systemd/system ?
Non. C’est le territoire du paquet. Créez une surcharge avec sudo systemctl edit mon-app.service, vérifiez la configuration effective puis conservez une procédure de retour arrière.
Partager cet article
Écrit par
larevuegeekÀ lire aussi

Linux Mint va désormais supprimer automatiquement les anciens noyaux

Flatpak, Snap ou AppImage : lequel choisir sous Linux ?

Guide Linux : Automatiser ses sauvegardes avec rsync, sans mauvaise surprise

Comprendre les permissions Linux sans prise de tête

15 commandes Linux vraiment utiles au quotidien

Commentaires (0)