Hier le tunnel fonctionnait, et ce matin l’unit est mort avec Failed at step EXEC spawning /usr/local/bin/vpnclient dans le journal. Les identifiants, les routes et le compte n’entrent jamais dans cette phrase. systemd a résolu ExecStart en un chemin, a demandé au noyau de l’exécuter, et le noyau a refusé, le plus souvent parce que rien n’existe à ce chemin ou parce que le fichier n’est pas exécutable. Traitez la panne comme un problème de fichier et d’argv jusqu’à ce que la commande analysée prouve le contraire.

Pourquoi EXEC échoue avant que l’authentification commence

systemd n’exécute pas ExecStart via un shell. Il fait un fork, construit un vecteur d’arguments à partir de la ligne et appelle execve sur le premier élément. Si cet appel échoue, le service n’atteint jamais le code propre au client VPN, donc aucune édition de profil ni vérification des identifiants ne peut aider.

Le code de statut sépare cette famille des plantages ordinaires. Une unit qui sort avec status=203/EXEC n’a jamais démarré de processus ; une unit qui démarre puis sort avec status=1 a peut-être échoué à l’authentification, au DNS ou au routage. Lisez le code avant le journal du client, car dans le premier cas le journal du client sera vide.

Le chemin du journal est celui que systemd a utilisé, pas nécessairement celui que vous avez édité. Les fichiers drop-in et la sortie du générateur peuvent remplacer le fichier du fournisseur, et un daemon-reload périmé laisse l’ancienne ligne en mémoire. C’est pourquoi le premier diagnostic consiste à demander à systemd ce qu’il considère comme la commande, plutôt qu’à ouvrir le fichier dont vous vous souvenez l’avoir modifié.

Trois endroits où le chemin du binaire est écrit

Un client VPN installé depuis un paquet de distribution, un script fournisseur et une archive de version peut atterrir dans trois préfixes différents. L’unit, la règle de privilège et l’installateur ont chacun enregistré l’un de ces préfixes, et la panne apparaît quand deux des trois ne concordent pas.

RecordCommon valueHow to read it
Unit or drop-in/usr/local/bin/vpnclientsystemctl cat vpnclient.service
Installed binary/usr/bin/vpnclientcommand -v vpnclient, then ls -l the result
Sudoers grant/opt/vpnclient/bin/vpnclientsudo grep -r vpnclient /etc/sudoers.d/

Une divergence concrète ressemble à ceci : le script fournisseur installe dans /usr/local/bin, l’archive a été décompressée sous /opt, et l’unit nomme encore l’ancien chemin /usr/bin d’un paquet précédent. Un seul de ces fichiers existe, et c’est rarement celui présent dans ExecStart.

Décidez quel chemin fait autorité avant de modifier l’un des deux fichiers. Si le paquet de distribution possède le binaire, le chemin du paquet fait autorité et l’unit doit le suivre. Si c’est le script fournisseur qui le possède, la même règle s’applique au préfixe du fournisseur. Copier un binaire d’un préfixe à l’autre crée deux copies qui divergent à la prochaine mise à jour.

Quand une commande entre guillemets devient un nom de fichier

Un second mode de panne se cache derrière la même ligne du journal. Quelqu’un écrit ceci dans l’unit et s’attend à ce que le client se connecte au démarrage :

ExecStart=/usr/bin/"vpnclient connect office"

systemd découpe la valeur en mots, si bien que le premier mot devient /usr/bin/vpnclient connect office, les guillemets étant retirés comme syntaxe de groupement. Le noyau cherche ensuite un fichier dont le nom contient des espaces, et il n’en existe pas. L’erreur est identique à celle d’un binaire manquant, alors même que le binaire est installé.

La forme prise en charge est un véritable exécutable suivi d’arguments distincts, par exemple ExecStart=/usr/bin/vpnclient connect office. Si le flux de travail a réellement besoin d’un tube ou d’une chaîne, placez-le dans un script et appelez le script, car ExecStart n’a pas &&, ni tubes, ni développement de glob.

Réparez l’unit, pas le système de fichiers

La réparation tentante est un lien symbolique du chemin attendu vers le vrai binaire. Il fonctionne jusqu’à ce que le paquet soit mis à jour et que le lien pointe vers un fichier supprimé, ou jusqu’à ce qu’une étiquette SELinux ne corresponde plus à la cible. Préférez éditer l’unit via un drop-in, afin que le fichier du fournisseur reste intact et que les mises à jour restent prévisibles.

La règle de privilège doit correspondre au même chemin et aux mêmes sous-commandes. Une entrée sudoers pour /usr/local/bin/vpnclient n’autorise pas /usr/bin/vpnclient, et une entrée qui autorise n’importe quel argument laisse tout processus local reconfigurer ou arrêter le tunnel. Accordez les deux verbes dont l’unit a besoin et gardez le démontage hors de la règle si l’arrêt du service ferme déjà l’interface.

C’est ici que la comparaison paie : l’unit, la base de données des paquets et sudoers devraient tous nommer un seul chemin. Quand c’est le cas, l’erreur EXEC disparaît sans redémarrage et sans copie du binaire à un troisième endroit.

Prouvez la commande analysée avant de redémarrer

sudo systemctl daemon-reload
sudo systemctl cat vpnclient.service
systemctl show -p ExecStart vpnclient.service
systemd-analyze verify /etc/systemd/system/vpnclient.service
sudo systemctl restart vpnclient.service
systemctl status vpnclient.service --no-pager

systemctl show est la sortie décisive, car elle imprime l’argv que systemd a réellement analysé, y compris tout drop-in. systemd-analyze verify détecte un chemin absent ou non exécutable avant que vous redémarriez, et la commande status après le redémarrage vous dit si le processus existe désormais.

Si l’unit démarre puis échoue, vous avez quitté la famille EXEC et êtes entré dans la gestion d’erreurs propre au client. Cherchez dans le journal le message du client, pas un nouvel échec d’execve. Conservez le diff de l’unit et le diff de sudoers ensemble dans le journal des changements, pour que la prochaine mise à jour ne réintroduise pas silencieusement le chemin divisé.

Articles similaires