Créer son propre service systemd et le lancer automatiquement au démarrage

Publié le 17 min de lecture

Le fichier unit ligne par ligne : Type, User, WorkingDirectory, Restart et network-online.target. Avec les messages d'erreur au mot près et la preuve que le service survit vraiment au redémarrage.

Un programme qui ne revient pas tout seul après un redémarrage n'est pas un service sur un serveur, c'est un risque. nohup, screen et tmux maintiennent un processus en vie tant que rien ne se passe. Cet article écrit un fichier unit ligne par ligne, montre les messages d'erreur au mot près et explique comment s'en sortir quand le service reste coincé dans le carrousel de démarrage.

Toutes les commandes s'exécutent en tant que root. Testé sur Debian 13 (systemd 257), Debian 12 (252), Ubuntu 24.04 LTS (255) et Ubuntu 22.04 LTS (249). Là où ces quatre systèmes divergent, c'est signalé au fil du texte.

Ce qu'un service systemd apporte et que nohup et screen n'apportent pas

La différence n'est pas une question de confort, mais de responsabilité. systemd démarre le processus au boot dans un ordre défini, le place dans son propre cgroup, collecte stdout et stderr dans le journal, le relance après un crash et l'arrête proprement avec SIGTERM à l'extinction. C'est le cgroup qui manque le plus cruellement : un script lancé via nohup laisse derrière lui des processus enfants orphelins, alors qu'une unit nettoie son groupe entièrement.

Préparation : programme, utilisateur et répertoire

Avant de créer l'unit, il faut que ce qu'elle doit démarrer fonctionne déjà. L'exemple retenu est un script qui écrit sur stdout. C'est justement le point important : sous systemd, un service ne journalise pas lui-même dans un fichier, il écrit sur stdout et systemd se charge de déposer le tout dans le journal.

mkdir -p /opt/kh-demo
cat > /opt/kh-demo/run.sh <<'EOF'
#!/bin/bash
set -euo pipefail
while true; do
  echo "kh-demo tourne, $(date --iso-8601=seconds), PWD=$PWD, GREETING=${GREETING:-non défini}"
  sleep 10
done
EOF
chmod +x /opt/kh-demo/run.sh

Ensuite un utilisateur système dédié : --system attribue un UID inférieur à 1000, /usr/sbin/nologin empêche toute connexion interactive.

useradd --system --no-create-home --home-dir /opt/kh-demo --shell /usr/sbin/nologin khdemo
id khdemo
chown -R root:khdemo /opt/kh-demo
chmod 750 /opt/kh-demo

Le test décisif avant d'écrire le fichier unit : est-ce que le programme tourne sous cet utilisateur ? Sauter cette étape revient à déboguer systemd plus tard, alors que le problème se situe dans le programme.

timeout 3 runuser -u khdemo -- /opt/kh-demo/run.sh; echo "Exitcode $?"

Exitcode 124 est ici le résultat souhaité : timeout a interrompu un programme qui tournait. Toute autre valeur signifie que le script est mort de lui-même, et dans ce cas l'erreur ne vient pas de systemd.

Le fichier unit ligne par ligne

Les units maison vont dans /etc/systemd/system/, pas dans /lib/systemd/system/ ni dans /usr/lib/systemd/system/ : ces deux répertoires appartiennent au gestionnaire de paquets et seront écrasés au prochain apt upgrade.

cat > /etc/systemd/system/kh-demo.service <<'EOF'
[Unit]
Description=KernelHost Demo Worker
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=khdemo
Group=khdemo
WorkingDirectory=/opt/kh-demo
EnvironmentFile=-/etc/kh-demo.env
ExecStart=/opt/kh-demo/run.sh
Restart=on-failure
RestartSec=5s
TimeoutStopSec=20s
SyslogIdentifier=kh-demo
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full

[Install]
WantedBy=multi-user.target
EOF

Section [Unit]

Description= est le texte affiché par systemctl status. Aucun effet technique, mais c'est lui qui décide si vous comprenez à trois heures du matin ce qui vient d'échouer. Une phrase, pas un mot.

After= ne fixe que l'ordre, pas la dépendance. After=network-online.target signifie : si cette cible est de toute façon démarrée pendant ce boot, attends-la. Cela ne signifie pas qu'elle sera démarrée.

Wants= la demande réellement, et ce sous forme de dépendance faible. Si la cible échoue, le service démarre quand même. Son pendant Requires= entraînerait le service dans l'échec. Pour la plupart des applications, Wants= est le bon choix, car un service qui ne démarre même pas à cause d'un timeout réseau est pire qu'un service qui tourne brièvement dans le vide et revient grâce à Restart=.

Section [Service]

User= et Group= définissent l'identité du processus. Sans ces lignes, le service tourne en tant que root. Sur Debian et Ubuntu, useradd crée par défaut un groupe du même nom, d'où Group=khdemo.

WorkingDirectory= est le répertoire de travail du processus. Sans cette indication, le service démarre dans /. Tout programme qui cherche ses fichiers de configuration, ses templates ou ses plugins relativement au répertoire courant coule alors immédiatement. Si le répertoire indiqué n'existe pas, le démarrage se termine par 200/CHDIR.

EnvironmentFile= charge des variables depuis un fichier. Le signe moins devant le chemin rend ce fichier facultatif. Sans ce moins, le démarrage échoue si le fichier manque. Le contenu se limite à de simples lignes KEY=VALUE, ce n'est pas du shell. Un export devant est une erreur, et PORT=$BASE_PORT ne sera pas résolu.

ExecStart= exige un chemin absolu vers l'exécutable. C'est la règle sur laquelle la plupart butent, d'où une section entière plus bas.

TimeoutStopSec= limite le temps que systemd attend après le SIGTERM avant d'envoyer SIGKILL. La valeur par défaut est de 90 secondes.

SyslogIdentifier= définit le nom sous lequel les lignes apparaissent dans le journal. Sans cette ligne, l'émetteur s'appelle run.sh, ce qui rend tout filtrage inutilisable.

NoNewPrivileges=true interdit au processus et à tous ses enfants de gagner des droits via des binaires setuid. PrivateTmp=true donne au service son propre /tmp. ProtectSystem=full monte /usr, /boot et /etc en lecture seule. Le niveau strict va plus loin et exige alors StateDirectory= ou ReadWritePaths= pour tout ce qui doit rester accessible en écriture.

Section [Install]

WantedBy=multi-user.target répond à la question de savoir quand le service doit démarrer automatiquement. multi-user.target correspond au fonctionnement multi-utilisateur normal sans interface graphique, c'est-à-dire à ce qu'atteint un serveur. Seul systemctl enable évalue cette section et crée le lien symbolique. Si [Install] manque, enable s'interrompt avec The unit files have no installation config (WantedBy=, RequiredBy=, Also=, Alias= settings in the [Install] section, and DefaultInstance= for template units). Le service peut alors être démarré à la main, mais il ne revient jamais après un redémarrage.

Type=simple, Type=exec et Type=forking

Le Type= répond à exactement une question : à quoi systemd reconnaît-il que le service a démarré ?

Avec Type=simple, le service est considéré comme démarré dès que le processus a été créé, donc après le fork() et avant même que le programme ne soit exécuté. C'est la valeur par défaut, et le bon choix pour la quasi-totalité des programmes modernes qui restent au premier plan.

Avec Type=exec, systemd attend en plus que execve() ait réussi. La différence pratique est considérable : avec Type=simple, systemctl start annonce un succès même si le binaire n'existe pas du tout, et l'erreur n'apparaît qu'ensuite dans le journal. Avec Type=exec, le démarrage échoue immédiatement dans ce cas précis. Disponible depuis systemd 240, donc sur les quatre distributions.

Avec Type=forking, systemd part du principe que le programme passe lui-même en arrière-plan : le processus lancé se termine, un enfant continue de tourner, et c'est cette fin qui vaut signal de démarrage. C'est le comportement classique d'un démon Unix. Qui utilise ce type a en règle générale besoin en plus de PIDFile= avec un chemin absolu, sinon systemd devine lequel des processus restants est le processus principal.

La recommandation est sans ambiguïté : Type=forking uniquement si le programme refuse absolument d'y renoncer. Presque tous les logiciels disposent d'une option pour cela, souvent --foreground, -D FOREGROUND, --no-daemon ou daemon off; dans la configuration. Au premier plan avec Type=simple, l'unit est plus courte, les logs atterrissent dans le journal et Restart= fonctionne de façon fiable.

La mauvaise combinaison typique : un programme qui passe lui-même en arrière-plan, sous Type=simple. systemd voit le processus de démarrage se terminer, considère le service comme arrêté et tue les enfants au passage. Le symptôme est Active: inactive (dead) juste après un systemctl start qui n'a signalé aucune erreur. Le cas inverse, un programme de premier plan sous Type=forking, laisse systemd attendre une fin qui n'arrive jamais : Job for foo.service failed because a timeout was exceeded, au bout de 90 secondes.

Câbler correctement After=network-online.target

C'est ici que presque tous les tutoriels s'arrêtent trop tôt. network.target signifie uniquement que la gestion du réseau a démarré, pas qu'une adresse IP est configurée. Si vous avez besoin d'une adresse joignable, par exemple parce que le programme se lie à une IP fixe, c'est network-online.target qu'il vous faut.

Mais cette cible n'est pas atteinte toute seule. Elle suppose qu'un service d'attente adapté soit activé, et celui-ci diffère selon la gestion du réseau :

systemctl list-unit-files 'systemd-networkd-wait-online.service' 'NetworkManager-wait-online.service' 'ifupdown-wait-online.service' --no-pager

Sur Ubuntu Server 22.04 et 24.04, le réseau passe par netplan avec systemd-networkd : systemd-networkd-wait-online.service y est actif et network-online.target a une vraie signification. Sur une installation Debian classique avec ifupdown, ifupdown-wait-online.service existe bien, mais n'est pas activé. La cible est alors considérée comme atteinte immédiatement, et le délai d'attente sur lequel vous comptez n'a jamais lieu.

Sur Debian 12 et 13 avec ifupdown, le service d'attente s'active une fois pour toutes en cas de besoin :

systemctl enable ifupdown-wait-online.service

Activer deux services d'attente en même temps n'est pas une bonne idée, car chacun attend alors sa propre gestion du réseau et l'un des deux part forcément dans le timeout de 90 secondes. C'est la cause la plus fréquente d'un serveur qui met soudain une minute et demie de plus à démarrer. De toute façon, plus robuste que n'importe quel ordre de démarrage : un programme qui supporte une connexion absente au lancement, combiné à Restart=on-failure.

Restart, RestartSec et le frein au démarrage

Restart=on-failure relance en cas de code de sortie différent de zéro, en cas de signal comme SIGSEGV et en cas de timeout du watchdog, mais pas après un exit 0 ni après un systemctl stop manuel. Restart=always relance en plus après une fin propre, et c'est le moyen le plus rapide de se construire une boucle infinie.

Contre cela, systemd dispose d'un frein intégré : par défaut, cinq tentatives de démarrage sont autorisées en dix secondes (StartLimitBurst=5, StartLimitIntervalSec=10s). Passé ce seuil, systemd abandonne et signale :

kh-demo.service: Start request repeated too quickly.
kh-demo.service: Failed with result 'exit-code'.
Failed to start kh-demo.service - KernelHost Demo Worker.

Le service reste alors dans l'état failed et ne réagit plus non plus à systemctl start, tant que le compteur n'est pas remis à zéro :

systemctl reset-failed kh-demo.service

Deux détails coûtent régulièrement du temps ici. Premièrement, StartLimitBurst= et StartLimitIntervalSec= appartiennent à la section [Unit], pas à [Service]. Dans la mauvaise section, ils sont ignorés, avec un avertissement dans le journal mais sans erreur. Deuxièmement, les appels manuels à systemctl restart comptent aussi : qui relance cinq fois pendant le débogage déclenche le frein lui-même.

Règle empirique : StartLimitIntervalSec supérieur à RestartSec multiplié par StartLimitBurst.

Sur Debian 13 et Ubuntu 24.04, il existe en plus RestartSteps= et RestartMaxDelaySec= (à partir de systemd 254) pour des temps d'attente croissant de façon exponentielle. Sur Debian 12 et Ubuntu 22.04, ces directives n'existent pas et sont ignorées.

Activer, démarrer et prouver que ça tourne

Avant le premier démarrage, une vérification de syntaxe vaut le détour. Elle trouve les fautes de frappe dans les noms de directives, les sections mal orthographiées et les programmes manquants, sans rien démarrer :

systemd-analyze verify /etc/systemd/system/kh-demo.service

Aucune sortie signifie que tout est en ordre. Une faute de frappe comme WorkingDirectiry= produit, selon la version de systemd, Unknown key name 'WorkingDirectiry' in section 'Service', ignoring ou Unknown key 'WorkingDirectiry' in section [Service], ignoring. C'est là que naissent les erreurs silencieuses : systemd ignore les clés inconnues, le service démarre, mais il se comporte autrement que prévu.

systemctl daemon-reload

daemon-reload relit les fichiers unit, mais ne relance rien. Si vous oubliez le reload, le prochain systemctl status vous répond : Warning: The unit file, source configuration file or drop-ins of kh-demo.service changed on disk. Run 'systemctl daemon-reload' to reload units. À retenir dans l'ordre : d'abord daemon-reload, ensuite restart.

systemctl enable kh-demo.service
systemctl start kh-demo.service

Les deux à la fois avec systemctl enable --now kh-demo.service.

Vient maintenant la partie que la plupart des tutoriels laissent de côté : la preuve que cela a vraiment fonctionné, et pas seulement qu'aucune erreur n'est apparue. Quatre preuves indépendantes.

Premièrement, le lien symbolique existe. Il constitue à lui seul tout le mécanisme derrière enable. S'il manque, le service ne démarre pas après le redémarrage, quoi que dise status sur le moment.

ls -l /etc/systemd/system/multi-user.target.wants/kh-demo.service
systemctl is-enabled kh-demo.service

Deuxièmement, l'état. Ce qui compte, c'est la parenthèse dans la ligne Active:. active (running) veut dire qu'un processus tourne. active (exited) veut dire que le programme a terminé et que plus personne n'est là. Pour un service permanent, c'est une erreur, même si l'affichage est vert.

systemctl status kh-demo.service --no-pager
systemctl is-active kh-demo.service

Troisièmement, le journal. Pas seulement de savoir si des lignes arrivent, mais si ce sont les bonnes.

journalctl -u kh-demo.service -n 20 --no-pager

Suivre en temps réel : journalctl -u kh-demo.service -f. Uniquement le dernier démarrage : -b. Une période donnée : --since "-1h". Plus de détails dans l'article journalctl : analyser les logs sous systemd.

Quatrièmement, la configuration effective. Pas le fichier, mais ce que systemd en a fait. La différence compte dès que des drop-ins entrent en jeu.

systemctl show kh-demo.service -p ExecStart -p User -p WorkingDirectory -p Restart -p MainPID
systemctl cat kh-demo.service

La preuve finale reste un vrai redémarrage. Ensuite, cette commande montre si quelque chose est resté sur le carreau :

systemctl list-units --type=service --state=failed --no-pager

Les erreurs les plus fréquentes, au mot près

systemd signale les erreurs de démarrage via ses propres codes de sortie au-dessus de 200. Le nombre affiché dans status indique déjà où chercher.

CodeSignificationCause
200/CHDIREXIT_CHDIRWorkingDirectory= n'existe pas ou n'est pas accessible pour l'utilisateur
203/EXECEXIT_EXECExecStart= introuvable, non exécutable ou mauvais interpréteur
216/GROUPEXIT_GROUPGroup= n'existe pas
217/USEREXIT_USERUser= n'existe pas
219/CGROUPEXIT_CGROUPle cgroup n'a pas pu être créé
238/STATE_DIRECTORYEXIT_STATE_DIRECTORYStateDirectory= existe déjà avec un mauvais propriétaire

203/EXEC : le mauvais chemin dans ExecStart

Le journal contient à ce sujet une ligne de la forme kh-demo.service: Failed at step EXEC spawning /opt/kh-demo/run.sh: No such file or directory, suivie de Main process exited, code=exited, status=203/EXEC.

No such file or directory est trompeur, car ce message a trois causes. Premièrement : le fichier n'existe réellement pas, le plus souvent à cause d'une faute de frappe ou parce que le binaire se trouve dans /usr/local/bin et non dans /usr/bin. Deuxièmement : le bit exécutable manque, et le journal signale alors Permission denied. Troisièmement, le cas le plus vicieux : le fichier est exécutable, mais sa ligne shebang pointe dans le vide. Un script avec #!/usr/bin/python échoue exactement de la même manière sur Debian 12 et plus récent, car seul /usr/bin/python3 y existe. Le noyau signale l'absence de l'interpréteur, systemd la transmet comme une absence du script.

La vérification prend trois secondes :

ls -l /opt/kh-demo/run.sh
head -n 1 /opt/kh-demo/run.sh

Chemins relatifs et PATH

ExecStart=node server.js ne fonctionne pas, même avec un WorkingDirectory= défini. L'erreur au chargement est Neither a valid executable name nor an absolute path. Seule la première entrée doit être absolue, les arguments qui suivent peuvent rester relatifs. La forme correcte est ExecStart=/usr/bin/node server.js, où server.js est résolu relativement au WorkingDirectory. Le bon chemin est fourni par command -v :

command -v bash

Attention avec les gestionnaires de versions : sous nvm, cela renvoie un chemin comme /root/.nvm/versions/node/v22.14.0/bin/node, qui n'existe pas pour l'utilisateur du service. Les environnements d'exécution destinés à des services s'installent à l'échelle du système, par exemple via NodeSource ou Adoptium Temurin.

Variables d'environnement manquantes

Le grand classique : dans une session interactive le programme tourne, en tant que service non. La raison est que systemd ne démarre aucun shell de connexion. Ni /etc/profile, ni ~/.bashrc, ni ~/.profile ne sont lus, et tout ce qui y est défini par export manque dans le service. Le PATH d'un service système est un chemin minimal figé, généralement /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin.

Les variables se déclarent donc dans l'unit ou dans un fichier dédié :

cat > /etc/kh-demo.env <<'EOF'
GREETING=bonjour
LANG=fr_FR.UTF-8
EOF
chmod 640 /etc/kh-demo.env
chown root:khdemo /etc/kh-demo.env
systemctl restart kh-demo.service
journalctl -u kh-demo.service -n 5 --no-pager

Si la sortie affiche maintenant GREETING=bonjour au lieu de GREETING=non défini, le fichier est bien arrivé. Cela se contrôle aussi directement :

systemctl show kh-demo.service -p Environment -p EnvironmentFiles

Trois pièges dans ce genre de fichiers : la substitution de commande avec $(...) n'a pas lieu, un $ voulu au sens littéral doit s'écrire $$, et les guillemets se retrouvent dans la valeur, là où ils n'ont rien à faire.

Unit not found

Failed to start kh-demo.service: Unit kh-demo.service not found. signifie presque toujours l'une de ces trois choses : le fichier se trouve dans le mauvais répertoire, il porte la mauvaise extension (.services au lieu de .service) ou le daemon-reload manque. ls -l /etc/systemd/system/ règle les deux premiers cas.

Différences entre Debian 13, Debian 12, Ubuntu 24.04 et 22.04

Les bases, à savoir [Unit], [Service], [Install], Type=simple, Restart=, User= et WorkingDirectory=, sont identiques sur les quatre systèmes. Les différences se situent dans les marges.

Directives disponibles. Ubuntu 22.04 embarque systemd 249, Debian 12 systemd 252, Ubuntu 24.04 systemd 255 et Debian 13 systemd 257. Tout ce qui arrive à partir de 253 manque sur les deux systèmes les plus anciens : Type=notify-reload (253) ainsi que RestartSteps=, RestartMaxDelaySec= et RestartMode=direct (254). Ces directives ne sont pas signalées comme des erreurs, elles sont simplement ignorées. C'est ainsi que naissent des units qui font ce qu'on attend d'elles sur un système et tout autre chose, sans raison apparente, sur l'autre.

systemctl --version

Réseau. Ubuntu Server utilise netplan avec systemd-networkd, Debian utilise ifupdown dans son installation par défaut. network-online.target est donc parlant sur Ubuntu sans rien faire, et sur Debian seulement après l'activation de ifupdown-wait-online.service.

Chemins des interpréteurs et des environnements d'exécution. C'est là que naissent la plupart des erreurs 203/EXEC lorsqu'on transpose une unit d'un système à l'autre. Node.js est fourni en 20.19 sur Debian 13, en 18.20 sur Debian 12, en 18.19 sur Ubuntu 24.04 et en 12.22 sur Ubuntu 22.04. PHP est en 8.4 sur Debian 13, 8.2 sur Debian 12, 8.3 sur Ubuntu 24.04 et 8.1 sur Ubuntu 22.04. Pour Java, l'écart est le plus grand : Debian 13 ne livre que openjdk-21-jre-headless, Debian 12 uniquement openjdk-17-jre-headless, tandis qu'Ubuntu 24.04 et 22.04 proposent 8, 11, 17 et 21. Copier ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java de Debian 12 vers Debian 13 donne à coup sûr un 203/EXEC.

Bases de données. Debian ne livre pas de mysql-server, c'est toujours MariaDB qui y tourne. Une unit avec After=mysql.service attend donc sur Debian un service qui n'existe pas, et démarre sans le moindre délai, en silence et sans avertissement. La forme correcte y est After=mariadb.service.

Modifier, revenir en arrière, nettoyer

Les units maison se modifient directement dans le fichier, suivi d'un daemon-reload. Pour les units issues d'un paquet, on utilise en revanche des drop-ins, afin que la prochaine mise à jour n'efface pas la personnalisation :

systemctl edit kh-demo.service

Cela crée /etc/systemd/system/kh-demo.service.d/override.conf, qui ne contient que les directives modifiées. Cas particulier : les directives de type liste comme ExecStart= ne sont pas remplacées, elles sont ajoutées. Pour les écraser, il faut d'abord vider la liste :

ExecStart=
ExecStart=/opt/kh-demo/run.sh --verbose

systemctl revert kh-demo.service supprime à nouveau tous les drop-ins. Démontage complet de l'exemple, exactement dans cet ordre :

systemctl disable --now kh-demo.service
rm -f /etc/systemd/system/kh-demo.service
rm -rf /etc/systemd/system/kh-demo.service.d
systemctl daemon-reload
systemctl reset-failed

Attention à la dernière étape : systemctl reset-failed sans argument réinitialise l'état d'erreur de toutes les unités, pas seulement celui du service d'exemple. Sur un système où d'autres services sont encore à l'état failed, leurs messages disparaissent eux aussi de systemctl list-units --state=failed. Pour ne nettoyer qu'ici, prenez la forme ciblée systemctl reset-failed kh-demo.service vue dans la section plus haut.

Supprimer le fichier sans avoir appelé disable au préalable laisse un lien symbolique mort dans /etc/systemd/system/multi-user.target.wants/, qui apparaît comme avertissement à chaque daemon-reload. Il se nettoie avec find /etc/systemd/system -xtype l -delete. Cet appel supprime toutefois chaque lien symbolique mort sous /etc/systemd/system, donc aussi les restes de services tiers dont vous avez peut-être encore besoin. Plus sûr : commencer par un coup d'œil sans -delete, ou passer directement par la variante ciblée find /etc/systemd/system -xtype l -name 'kh-demo*' -delete. En option, l'utilisateur et les fichiers :

userdel khdemo
rm -rf /opt/kh-demo /etc/kh-demo.env

Chemin absolu dans ExecStart, Type adapté, utilisateur dédié, WorkingDirectory défini, variables via EnvironmentFile= : avec cet ensemble en place, vous avez un service qui survit à un redémarrage et qui, en cas de problème, vous dit ce qui s'est passé.

Questions fréquentes

Dois-je exécuter systemctl daemon-reload après chaque modification du fichier unit ?
Oui. systemd garde les fichiers unit en mémoire, une modification sur le disque ne prend effet qu'après le reload. Si vous l'oubliez, le prochain systemctl status affiche l'avertissement « The unit file, source configuration file or drop-ins of ... changed on disk ». L'ordre est toujours d'abord systemctl daemon-reload, puis systemctl restart. Le reload seul ne relance rien et ne change rien aux processus en cours.
Que signifie status=203/EXEC ?
systemd n'a pas pu exécuter le programme indiqué dans ExecStart. Trois causes sont possibles : le fichier n'existe pas (faute de frappe ou mauvais répertoire), le bit exécutable manque, ou la ligne shebang d'un script pointe vers un interpréteur qui n'existe pas, par exemple #!/usr/bin/python sur Debian 12 ou plus récent, où seul /usr/bin/python3 existe. Vérifiez avec ls -l sur le fichier et head -n 1 sur la première ligne.
Pourquoi mon service ne démarre-t-il pas après le redémarrage alors que systemctl start fonctionne ?
Il manque presque toujours la section [Install] avec WantedBy=multi-user.target, ou bien systemctl enable n'a jamais été exécuté. La preuve est le lien symbolique : ls -l /etc/systemd/system/multi-user.target.wants/nom.service doit afficher le fichier, et systemctl is-enabled doit renvoyer « enabled ». Si la section manque, enable s'interrompt avec « The unit files have no installation config ».
Type=simple ou Type=forking ?
Type=simple quand le programme reste au premier plan, ce qui vaut pour pratiquement toutes les applications modernes et correspond à la valeur par défaut. Type=forking uniquement si le programme passe impérativement lui-même en arrière-plan, avec PIDFile= en complément. La plupart des démons ont une option comme --foreground ou --no-daemon, et avec elle Type=simple est le meilleur choix. Mal combiné, cela se traduit par « inactive (dead) » juste après le démarrage, ou par un timeout au bout de 90 secondes.
Pourquoi mon service ne trouve-t-il pas ses variables d'environnement ?
systemd ne démarre aucun shell de connexion. Ni /etc/profile, ni ~/.bashrc, ni ~/.profile ne sont lus, et tout ce qui y est défini par export manque. Le PATH est un chemin minimal figé. Les variables se déclarent donc en Environment= dans l'unit, ou dans un fichier chargé via EnvironmentFile=. Ce fichier ne contient que des lignes KEY=VALUE sans export, et la substitution de commande n'y a pas lieu. Contrôle avec systemctl show nom.service -p Environment.
Le service ne réagit plus à systemctl start, que faire ?
Le frein au démarrage s'est probablement déclenché : par défaut, cinq tentatives de démarrage sont autorisées en dix secondes, ensuite le journal signale « Start request repeated too quickly » et le service reste en failed. systemctl reset-failed nom.service remet le compteur à zéro. Attention : les appels manuels à systemctl restart comptent eux aussi, qui relance plusieurs fois coup sur coup pendant le débogage déclenche le frein lui-même.

systemd Linux Debian Ubuntu Administration serveur Démarrage automatique journalctl Fichier unit Serveur root