Coldwast
Générateur de fichier service systemd
Indiquez la commande qui lance votre application et obtenez une unité .service à déposer dans /etc/systemd/system/, avec les commandes d'installation correspondantes. Le générateur relit aussi ce que vous avez saisi et signale les erreurs qui font échouer une unité dès le premier démarrage. Tout tourne dans votre navigateur.

Votre fichier d’unité
Ce que le générateur a relevé
L’installer
Les cinq lignes qui décident si ça marche
ExecStart n'est pas un shell. systemd découpe lui-même la ligne en arguments et lance le programme directement. Un pipe, une redirection > ou un && est transmis tel quel à votre programme comme argument. systemd développe aussi $VAR de lui-même : un dollar littéral s'écrit donc $$. Quand le générateur repère de la syntaxe shell, il enveloppe la commande dans /bin/sh -c et fait cet échappement à votre place.
Utilisez un chemin absolu. Un chemin comme ./server est refusé d'emblée. Un nom nu comme node est accepté, mais systemd ne cherche que dans ses propres répertoires fixes, pas dans le PATH de votre shell : un Node installé avec nvm ou un outil placé dans ~/.local/bin est introuvable.
Type=simple ou exec pour presque tout. Un serveur web, un worker ou un bot qui reste au premier plan relève de simple. exec diffère sur un point utile : le démarrage n'est déclaré réussi qu'une fois le binaire réellement exécuté, si bien qu'un mauvais chemin fait échouer systemctl start au lieu d'échouer en silence un instant plus tard. forking sert aux vieux démons qui passent d'eux-mêmes en arrière-plan, et notify ne marche que si le programme appelle sd_notify.
Restart=on-failure, avec une limite. on-failure relance après un plantage ou une sortie non nulle, pas après un arrêt propre ; always relance même dans ce cas. Par défaut systemd autorise 5 démarrages en 10 secondes, et avec un délai de 5 secondes une app qui plante n'atteint jamais cette limite : elle boucle sans fin. StartLimitIntervalSec=300 avec StartLimitBurst=5 la fait abandonner et laisse un état d'échec clair.
Wants= et After= ensemble. After=network-online.target seul ne fait que fixer un ordre ; il n'active pas la cible. Sans Wants=network-online.target, votre service peut encore démarrer avant que le réseau soit prêt : c'est le bug classique « ça marche après un redémarrage manuel, ça échoue au boot ».
L'isolation, et ce qui a été laissé de côté exprès
Avec ProtectSystem=strict, tout le système de fichiers est en lecture seule pour le service, sauf les chemins listés dans ReadWritePaths=. PrivateTmp lui donne son propre /tmp, NoNewPrivileges l'empêche de gagner des droits via des binaires setuid, et les réglages noyau et cgroup l'empêchent de modifier l'état du système. Lancez systemd-analyze security votreapp.service avant et après : la commande affiche un score d'exposition et liste chaque réglage encore ouvert.
Deux recommandations courantes sont volontairement absentes. MemoryDenyWriteExecute=true casse les compilateurs à la volée, c'est-à-dire Node.js, la JVM et PyPy. PrivateDevices=true ne pose pas de problème pour une app web mais casse tout ce qui parle à un GPU ou à un port série. Ajoutez-les vous-même si vous savez que votre programme n'en a pas besoin.

Quand il refuse de démarrer : les codes de sortie
systemctl status votreapp affiche un état du type status=203/EXEC. Le nombre indique quelle étape a échoué avant même que votre programme tourne :
| État | Signification | Correction habituelle |
|---|---|---|
203/EXEC | Le programme n'a pas pu être exécuté | Mauvais chemin, bit d'exécution absent, ou script sans ligne shebang |
200/CHDIR | WorkingDirectory absent ou inaccessible | Le créer, ou vérifier que ProtectHome ne le masque pas |
217/USER | Le compte User= n'existe pas | Lancer la ligne useradd ci-dessus |
226/NAMESPACE | L'isolation n'a pas pu être mise en place | Un chemin de ReadWritePaths n'existe pas : le créer ou le préfixer par - |
Tout autre échec vient de votre programme lui-même, et sa sortie se trouve dans journalctl -u votreapp -b.
Le modifier plus tard
Après avoir modifié le fichier dans /etc/systemd/system/, lancez sudo systemctl daemon-reload avant de redémarrer, sinon systemd garde l'ancienne version. Pour une unité installée par un paquet, ne la modifiez pas sur place : sudo systemctl edit votreapp crée un fichier de surcharge qui survit aux mises à jour et recharge systemd pour vous.
À lire aussi : ce qu'est un VPS, placer un reverse proxy devant l'app, les bases de SSH, et pour les tâches planifiées le décodeur d'expression cron.