Vous déployez un service. Il plante. Vous corrigez l’évidence. Il replante—plus vite. Puis systemd affiche la ligne redoutée : « Requête de démarrage répétée trop rapidement » et cesse même d’essayer. Maintenant vous êtes coincé : non seulement le service est arrêté, mais votre système d’init refuse poliment que vous continuiez à vous heurter au même mur.
Ce n’est pas que systemd soit « mystérieux ». Il fait exactement ce pour quoi il a été conçu : limiter le rythme des unités qui oscillent afin qu’un mauvais service ne transforme pas votre hôte en chauffage loggueur. L’astuce est de diagnostiquer la vraie défaillance, puis d’appliquer des correctifs qui survivent aux mises à jour, aux redémarrages et à votre vous de 2 h du matin.
Ce que signifie réellement « Requête de démarrage répétée trop rapidement »
Systemd vous dit : « J’ai essayé de démarrer cette unité plusieurs fois dans une courte fenêtre. Elle a continué d’échouer. J’arrête tant que vous n’intervenez pas. » L’intervention peut être une correction de configuration, une réparation de dépendance, ou un changement délibéré de la politique de redémarrage. Parfois, vous devez aussi effacer l’état d’échec.
Sous le capot, systemd applique une limite de rythme de démarrage par unité. Si l’unité bascule en échec trop souvent dans l’intervalle configuré, systemd atteint la limite et cesse de la démarrer automatiquement. Le comportement par défaut varie selon la version et la politique de la distribution, mais le mécanisme central est constant : StartLimitIntervalSec + StartLimitBurst. Atteignez cette combinaison et vous verrez le message.
Nuance importante : il ne s’agit pas seulement de Restart=. Même les unités qui ne redémarrent pas d’elles-mêmes peuvent atteindre la limite si quelque chose (une dépendance, un timer, un administrateur lançant répétitivement systemctl start) déclenche des tentatives fréquentes.
Ce que vous devez faire : considérez le message comme un symptôme d’une boucle. La boucle est le problème. Le limiteur est la ceinture de sécurité.
Une citation, parce qu’elle tient toujours : « L’espoir n’est pas une stratégie. » — Gene Kranz
Guide de diagnostic rapide (premier/deuxième/troisième)
Si vous voulez trouver le goulot d’étranglement rapidement, ne commencez pas par éditer les fichiers d’unité. Commencez par répondre à trois questions : Qu’est-ce qui a échoué ? Pourquoi cela a échoué ? Qu’est-ce qui a continué à réessayer ?
Premier : confirmer que le limiteur a stoppé les tentatives
- Cherchez des résultats
start-limit-hitet le code de sortie réel du processus du service. - Confirmez si le service sort immédiatement (binaire défectueux, config invalide) ou s’il time-out (blocage, dépendance indisponible).
Deuxième : trouver la première erreur réelle dans les logs
- Utilisez
journalctl -uavec une fenêtre temporelle serrée. - Cherchez la première défaillance dans la rafale, pas la dernière. La dernière est souvent simplement systemd qui abandonne.
Troisième : cartographier le déclencheur de la boucle
- L’unité est-elle configurée avec
Restart=always? - S’agit-il d’une chaîne de dépendances où un parent continue d’essayer ?
- Un timer ou une unité path la déclenche-t-elle ?
- Quelque chose d’externe exécute-t-il répétitivement
systemctl start(ou un agent de gestion de configuration) ?
Les gains les plus rapides viennent généralement de : corriger la vraie erreur sous-jacente, puis ajuster la sémantique de redémarrage pour qu’un échec ne devienne pas un déni de service contre votre propre hôte.
Tâches pratiques : commandes, sorties, décisions (12+)
Ci‑dessous des tâches éprouvées sur le terrain. Chaque élément inclut : une commande, à quoi ressemble une sortie réaliste, ce que cela signifie, et la décision suivante à prendre.
Task 1: Check unit state and confirm start-limit-hit
cr0x@server:~$ systemctl status myapp.service
× myapp.service - MyApp API
Loaded: loaded (/lib/systemd/system/myapp.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/myapp.service.d
└─override.conf
Active: failed (Result: start-limit-hit) since Mon 2025-12-29 09:12:11 UTC; 2min 3s ago
Duration: 220ms
Process: 18934 ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml (code=exited, status=1/FAILURE)
Main PID: 18934 (code=exited, status=1/FAILURE)
Dec 29 09:12:11 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 5.
Dec 29 09:12:11 server systemd[1]: myapp.service: Start request repeated too quickly.
Dec 29 09:12:11 server systemd[1]: myapp.service: Failed with result 'start-limit-hit'.
Dec 29 09:12:11 server systemd[1]: Failed to start myapp.service - MyApp API.
Signification : systemd a limité le taux de démarrage de l’unité. Le service lui‑même sort avec le statut 1. La boucle de redémarrage a atteint 5 tentatives (seuil de rafale typique).
Décision : ne modifiez pas d’abord les limites de redémarrage. Vous avez une vraie défaillance d’application. Allez consulter les logs pour la première erreur.
Task 2: Pull logs around the failure window (and stop reading the last line only)
cr0x@server:~$ journalctl -u myapp.service -b --since "10 minutes ago" --no-pager
Dec 29 09:12:10 server myapp[18934]: ERROR: cannot read config file: /etc/myapp/config.yml: permission denied
Dec 29 09:12:10 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 09:12:10 server systemd[1]: myapp.service: Failed with result 'exit-code'.
Dec 29 09:12:11 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 1.
Dec 29 09:12:11 server systemd[1]: Started myapp.service - MyApp API.
Dec 29 09:12:11 server myapp[18938]: ERROR: cannot read config file: /etc/myapp/config.yml: permission denied
Dec 29 09:12:11 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 09:12:11 server systemd[1]: myapp.service: Start request repeated too quickly.
Signification : la vraie défaillance est un problème de permissions sur le fichier de configuration. Le limiteur est en aval.
Décision : corrigez la propriété/permissions du fichier ou le User/Group de l’unité. N’augmentez pas le burst pour « faire fonctionner » artificiellement le service.
Task 3: Inspect the effective unit configuration (what systemd really runs)
cr0x@server:~$ systemctl cat myapp.service
# /lib/systemd/system/myapp.service
[Unit]
Description=MyApp API
After=network-online.target
Wants=network-online.target
[Service]
User=myapp
Group=myapp
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml
Restart=on-failure
RestartSec=1
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/myapp.service.d/override.conf
[Service]
RestartSec=250ms
Signification : il y a un drop-in override définissant RestartSec=250ms. Cela fait que les services oscillants atteignent rapidement le limiteur.
Décision : gardez un délai de redémarrage raisonnable (2–10 secondes) sauf raison très valable. Vos logs et vos disques vous remercieront.
Task 4: Identify start limit settings (defaults vs overridden)
cr0x@server:~$ systemctl show myapp.service -p StartLimitIntervalSec -p StartLimitBurst -p Restart -p RestartUSec
StartLimitIntervalSec=10s
StartLimitBurst=5
Restart=on-failure
RestartUSec=250ms
Signification : avec un intervalle de 10 secondes et un burst de 5, un délai de redémarrage de 250ms peut déclencher la limite presque immédiatement.
Décision : corrigez l’erreur sous-jacente ; ensuite définissez RestartSec=2s ou plus sauf si le service est sûr à redémarrer fortement.
Task 5: Reset the failed state (only after you made a change)
cr0x@server:~$ systemctl reset-failed myapp.service
cr0x@server:~$ systemctl start myapp.service
cr0x@server:~$ systemctl status myapp.service
● myapp.service - MyApp API
Loaded: loaded (/lib/systemd/system/myapp.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/myapp.service.d
└─override.conf
Active: active (running) since Mon 2025-12-29 09:16:42 UTC; 3s ago
Main PID: 19501 (myapp)
Tasks: 8
Memory: 34.2M
CPU: 210ms
CGroup: /system.slice/myapp.service
└─19501 /usr/local/bin/myapp --config /etc/myapp/config.yml
Signification : la limite de démarrage bloquait les démarrages ; reset-failed l’a effacée. Le service fonctionne maintenant.
Décision : si cela replante après le reset, vous n’avez pas corrigé la cause racine. Retournez aux logs.
Task 6: Confirm the service user can read what it needs
cr0x@server:~$ sudo -u myapp test -r /etc/myapp/config.yml && echo OK || echo NO
NO
cr0x@server:~$ ls -l /etc/myapp/config.yml
-rw------- 1 root root 912 Dec 29 08:58 /etc/myapp/config.yml
Signification : la config est accessible uniquement par root. Si le service s’exécute en User=myapp, il ne peut pas la lire.
Décision : changez la propriété/groupe, ajustez les permissions, ou exécutez le service sous un autre utilisateur (dernier recours).
Task 7: Spot dependency chain failures (the “it’s not my unit” problem)
cr0x@server:~$ systemctl list-dependencies --reverse myapp.service
myapp.service
● myapp.target
● multi-user.target
Signification : Peu de choses l’invoquent ; elle est probablement activée directement.
Décision : si la liste des dépendances inverses montre une chaîne plus large (comme un target ou un autre service), vous pourriez devoir corriger l’unité parente aussi.
Task 8: Identify whether a timer or path unit is repeatedly triggering the service
cr0x@server:~$ systemctl list-timers --all | grep -E 'myapp|myapp-'
Mon 2025-12-29 09:20:00 UTC 2min 10s left Mon 2025-12-29 09:10:00 UTC 7min ago myapp-refresh.timer myapp-refresh.service
cr0x@server:~$ systemctl status myapp-refresh.timer
● myapp-refresh.timer - MyApp refresh timer
Loaded: loaded (/lib/systemd/system/myapp-refresh.timer; enabled; preset: enabled)
Active: active (waiting) since Mon 2025-12-29 08:10:00 UTC; 1h 10min ago
Trigger: Mon 2025-12-29 09:20:00 UTC; 2min 10s left
Signification : Une autre unité peut appeler votre service (ou un service lié) régulièrement. Un job de rafraîchissement défaillant peut ressembler à « myapp oscille » s’il partage des ressources.
Décision : si le timer est trop agressif ou cassé, corrigez l’unité timer ou désactivez-la jusqu’à stabilisation.
Task 9: Check exit codes and signal reasons across restarts
cr0x@server:~$ systemctl show myapp.service -p ExecMainStatus -p ExecMainCode -p ExecMainStartTimestamp -p ExecMainExitTimestamp
ExecMainStatus=1
ExecMainCode=exited
ExecMainStartTimestamp=Mon 2025-12-29 09:12:10 UTC
ExecMainExitTimestamp=Mon 2025-12-29 09:12:10 UTC
Signification : Il sort immédiatement. C’est généralement la configuration, les permissions, des fichiers manquants, des bibliothèques manquantes, ou « impossible de binder le port ». Ce n’est pas une dépendance lente.
Décision : priorisez la validation de configuration et les problèmes d’environnement ; ne perdez pas de temps à augmenter TimeoutStartSec.
Task 10: Verify ports and socket activation assumptions
cr0x@server:~$ ss -lntp | grep ':8080'
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("nginx",pid=1201,fd=6))
Signification : Quelqu’un d’autre occupe le port 8080 (nginx ici). Si myapp s’attend à le binder, il va boucler et crasher.
Décision : changez le port d’écoute de myapp, ajustez le proxy nginx, ou utilisez correctement l’activation par socket systemd (ne faites pas à moitié).
Task 11: Validate unit file syntax and hidden typos
cr0x@server:~$ systemd-analyze verify /etc/systemd/system/myapp.service
/etc/systemd/system/myapp.service:12: Unknown lvalue 'RestartSecs' in section 'Service'
Signification : Systemd a ignoré une directive mal orthographiée. Vous pensiez avoir un délai de redémarrage ; ce n’est pas le cas.
Décision : corrigez la faute de frappe, exécutez daemon-reload, et revérifiez systemctl show pour les valeurs effectives.
Task 12: Inspect environment seen by the service (PATH, vars, working dir)
cr0x@server:~$ systemctl show myapp.service -p Environment -p EnvironmentFile -p WorkingDirectory -p User -p Group
Environment=
EnvironmentFile=
WorkingDirectory=/
User=myapp
Group=myapp
Signification : Aucun environnement n’est défini et le répertoire de travail est root. Si votre application attend des chemins relatifs, elle peut échouer instantanément.
Décision : définissez WorkingDirectory= et Environment= ou EnvironmentFile= explicites. Évitez de compter sur les valeurs d’un shell interactif.
Task 13: Find who changed what (drop-ins, vendor units, overrides)
cr0x@server:~$ systemctl show myapp.service -p FragmentPath -p DropInPaths
FragmentPath=/lib/systemd/system/myapp.service
DropInPaths=/etc/systemd/system/myapp.service.d/override.conf
Signification : L’unité fournisseur est dans /lib, et votre override local est dans /etc. C’est le schéma correct sur Debian.
Décision : ne modifiez jamais directement /lib/systemd/system/*.service. Placez les changements en drop-ins pour qu’ils persistent après les mises à jour de paquets.
Task 14: Track down “it works manually but not as a service”
cr0x@server:~$ sudo -u myapp /usr/local/bin/myapp --config /etc/myapp/config.yml
ERROR: cannot open database socket /run/postgresql/.s.PGSQL.5432: no such file or directory
Signification : Lorsqu’exécuté en tant qu’utilisateur du service, il ne peut pas atteindre une dépendance (socket PostgreSQL). Peut-être que postgres n’est pas lancé, ou qu’il écoute ailleurs, ou que les permissions le bloquent.
Décision : corrigez la disponibilité de la dépendance et la configuration ; puis ajustez l’ordre des unités (After=) seulement si nécessaire.
Task 15: Visualize boot ordering and critical chain (slow starts and timeouts)
cr0x@server:~$ systemd-analyze critical-chain myapp.service
myapp.service +4.212s
└─network-online.target +4.198s
└─systemd-networkd-wait-online.service +4.150s
└─systemd-networkd.service +1.021s
└─systemd-udevd.service +452ms
Signification : Votre unité est bloquée par network-online.target, qui attend que le réseau soit « en ligne ». Cela peut être acceptable—ou un piège de délai au démarrage.
Décision : si votre service n’exige pas vraiment d’être « en ligne », passez à After=network.target et supprimez la dépendance wait-online. Moins de surprises au démarrage.
Correctifs systemd durables (et pourquoi)
Les « correctifs qui tiennent » ont deux caractéristiques : ils survivent aux mises à jour, et ils reflètent le comportement réel du service. La plupart des services qui oscillent sont soit mal spécifiés (mauvaise sémantique d’unité) soit cassés à l’exécution (config, permissions, dépendances). La réponse correcte est généralement un petit drop-in d’override plus une vraie correction dans l’environnement applicatif.
Utilisez des drop-ins, pas des modifications dans /lib
Sur Debian, les fichiers d’unité gérés par les paquets vivent sous /lib/systemd/system. Les modifications locales appartiennent à /etc/systemd/system. Si vous éditez les fichiers sous /lib, une mise à jour de paquet finira par « corriger » votre correctif.
cr0x@server:~$ sudo systemctl edit myapp.service
# (editor opens)
Ajoutez un drop-in comme ceci :
cr0x@server:~$ cat /etc/systemd/system/myapp.service.d/override.conf
[Service]
Restart=on-failure
RestartSec=5s
TimeoutStartSec=30s
[Unit]
StartLimitIntervalSec=60s
StartLimitBurst=3
Pourquoi cela tient : c’est dans /etc, donc les mises à jour ne l’écraseront pas. Cela rend aussi votre service moins susceptible d’étrangler l’hôte : 5 secondes entre les tentatives, et seulement 3 tentatives par minute avant que systemd n’arrête et n’exige l’intervention humaine.
À éviter : définir StartLimitBurst=1000 ou RestartSec=100ms parce que vous « voulez une récupération rapide ». Ce n’est pas une récupération ; c’est une bombe à forks avec un meilleur branding.
Faites correspondre la logique de redémarrage aux modes de défaillance
La plupart des services n’ont pas besoin de Restart=always. Utilisez-le uniquement pour les démons qui doivent être présents et sont sûrs à redémarrer quel que soit le motif de sortie. Préférez :
Restart=on-failurepour les serveurs typiques qui peuvent quitter volontairement lors de mises à jour ou de reloads de config.Restart=nopour les tâches one-shot qui doivent échouer bruyamment et s’arrêter.Restart=on-abnormalsi vous voulez redémarrer sur signaux ou dumps, pas sur des sorties propres non‑zéro (utile dans certains scénarios).
Corrigez Type= et la readiness, sinon systemd vous « aidera » dans une boucle
Un autre classique : le service va bien, mais systemd pense qu’il n’a pas démarré. Cela arrive quand Type= est incorrect.
Type=simple(par défaut) : le processus démarre et reste au premier plan. La plupart des applis correspondent à ce mode.Type=forking: daemons legacy qui forkent en arrière-plan. Si vous utilisez cela pour une appli foreground, systemd peut mal interpréter son état et la tuer/redémarrer.Type=notify: l’appli doit appeler sd_notify. Si elle ne le fait pas, systemd peut time‑out et la redémarrer.Type=oneshot: tâches qui s’exécutent puis sortent ; combinez avecRemainAfterExit=yessi pertinent.
Définissez le type correctement. Si vous ne contrôlez pas l’appli et qu’elle ne supporte pas sd_notify, ne faites pas semblant.
Utilisez ExecStartPre pour validation, mais ne l’armes pas
ExecStartPre= est un bon endroit pour valider la config avant de consommer une tentative de démarrage. Bien fait, cela empêche le flapping. Mal fait, cela crée du flapping.
Exemple : valider qu’un fichier de config existe et est lisible par l’utilisateur du service :
cr0x@server:~$ cat /etc/systemd/system/myapp.service.d/validate.conf
[Service]
ExecStartPre=/usr/bin/test -r /etc/myapp/config.yml
Si ce test échoue, l’unité échoue rapidement avec une raison claire. Votre politique de redémarrage devrait être conservatrice ici. Vous ne voulez pas que l’hôte martèle le même fichier manquant 20 fois par seconde.
Rendez les dépendances explicites, mais ne surchargez pas le démarrage
Ajouter After=postgresql.service peut aider si votre appli ne peut vraiment pas démarrer tant que la BD n’est pas prête. Mais trop d’équipes appliquent par mimétisme « wait for network online » et « wait for everything ». Alors un serveur DHCP lent prend la moitié du démarrage en otage.
Position pratique :
- Utilisez
Wants=pour des dépendances souples que vous acceptez de dégrader. - Utilisez
Requires=pour des dépendances dures. Mais sachez que si la dépendance échoue, votre unité échoue aussi. - Privilégiez que votre appli fasse ses propres retries pour les services en amont (BDD, API) pendant que systemd la garde en marche, plutôt que systemd ne la redémarre constamment.
Blague #1 : Les boucles de redémarrage sont comme les réunions générales d’entreprise—beaucoup d’activité, aucun progrès, et tout le monde repart plus fatigué.
Sachez quand ajuster StartLimit* (et quand pas)
Il existe des cas légitimes pour changer les limites de démarrage :
- Services qui peuvent échouer brièvement pendant une maintenance en amont et qui peuvent se réessayer en toute sécurité sur plusieurs minutes.
- Agents qui se connectent à des endpoints distants et qui se retrouvent parfois en concurrence au démarrage.
Mais changer les limites n’est pas une correction pour « permission denied » ou « config invalide ». Dans ces cas, augmenter les retries transforme une erreur en agitation.
Utilisez systemd-run pour reproduire en sécurité
Si vous devez reproduire l’environnement du service sans éditer l’unité réelle, systemd-run peut créer des unités transitoires avec des contraintes similaires. C’est un bon moyen de tester des hypothèses sur l’utilisateur, le répertoire de travail et l’environnement.
cr0x@server:~$ sudo systemd-run --unit=myapp-test --property=User=myapp --property=WorkingDirectory=/var/lib/myapp /usr/local/bin/myapp --config /etc/myapp/config.yml
Running as unit: myapp-test.service
cr0x@server:~$ systemctl status myapp-test.service
● myapp-test.service - /usr/local/bin/myapp --config /etc/myapp/config.yml
Loaded: loaded (/run/systemd/transient/myapp-test.service; transient)
Active: failed (Result: exit-code) since Mon 2025-12-29 09:22:06 UTC; 1s ago
Process: 20411 ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml (code=exited, status=1/FAILURE)
Décision : si cela échoue de la même manière, ce n’est pas de la « magie systemd ». C’est l’environnement d’exécution.
Modes de panne typiques derrière le message
Voici ce que je vois le plus souvent dans des flottes Debian. Le message de limite est juste le portier. Voici les invités ivres.
1) Mismatch de permissions et de propriété
Le service s’exécute en tant qu’utilisateur non-root. Les fichiers de config, sockets ou répertoires d’état sont root-owned. Le binaire sort immédiatement avec une erreur utile, mais l’unité redémarre trop vite, atteint les limites de démarrage, et vous vous retrouvez à regarder la mauvaise ligne.
2) Mauvais Type de service
Programme au premier plan étiqueté comme forking, ou notify sans support de notification. Systemd interprète qu’il n’a pas démarré, attend, tue, redémarre, répète.
3) Crash rapide sur dépendance manquante
Exemple : socket de base de données manquant au démarrage. L’appli sort rapidement ; systemd redémarre ; répète. Corrigez soit l’ordre, soit faites en sorte que l’appli tolère l’indisponibilité.
4) Échecs de binding
Port déjà utilisé, port privilégié sans capacité, ou mauvaise adresse d’écoute. Ces échecs sont instantanés et bouclent agressivement si le délai de redémarrage est petit.
5) ExecStart pointe vers un script wrapper avec un mauvais shebang
/bin/bash peut ne pas exister dans des conteneurs minimaux. Ou le script utilise set -e et arrête pour une variable d’environnement manquante. Systemd le redémarrera jusqu’à l’overdose.
6) Timeouts et démarrages lents
Le processus démarre mais n’est pas « ready » avant TimeoutStartSec. Souvent couplé à une mauvaise configuration Type=notify.
7) Gestion de configuration qui vous combat
Vous « corrigez » l’override manuellement, et 5 minutes plus tard un agent de gestion de config le restaure. L’unité replante et atteint les limites. Félicitations, vous avez découvert la main invisible de la conformité.
Erreurs courantes : symptôme → cause racine → correctif
Cette section vise à changer vos décisions. Si vous reconnaissez un symptôme, évitez l’auto-reproche et allez droit au correctif réel.
Symptôme : « Requête de démarrage répétée trop rapidement » après avoir mis Restart=always
Cause racine : Vous avez masqué une vraie défaillance avec un redémarrage agressif. L’appli sort pour une raison—config, permission, dépendance, crash.
Correctif : passez à Restart=on-failure, mettez RestartSec=5s, puis localisez la première erreur dans journalctl -u. Ne retouchez les limites qu’après stabilité.
Symptôme : Le service démarre manuellement mais échoue sous systemd
Cause racine : environnement différent : working directory, PATH, variables d’environnement, permissions utilisateur, ou configuration interactive manquante.
Correctif : inspectez systemctl show -p WorkingDirectory -p Environment, définissez-les explicitement, et testez avec sudo -u.
Symptôme : L’unité « time out » puis atteint la limite de démarrage
Cause racine : mauvais Type= (notify sans notify) ou disponibilité lente d’une dépendance ; systemd la tue après TimeoutStartSec.
Correctif : mettez Type=simple sauf si vous savez mieux, ou implémentez la notification correcte ; ajustez TimeoutStartSec seulement après avoir confirmé que c’est vraiment lent et non bloqué.
Symptôme : « Failed to start » avec exit-code, mais pas de logs applicatifs utiles
Cause racine : stdout/stderr n’atteignent pas le journal (logging personnalisé), ou le processus plante avant que son logger ne s’initialise ; parfois ExecStart pointe vers le mauvais fichier.
Correctif : confirmez qu’ExecStart existe et est exécutable ; ajoutez temporairement StandardOutput=journal et StandardError=journal dans un drop-in ; vérifiez avec systemctl cat.
Symptôme : Le correctif fonctionne jusqu’au reboot, puis replante
Cause racine : répertoires d’état dans /run non créés, ou tmpfiles manquants ; ou ordonnancement dépendant du timing de démarrage.
Correctif : utilisez RuntimeDirectory= ou StateDirectory= dans l’unité ; assurez les permissions via systemd plutôt que via des scripts ad‑hoc.
Symptôme : Start limit hit seulement pendant les déploiements
Cause racine : scripts de déploiement redémarrant à répétition pendant le remplacement des binaires/config, provoquant des échecs transitoires en boucle serrée.
Correctif : coordonnez les étapes de déploiement : stoppez l’unité, remplacez les artefacts, validez la config, puis démarrez une fois. Envisagez ExecReload si applicable.
Symptôme : L’unité affiche « start-limit-hit » même après avoir corrigé l’appli
Cause racine : systemd se souvient de l’état d’échec.
Correctif : exécutez systemctl reset-failed myapp.service, puis démarrez à nouveau. Si ça retombe, vous n’êtes pas réparé.
Trois mini-récits d’entreprise tirés du terrain
Mini-récit 1 : L’incident causé par une mauvaise hypothèse
Ils avaient un service API basé sur Debian derrière un load balancer. Après un reboot de maintenance, la moitié du pool revenait « unhealthy ». Les tableaux de bord criaient, mais n’aidaient pas : les hôtes étaient UP, le réseau semblait OK, et systemd crachotait « Start request repeated too quickly » comme s’il s’ennuyait de toute l’affaire.
L’hypothèse d’astreinte était classique : « network-online.target est capricieux ». Donc ils ont fait ce que font les gens sous stress—ils ont allongé l’attente. Quelqu’un a augmenté TimeoutStartSec à quelques minutes et ajouté d’autres After=, sérialisant le démarrage derrière plus de targets.
La vraie cause était banale : le service tournait en User=api, et un script post-install du paquet avait réécrit le fichier de config en permissions root-only. L’appli sortait instantanément avec « permission denied », puis systemd faisait sa boucle de redémarrage jusqu’à ce que la limitation démarre. Rien à voir avec le réseau. Tout à voir avec les modes de fichier.
Une fois qu’ils ont consulté journalctl -u pour la première erreur et exécuté sudo -u api test -r, c’était évident. Ils ont corrigé la propriété et ajouté une validation stricte des permissions dans le CI. Le « problème systemd » a disparu, parce que ce n’était jamais un problème systemd.
Ils ont aussi supprimé les dépendances de démarrage ajoutées en panique. Ce nettoyage a compté : il a réduit le temps de boot et évité des pannes non liées plus tard. La leçon n’était pas « ne pas supposer ». C’est trop générique. La leçon : supposez d’abord les choses banales—permissions, chemins, ports—avant de construire une mythologie autour des targets et de l’ordre.
Mini-récit 2 : L’optimisation qui s’est retournée contre eux
Une équipe plateforme voulait une récupération plus rapide des pannes transitoires. Leur raisonnement : si un service meurt, redémarrez-le immédiatement. Ils ont donc mis Restart=always et RestartSec=200ms sur une flotte de microservices internes. Ils ont aussi augmenté StartLimitBurst parce que « on ne veut pas que systemd abandonne ».
Ça a semblé correct pendant quelques semaines. Puis une dépendance a commencé à renvoyer des réponses malformées suite à un mauvais déploiement. Un client segfaultait en les parsant. Avec la nouvelle politique, il ne s’est pas contenté de crasher—il a mitraillette de redémarrages. Chaque redémarrage rechargeait la config, ouvrait des connexions, écrivait des logs et chamboulait les caches CPU. Les hôtes n’ont pas cramé instantanément, mais la latence a explosé. Pas parce que le service était down, mais parce qu’il oscillait assez fort pour affamer ses voisins.
Ils avaient remplacé « un crash » par « un déni de service soutenu contre leurs propres nœuds ». Pire, l’augmentation de StartLimitBurst faisait que systemd participait plus longtemps au chaos. Le rayon d’action s’est étendu d’un client cassé à un cluster bruyant.
Le correctif n’a pas été héroïque. Ils ont rollbacké les paramètres agressifs, rétabli des limites conservatrices, et ajouté un backoff côté application quand les données en amont semblaient incorrectes. Puis ils ont adopté Restart=on-failure plus RestartSec=5s pour la plupart des services, réservant les redémarrages rapides à quelques démons vraiment stateless et bien comportés.
Ils ont gardé une « optimisation » : un court ExecStartPre de validation de config pour que la mauvaise config échoue une fois et reste en échec. C’est le genre d’échec rapide voulu—rapide à détecter, pas rapide à répéter.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Un démon adjacent au stockage montait et vérifiait des volumes chiffrés avant que les services dépendants démarrent. Pas glamour. Mais fondamental. L’équipe l’avait implémenté avec trois habitudes peu sexy : drop-ins d’unité dans /etc, StateDirectory= et RuntimeDirectory= explicites, et une courte vérification de santé qui logguait clairement dans le journal.
Un matin, après une mise à jour OS, un sous-ensemble d’hôtes a commencé à échouer l’étape de préparation des volumes. Les services dépendants montraient tous « Requête de démarrage répétée trop rapidement », parce que leur montage requis n’était jamais apparu. La différence était que les logs de l’unité de préparation étaient propres et spécifiques : un module kernel manquant sur ce sous-ensemble d’hôtes.
Parce que les dépendances étaient modélisées avec Requires= et un ordonnancement clair, le mode de panne était contrôlé. Au lieu que chaque service dépendant oscille, ils échouaient rapidement et restaient en bas. Ça semble mauvais jusqu’à ce que vous ayez vu l’alternative : une foule de redémarrages qui enterre la cause racine sous le bruit.
Le correctif : installer le paquet du module manquant et reconstruire initramfs sur les nœuds touchés. La reprise a été tout aussi simple : systemctl reset-failed sur les targets et les démarrer. Pas de bizarrerie, pas d’éditions mystères dans les unités fournisseurs, pas de symlinks « temporaires » vivants dans /lib.
Blague #2 : Le meilleur truc SRE est de rendre les pannes ennuyeuses—parce que l’excitation n’est que du downtime avec un meilleur marketing.
Listes de contrôle / plan étape par étape
Étape par étape : stabiliser une unité oscillante sans masquer le problème
- Geler la boucle : si elle marteau l’hôte, arrêtez-la.
cr0x@server:~$ sudo systemctl stop myapp.serviceDécision : si l’arrêt améliore immédiatement la charge système, vous étiez en plein restart storm. Gardez-la arrêtée pendant le débogage.
- Obtenir la vraie erreur : lisez les logs pour la première défaillance.
cr0x@server:~$ journalctl -u myapp.service -b --since "30 minutes ago" --no-pager | head -n 60 Dec 29 09:12:10 server myapp[18934]: ERROR: cannot read config file: /etc/myapp/config.yml: permission denied Dec 29 09:12:10 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE ...Décision : si vous voyez une erreur applicative claire, corrigez-la d’abord. Si les logs sont vides, validez ExecStart et activez la sortie vers le journal temporairement.
- Confirmer la configuration effective de l’unité :
cr0x@server:~$ systemctl show myapp.service -p FragmentPath -p DropInPaths -p User -p Group -p ExecStart -p Type -p Restart -p RestartUSec FragmentPath=/lib/systemd/system/myapp.service DropInPaths=/etc/systemd/system/myapp.service.d/override.conf User=myapp Group=myapp ExecStart={ path=/usr/local/bin/myapp ; argv[]=/usr/local/bin/myapp --config /etc/myapp/config.yml ; ... } Type=simple Restart=on-failure RestartUSec=250msDécision : si RestartSec est trop petit, corrigez-le dans un override. Si Type semble incorrect, corrigez-le. Si User/Group ne correspond pas à la propriété des fichiers, corrigez les permissions ou l’unité.
- Appliquer un override durable via drop-in :
cr0x@server:~$ sudo systemctl edit myapp.serviceUtilisez des réglages comme
RestartSec=5set des limites de démarrage conservatrices pendant que vous stabilisez. - Recharger systemd et réinitialiser l’échec :
cr0x@server:~$ sudo systemctl daemon-reload cr0x@server:~$ sudo systemctl reset-failed myapp.serviceDécision : si vous oubliez daemon-reload, vos modifications peuvent ne pas s’appliquer. Si vous oubliez reset-failed, systemd peut encore refuser de démarrer.
- Démarrer une fois, observer :
cr0x@server:~$ sudo systemctl start myapp.service cr0x@server:~$ systemctl status myapp.service --no-pager ● myapp.service - MyApp API Active: active (running) since Mon 2025-12-29 09:30:01 UTC; 2s agoDécision : si cela échoue à nouveau, ne continuez pas à redémarrer manuellement. Retournez aux logs ; itérez avec méthode.
Checklist : « Cette unité mérite-t-elle vraiment un redémarrage ? »
- Si c’est un job batch : utilisez
Type=oneshot, envisagezRestart=no, et échouez bruyamment. - Si c’est un démon qui doit être présent :
Restart=on-failureest généralement correct. - Si l’unité sort proprement dans le cadre d’un fonctionnement normal : évitez
Restart=alwaysou vous créerez une boucle par conception. - Si elle dépend du réseau ou de services en amont : privilégiez le retry/backoff côté application pendant que le processus reste en marche.
Checklist : valeurs par défaut sûres pour la plupart des services internes
Restart=on-failureRestartSec=5s(2s pour des démons très légers et stateless ; 10s pour les lourds)StartLimitIntervalSec=60sStartLimitBurst=3(5 est aussi acceptable ; choisissez quelque chose qui force l’attention humaine)User=,Group=explicitesWorkingDirectory=si l’appli utilise des chemins relatifsRuntimeDirectory=/StateDirectory=pour les répertoires nécessaires au service
Faits et histoire intéressants (édition limitation de démarrage)
- Systemd n’a pas inventé les boucles de redémarrage ; il les a juste rendues plus faciles à exprimer avec
Restart=et plus visibles avec l’état structuré d’unité. - La « limite de démarrage » est par unité, pas globale. Un service oscillant peut être contenue sans punir les autres—sauf s’il affame la machine.
- Les répertoires drop-in (
/etc/systemd/system/UNIT.d/*.conf) existent précisément pour que les mises à jour de paquets n’écrasent pas l’intention opérationnelle locale. - La séparation Debian entre /lib et /etc est un choix de politique délibéré : fichiers fournisseur dans
/lib, modifications administratives dans/etc. - Le journal de systemd a été conçu pour capturer des métadonnées structurées (nom d’unité, PID, cgroup) afin que vous puissiez répondre à « que s’est‑il passé ? » sans archéologie de grep.
- La limitation de démarrage est une fonction de sécurité qui empêche les inondations de logs et l’usure CPU ; c’est essentiellement un disjoncteur pour la supervision de processus.
- Type=notify provient du désir de remplacer le « sleep 5 et espérer » par un signal explicite de readiness—excellent quand utilisé correctement, punitif quand simulé.
- network-online.target est souvent mal compris : ce n’est pas « le réseau existe », c’est « un composant déclare que le réseau est configuré », ce qui peut être lent ou incorrect selon la pile.
- Réinitialiser les unités en échec est une action opérateur explicite parce que systemd considère l’échec répété comme un état significatif, pas un glitch transitoire à ignorer.
FAQ
1) « Start request repeated too quickly » signifie-t‑il que systemd est cassé ?
Non. Cela signifie que votre service a échoué à plusieurs reprises et que systemd a appliqué sa limite de rythme configurée. Le service est défaillant (ou mal spécifié), et systemd empêche un churn infini.
2) Comment effacer la limite de démarrage pour réessayer ?
Après avoir apporté une vraie correction, exécutez :
cr0x@server:~$ sudo systemctl reset-failed myapp.service
cr0x@server:~$ sudo systemctl start myapp.service
Si cela atteint immédiatement la limite à nouveau, vous n’avez pas corrigé la cause racine.
3) Dois‑je augmenter StartLimitBurst pour le rendre plus « résilient » ?
Généralement non. Augmenter le burst masque les vraies défaillances et accroît l’agitation sur l’hôte. Préférez corriger l’erreur réelle et utiliser un RestartSec sensé. Si vous augmentez le burst, faites‑le modestement et intentionnellement.
4) Quelle est la différence entre RestartSec et StartLimitIntervalSec ?
RestartSec est le délai entre les tentatives de redémarrage. StartLimitIntervalSec est la fenêtre sur laquelle systemd compte les démarrages échoués, et StartLimitBurst est le nombre maximal de tentatives autorisées dans cette fenêtre.
5) Pourquoi cela échoue seulement au démarrage mais fonctionne si je le lance plus tard ?
Le timing du boot expose des problèmes de dépendance : montages manquants, réseau indisponible, BD pas prête, répertoires runtime pas créés. Modélisez la dépendance (ou faites que l’appli la tolère) et évitez de surutiliser network-online.target.
6) J’ai édité l’unité mais rien n’a changé. Pourquoi ?
Causes courantes : vous avez édité l’unité fournisseur sous /lib et elle a été remplacée, ou vous avez oublié systemctl daemon-reload, ou votre réglage était mal orthographié et ignoré. Exécutez systemd-analyze verify et systemctl show pour confirmer les valeurs effectives.
7) Est‑ce acceptable de mettre Restart=always pour des services critiques ?
Parfois, oui. Mais uniquement si vous comprenez le comportement de sortie du service et qu’il est sûr de le redémarrer sans condition. Beaucoup de services sortent volontairement lors de mises à jour ou de modifications de configuration ; Restart=always peut lutter contre ces workflows.
8) Comment savoir si un timer ou autre chose déclenche des redémarrages ?
Vérifiez les timers et les dépendances inverses :
cr0x@server:~$ systemctl list-timers --all
cr0x@server:~$ systemctl list-dependencies --reverse myapp.service
Si un timer déclenche un job chaque minute, vous voyez peut‑être des tentatives répétées qui ne viennent pas de Restart=.
9) Quand devrais‑je changer TimeoutStartSec ?
Seulement après avoir confirmé que le service est légitimement lent à être prêt. Si l’appli sort immédiatement, augmenter le timeout ne sert à rien. Si Type=notify est incorrect, vous réglez le mauvais niveau.
10) Puis‑je faire en sorte que systemd log plus sur pourquoi il a arrêté d’essayer ?
Systemd loggue déjà l’événement de start-limit. Le manque est souvent la sortie d’erreur de l’appli elle‑même. Assurez‑vous qu’elle écrit sur stderr/stdout ou configurez le logging pour que journalctl -u capture la première raison d’échec.
Conclusion : étapes suivantes pour rester hors du fossé
« Requête de démarrage répétée trop rapidement » est une bienveillance. C’est systemd qui vous dit que le service échoue en boucle serrée et qu’il ne va pas vous aider à fumer l’hôte. Traitez‑le comme un point de contrôle, pas comme la cause racine.
Étapes suivantes qui fonctionnent réellement dans des environnements Debian 13 :
- Trouvez la première erreur réelle avec
journalctl -u UNITet arrêtez de ne lire que la dernière ligne. - Inspectez la configuration effective avec
systemctl showetsystemctl cat. Faites confiance à ce que systemd exécute, pas à ce que vous pensez avoir écrit. - Corrigez le problème d’exécution (permissions, ports, dépendances, répertoire de travail) avant de toucher aux limites.
- Appliquez des changements durables avec des drop-ins sous
/etc/systemd/system/UNIT.d/, puisdaemon-reload. - Définissez des sémantiques de redémarrage sensées :
Restart=on-failure,RestartSecraisonnable,StartLimit*conservateurs. - Réinitialisez l’état d’échec seulement après les modifications :
systemctl reset-failed.
Si vous ne faites qu’une chose : arrêtez de traiter systemd comme une machine à sous. Tirez moins le levier. Lisez plus les logs. Le service démarrera — ou il échouera pour une raison que vous pourrez corriger.