Service systemd qui ne démarre pas ? Voici le correctif
Service systemd qui ne démarre pas ? Lis journalctl comme il faut, corrige les cinq causes les plus fréquentes et rends l'unité active en quelques minutes.
Un service systemd qui refuse de démarrer, ce n'est presque jamais mystérieux : systemctl status <unit>, puis les 50 dernières lignes de journal pour cette unité avec journalctl -u <unit> -n 50 --no-pager, et l'une de cinq causes est généralement nommée noir sur blanc. Un mauvais chemin, un binaire absent, des permissions erronées, un refus SELinux/AppArmor ou une erreur de syntaxe dans le fichier d'unité. La raison de l'échec est dans le log ; les correctifs ci-dessous ne font que du pattern matching contre lui.
Comment voir pourquoi un service systemd a échoué ?
D'abord le statut, ensuite le journal :
systemctl status myapp.service
journalctl -u myapp.service -n 50 --no-pager
status te donne l'état (inactive (dead), failed (exit-code), activating (auto-restart)) et les dernières lignes de log. Le journal te donne l'histoire complète : stdout, stderr et les plaintes de systemd lui-même sur l'unité.
Si l'unité a échoué avant et que tu veux la trace de cette exécution-là, ajoute -b pour le boot courant ou --since today :
journalctl -u myapp.service -b --no-pager
Pour la boîte à outils complète — boots, priorités, suivi de la sortie en direct — voir le mémo journalctl. C'est le même réflexe.
Encore une paire utile à connaître :
systemctl list-units --failed
systemctl reset-failed myapp.service
La première liste toutes les unités en rouge de la machine. La seconde efface l'état « failed » une fois que tu as corrigé — cosmétique, mais ça évite aux pages de statut de crier au loup.
Quelles sont les causes les plus fréquentes ?
Quand le log nomme le symptôme, la cause est presque toujours l'une de ces cinq :
| Le log dit | Cause probable | Correctif |
|---|---|---|
status=203/EXEC |
Mauvais chemin ExecStart= ou interpréteur manquant |
Chemin absolu, chmod +x, vérifie le shebang |
status=203/EXEC sur un script |
Le script a des fins de ligne CRLF ou un mauvais shebang | dos2unix script.sh, corrige la première ligne |
status=1/FAILURE, aucun log de l'app |
Répertoire de travail ou variable d'env manquante | Définis WorkingDirectory=, ajoute Environment= |
Permission denied |
L'utilisateur ne peut pas lire les fichiers ni lier le port | Corrige les droits ; les ports sous 1024 demandent AmbientCapabilities=CAP_NET_BIND_SERVICE ou root |
Unit is masked |
Quelqu'un a lancé systemctl mask |
systemctl unmask myapp.service |
| L'édition du fichier d'unité « ne fait rien » | Le daemon n'a pas été rechargé | systemctl daemon-reload |
La famille 203/EXEC mérite une mention à part, parce que c'est celle qui dévore des après-midis. Systemd ne passe pas par ton shell pour lancer ExecStart=. Concrètement :
# Faux — pas de shell, ~ jamais expansé, pas de recherche dans le PATH
ExecStart=~/app/run.sh
# Correct
ExecStart=/opt/app/run.sh
Et le script lui-même doit être exécutable et commencer par un vrai shebang (#!/bin/bash ou #!/usr/bin/env bash). Un script qui tourne très bien dans ton terminal mais qui échoue avec un 203 sous systemd, c'est presque toujours l'une de ces quatre : pas exécutable, fins de ligne CRLF, shebang qui pointe nulle part, ou chemin relatif.
Pourquoi ça démarre à la main mais pas au boot ?
Le bug d'ordonnancement classique. Si le log montre des échecs juste après le boot mais que l'unité démarre très bien quand tu la lances à la main avec systemctl start, ton service perd une course — il tend vers le réseau, un disque monté ou une base de données avant que ça existe.
Le correctif : déclarer les dépendances au lieu d'espérer :
[Unit]
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
ExecStartPre=/usr/bin/test -f /opt/app/config.toml
After= ordonne le démarrage ; Wants= fait que systemd monte réellement la dépendance. network-online.target ne fonctionne que si le service d'attente réseau est activé sur ta distro — vérifie avec systemctl is-enabled NetworkManager-wait-online.service (ou l'équivalent systemd-networkd). Le garde-fou ExecStartPre= est un moyen pas cher et honnête d'échouer bruyamment avec un message lisible plutôt qu'une stack trace.
Seconde variante : le service démarre au boot mais meurt aussitôt. Cherche ce que ton environnement interactif avait et que le boot n'a pas — différences de PATH, un virtualenv, un HOME. Définis explicitement ce qu'il faut :
[Service]
Environment="PATH=/opt/app/venv/bin:/usr/bin"
User=appuser
WorkingDirectory=/opt/app
Pourquoi mon service n'écrit rien dans le journal ?
Si journalctl -u ne montre rien, vérifie trois choses dans l'ordre :
StandardOutput=etStandardError=dans l'unité — ils doivent valoirjournal(le défaut) oujournal+console. Quelqu'un les a peut-être mis surnull.- L'app écrit dans un fichier au lieu de stdout. Systemd ne capture que stdout/stderr ; qui écrit dans un fichier de log contourne entièrement le journal. Oriente l'app vers stdout, ou lis le fichier.
- Les limites de stockage ont évincé les vieilles lignes :
journalctl --disk-usage, etSystemMaxUse=dans/etc/systemd/journald.confsi le journal étouffe.
Pour débugger le démarrage lui-même, rien ne vaut de glisser un shell dans le contexte de boot :
[Service]
ExecStart=/bin/bash -c 'exec /opt/app/bin/server 2>&1'
Ou, pour les vraies énigmes, lance la commande ExecStart= exacte dans un shell avec l'utilisateur de l'unité — la plupart des différences d'environnement se révèlent dans les dix premières secondes.
Comment le faire redémarrer tout seul après un crash ?
La politique par défaut est Restart=no : un service planté reste mort, et tu l'apprends par un utilisateur. Corrige ça service par service :
[Service]
Restart=on-failure
RestartSec=5
| Réglage | Redémarre quand |
|---|---|
no (défaut) |
Jamais |
on-failure |
Sortie non nulle, signal, timeout |
always |
Toute sortie, même propre |
on-watchdog |
Timeout du watchdog uniquement |
Accompagne Restart=on-failure d'un garde-fou anti-boucle pour qu'un crash en boucle ne martèle pas la machine : StartLimitIntervalSec= et StartLimitBurst= dans la section [Unit]. Cinq échecs en soixante secondes devraient pager un humain, pas faire tourner un CPU.
Si tu câbles ça comme un job planifié plutôt qu'un daemon, pèse d'abord cron vs systemd timer — les timers t'offrent le logging dans le journal et l'ordonnancement des dépendances gratuitement, exactement ce que cet article n'arrête pas de réclamer.
La checklist de 60 secondes
systemctl status <unit>— lis l'état et les dernières lignes.journalctl -u <unit> -n 50 --no-pager— trouve la vraie erreur.203/EXEC? Chemin, shebang, permissions.Permission denied? Utilisateur et propriétaire des fichiers.- Échoue seulement au boot ? Ajoute
After=/Wants=network-online.targetet un gardeExecStartPre=. - Fichier d'unité modifié ?
systemctl daemon-reload && systemctl restart <unit>. - Ajoute
Restart=on-failurepour que le prochain crash s'annonce au lieu de se cacher.
La plupart des moments « systemd, c'est compliqué » se réduisent à l'erreur était dans le journal depuis le début. Lis-le avant de modifier le fichier d'unité, pas après.