Partager :
systemd · Raspberry Pi
« Failed to enable unit: Cannot alias » : systemctl échoue, et le service est activé quand même
Publié le · HardStacked · 8 min
Tu as tapé systemctl enable monservice.service. Réponse : Failed to enable unit: Cannot alias monservice.service as monservice, code de sortie 1. Tu vérifies. systemctl is-enabled monservice.service répond enabled. Tu redémarres, et le service tourne. Rien dans cette séquence ne s'accorde avec le reste.
Une question posée sur Unix & Linux Stack Exchange, toujours sans réponse acceptée, le dit mieux qu'aucun rapport de bug :
« Oddly enough, when I check the status, it says it is enabled, and when I reboot, the service is live. I don't understand what is going on. »
Traduction : « Curieusement, quand je vérifie le statut, ça dit que c'est activé, et quand je redémarre, le service tourne. Je ne comprends plus ce qui se passe. »
(Unix & Linux Stack Exchange, question 788862, Raspberry Pi Zero W, 2 réponses, aucune acceptée.) L'une des deux réponses dit à la personne qui a posé la question de retirer la ligne Alias=, et c'est le bon correctif ; l'autre évoque un fichier .service en double, une autre cause. Aucune des deux ne lui explique pourquoi enable a dit avoir échoué en faisant exactement ce qui était demandé.
01. Le verdict en deux lignes
systemctl enable a fait le travail, puis t'a dit qu'il avait échoué. En sortant, il a sauté l'affichage de la liste de ce qu'il venait de faire, et il a sauté le rechargement qui aurait permis au systemd en marche de s'en apercevoir. Trois signaux distincts, trois mensonges distincts, et le vrai changement n'apparaît qu'au prochain redémarrage ou au prochain daemon-reload.
Tout ce qui suit a été mesuré le 2026-09-24, sur Debian 13 (trixie), systemd 257 (257.13-1~deb13u1), Raspberry Pi 5. Les commandes ont tourné dans l'instance utilisateur (systemctl --user), donc n'importe qui peut les reproduire sans root. L'instance système passe par exactement le même code (src/shared/install.c), mais on n'a pas exécuté ce banc précis en root : prends le mécanisme comme confirmé, les chiffres pour l'instance système comme non mesurés directement par nous.
02. Trois mensonges, une seule unité
L'unité, une demo.service jetable, Type=oneshot, ExecStart=/bin/true, avec un [Install] réglé à Alias=demo et WantedBy=default.target. Alias=demo n'a pas de suffixe, ce qui est invalide : man systemd.unit exige qu'un alias porte le même suffixe de type que l'unité qu'il désigne, ici .service.
Ces commandes sont fournies telles quelles, sans garantie. Teste d'abord sur une machine non critique ou sur un instantané.
Alias=demo, instance utilisateur
$ systemctl --user enable demo.service; echo "exit=$?"
Failed to enable unit: Cannot alias demo.service as demo
exit=1
$ systemctl --user is-enabled demo.service
enabled
$ systemctl --user list-dependencies default.target | grep demo
$ ls -l ~/.config/systemd/user/default.target.wants/ | grep demo
lrwxrwxrwx 1 user user 46 24 sep 15:55 demo.service -> ~/.config/systemd/user/demo.service
Lis dans l'ordre. La commande annonce un échec. is-enabled annonce un succès. list-dependencies (la vue du systemd en marche lui-même) ne montre rien. ls -l sur le dossier .wants/ montre le lien symbolique bien présent sur le disque. Quatre commandes, trois réponses différentes à la même question.
Un témoin sans Alias= affiche Created symlink '~/.config/systemd/user/default.target.wants/demo.service' → '~/.config/systemd/user/demo.service'. et sort avec 0. Notre unité n'affiche jamais cette ligne. Rien sur le lien n'apparaît nulle part dans la sortie, même s'il est bel et bien sur le disque.
La conséquence n'a rien de cosmétique. Un script tournant sous set -e qui appelle systemctl enable demo.service s'arrête net, exit 1, sur une étape qui en fait a réussi :
script sous set -e
Failed to enable unit: Cannot alias demo.service as demo
script exit=1
Ce que le script devait faire ensuite (démarrer l'unité, en activer une seconde, journaliser un succès) ne tourne jamais, parce que le shell croit déjà que la première étape a échoué.
03. Pourquoi, vérifié dans le code source de systemd (v257)
Trois choses se produisent dans un ordre qui produit exactement ce comportement, et aucune n'est un plantage : c'est la structure même du code.
install_info_apply(), dans src/shared/install.c, crée d'abord le lien de l'alias. Si ça échoue, la fonction ne s'arrête pas : elle continue à travers WantedBy=, RequiredBy=, UpheldBy=, créant chacun de ces liens quoi qu'il arrive. Elle garde la première erreur rencontrée et la retourne à la fin. L'alias échoue, les liens d'activation sont créés quand même, et l'appelant apprend « échec » en dernier.
systemctl, dans le cas normal où systemd tourne, demande ce travail par D-Bus. Quand la réponse revient comme une erreur, systemctl affiche l'erreur et repart immédiatement, il n'ouvre jamais la partie de la réponse qui liste ce qui a réellement été créé. C'est toute la raison pour laquelle Created symlink n'apparaît jamais sur un enable en échec : le message existait, systemctl ne l'a simplement pas lu.
Le daemon-reload automatique qui suit normalement un enable réussi ne tourne qu'après un succès. Sur une erreur, systemctl repart avant de l'atteindre. L'instance systemd en marche n'apprend jamais qu'une nouvelle entrée .wants/ existe, donc list-dependencies continue de ne rien montrer, jusqu'à ce que quelque chose d'autre déclenche un rechargement, un daemon-reload manuel ou un redémarrage. C'est exactement l'histoire du demandeur : is-enabled, qui lit le disque, dit activé ; list-dependencies, qui lit le systemd en marche, ne montre rien ; et le redémarrage effectue le rechargement que enable a sauté.
Une unité témoin sans Alias= est prise en compte par le systemd en marche tout de suite, sans écart. L'unité qui déclenche le bug de l'alias, non :
avant et après daemon-reload
$ systemctl --user enable demo.service; echo "exit=$?"
Failed to enable unit: Cannot alias demo.service as demo
exit=1
$ ls ~/.config/systemd/user/default.target.wants/ | grep demo
demo.service
$ systemctl --user list-dependencies default.target | grep -c demo
0
$ systemctl --user daemon-reload
$ systemctl --user list-dependencies default.target | grep -c demo
1
enable annonce un échec. Le lien symbolique est déjà présent dans .wants/ (la ligne ls). Avant tout rechargement, list-dependencies le compte 0 fois, le systemd en marche ne sait pas qu'il existe. Un seul systemctl --user daemon-reload, aucun autre changement, et le même comptage list-dependencies passe à 1. Rien n'a changé dans l'unité entre ces deux comptages, seulement le fait que le systemd en marche ait été prévenu de regarder à nouveau.
Une autre mesure appuie le même mécanisme sous un autre angle. En passant enable par --root=, un chemin qui ne parle jamais à un systemd en marche (utilisé pour construire des images ou appliquer une unité à un système hors ligne), l'erreur et la ligne de création du lien apparaissent dans la même sortie :
--root=, sans systemd en marche
$ systemctl --root=/rootfs enable demo.service; echo "exit=$?"
Failed to enable unit: Cannot alias demo.service as demo
Created symlink '/rootfs/etc/systemd/system/multi-user.target.wants/demo.service' → '/etc/systemd/system/demo.service'.
Unit /rootfs/etc/systemd/system/demo.service is added as a dependency to a non-existent unit multi-user.target.
exit=1
$ find /rootfs -type l
/rootfs/etc/systemd/system/multi-user.target.wants/demo.service
Même échec, même lien symbolique, mais cette fois les deux sont visibles. La ligne absente sur le chemin normal n'est pas due à une absence de création du lien, c'est systemctl qui ne lit pas la réponse qui t'en aurait informé. Ça confirme le mécanisme spécifiquement pour --root= ; on n'a pas mesuré un chroot ordinaire sans ce paramètre, et on n'affirmera pas la même chose pour lui sans banc.
04. Deux pièges voisins
Activer par le nom de l'alias plutôt que par le vrai nom de l'unité échoue pour sa propre raison :
activer par le nom de l'alias
$ systemctl --user enable demo-alias.service; echo "exit=$?"
Failed to enable unit: Refusing to operate on linked unit file demo-alias.service
exit=1
demo-alias.service est lui-même un lien symbolique, pas un vrai fichier d'unité. systemctl refuse d'activer directement un fichier d'unité qui est un lien ; on active l'unité vers laquelle il pointe. Un alias valide (avec un suffixe qui correspond, Alias=demo-alias.service) fonctionne proprement : deux lignes Created symlink, exit 0, aucune ambiguïté.
L'autre voisin, ce sont deux unités qui se disputent le même alias, la forme que prennent deux gestionnaires de connexion graphique qui réclament tous les deux display-manager.service. Reproduit avec deux unités utilisateur jetables partageant un Alias=, pas avec un vrai gestionnaire de connexion : la deuxième à s'activer reçoit Failed to enable unit: File '.../skyshared.service' already exists and is a symlink to .../skyA.service, exit 1, et is-enabled sur cette deuxième unité répond quand même enabled. L'alias lui-même ne bouge jamais, il continue de pointer vers la première unité. is-enabled qui répond enabled ici ne veut pas dire que c'est la deuxième unité qui démarrera réellement via cet alias. Prends ça comme le même schéma de compte-rendu défaillant sous une autre forme, pas comme une affirmation sur ce que fait un gestionnaire de connexion précis ; ça demanderait de tester les vrais paquets, ce qu'on n'a pas fait.
Une autre cause est signalée pour le même message d'erreur mais n'a pas été reproduite ici : deux fichiers .service du même nom, un sous /etc, un sous /usr/lib. Ça vaut la peine de vérifier avec find si le mécanisme ci-dessus ne colle pas à ta situation, mais ce n'est pas quelque chose qu'on a mesuré.
05. Le correctif, et l'habitude qui se généralise
Retire Alias=demo, ou donne-lui un vrai nom avec le bon suffixe. Alias=demo-alias.service, mesuré, fonctionne proprement : deux lignes Created symlink, exit 0. Lance ensuite systemctl daemon-reload suivi de systemctl reenable demo.service pour remettre l'unité dans un état propre et connu. Pour reenable, on n'a pas capturé de sortie au banc ; considère-le comme la commande documentée pour repartir à zéro, pas comme une affirmation appuyée par notre propre sortie.
La leçon réutilisable est là, en dessous de tout ça : is-enabled répond en lisant les fichiers d'unité et les liens symboliques sur le disque. list-dependencies répond en lisant ce que l'instance systemd en marche sait réellement. Ce sont deux questions différentes, et un enable en échec est exactement le cas où elles peuvent se contredire. Quand enable signale une erreur, vérifie les deux avant de croire l'une ou l'autre, et lance ls -l sur le dossier .wants/ concerné pour voir ce qui a vraiment atterri sur le disque. Si tu es sur un systemd plus ancien (ceci a été mesuré sur la 257 ; la personne qui a posé la question était sur Raspbian, sans préciser sa version de systemd) et que tu ne vois pas de lien symbolique là où cet article dit qu'il devrait y en avoir un, c'est une vraie différence à vérifier avec ls -l, pas une raison de supposer que le mécanisme ne s'applique pas à ta version.
La même forme traverse le reste de cette série, où le système déclare une chose et en fait une autre : un service systemd qui tourne en root et reçoit quand même Permission denied, un pare-feu que l'unité systemd déclare activé alors qu'il est éteint, et un service WireGuard qui se déclare actif alors qu'aucune interface n'existe.
Partager :