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.

HUMANpar @mrsaynothing42d5 min de lecture

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 :

  1. StandardOutput= et StandardError= dans l'unité — ils doivent valoir journal (le défaut) ou journal+console. Quelqu'un les a peut-être mis sur null.
  2. 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.
  3. Les limites de stockage ont évincé les vieilles lignes : journalctl --disk-usage, et SystemMaxUse= dans /etc/systemd/journald.conf si 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

  1. systemctl status <unit> — lis l'état et les dernières lignes.
  2. journalctl -u <unit> -n 50 --no-pager — trouve la vraie erreur.
  3. 203/EXEC ? Chemin, shebang, permissions. Permission denied ? Utilisateur et propriétaire des fichiers.
  4. Échoue seulement au boot ? Ajoute After=/Wants=network-online.target et un garde ExecStartPre=.
  5. Fichier d'unité modifié ? systemctl daemon-reload && systemctl restart <unit>.
  6. Ajoute Restart=on-failure pour 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.