Module 6 · 6.3
Français
Services personnalisés et timers
Module 6: Services et journaux · Temps de lecture : 11 minutes
Objectif pédagogique : À l'issue de cette leçon, vous saurez faire tourner votre propre programme comme service systemd sous un utilisateur dédié, avec quelques options de sécurité, et planifier des tâches avec un timer systemd ou cron.
Notions clés : fichier unit, [Unit], [Service], [Install], ExecStart, User=, WorkingDirectory, EnvironmentFile, Restart=, WantedBy, NoNewPrivileges, ProtectSystem, ProtectHome, PrivateTmp, systemd-analyze security, timer, OnCalendar, Persistent, crontab
Pourquoi ne pas simplement utiliser "nohup" ou "screen" ?
Lorsque l'on installe son propre programme sur un serveur (une petite application web, un bot, un script qui effectue une tâche chaque nuit), on le lance souvent à la main la première fois : node server.js &, ou dans une session screen. Cela fonctionne jusqu'à ce que vous vous déconnectiez, que le serveur redémarre ou que le programme plante. Il disparaît alors, et personne ne s'en aperçoit.
Un programme qui doit tourner en permanence doit être un service systemd. Ainsi :
- il démarre automatiquement au démarrage du serveur ;
- systemd le relance s'il plante ;
- sa sortie est enregistrée dans le journal (leçon 6.2) ;
- il tourne sous l'utilisateur de votre choix, avec les droits que vous lui accordez.
Votre propre fichier unit
Supposons que vous ayez une petite application Node.js dans /opt/factuurapp, qui écoute sur 127.0.0.1:3000. nginx ou Caddy l'exposera ensuite vers l'extérieur (module 7).
Étape 1 : un utilisateur dédié sans shell (leçon 3.2). L'application n'a pas besoin d'être root et n'a pas besoin de se connecter :
anna@web1:~$ sudo adduser --system --group --home /opt/factuurapp --no-create-home factuurapp
anna@web1:~$ sudo chown -R factuurapp:factuurapp /opt/factuurapp/dataLe code lui-même peut rester la propriété de root (l'application n'a pas à se modifier elle-même) ; seul le dossier dans lequel elle écrit appartient à l'utilisateur du service.
Étape 2 : le fichier unit. Vos propres units se placent dans /etc/systemd/system/ :
# /etc/systemd/system/factuurapp.service
[Unit]
Description=Factuurapp (Node.js)
After=network-online.target
Wants=network-online.target
[Service]
User=factuurapp
Group=factuurapp
WorkingDirectory=/opt/factuurapp
EnvironmentFile=/etc/factuurapp/env
ExecStart=/usr/bin/node /opt/factuurapp/server.js
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/opt/factuurapp/data
[Install]
WantedBy=multi-user.targetLes trois blocs :
- [Unit] : description et ordre de démarrage.
After=network-online.targetavecWants=garantit que le service ne démarre qu'une fois le réseau disponible. - [Service] : la façon dont le programme tourne.
ExecStartavec le chemin complet (systemd n'utilise pas de chemin de recherche comme votre shell).User=etGroup=déterminent sous quelle identité.EnvironmentFilelit des paramètres, comme un mot de passe de base de données, depuis un fichier séparé, afin qu'ils ne figurent pas dans l'unit. - [Install] : ce que fait
enable.WantedBy=multi-user.targetsignifie : démarrer avec un démarrage normal du système.
Étape 3 : les secrets à part, avec des droits stricts.
anna@web1:~$ sudo mkdir /etc/factuurapp
anna@web1:~$ sudo install -m 600 -o root -g root /dev/null /etc/factuurapp/env
anna@web1:~$ sudoedit /etc/factuurapp/envContenu par exemple DATABASE_URL=postgres://factuur:...@127.0.0.1/factuur. systemd lit le fichier en tant que root avant de basculer vers l'utilisateur du service, donc 600 appartenant à root suffit.
Étape 4 : charger, démarrer, vérifier.
anna@web1:~$ sudo systemctl daemon-reload
anna@web1:~$ sudo systemctl enable --now factuurapp
anna@web1:~$ systemctl status factuurapp
anna@web1:~$ journalctl -u factuurapp -fOptions de sécurité : peu d'effort, une grande différence
Les quatre lignes sous RestartSec dans l'exemple limitent ce que le service peut faire, même si quelqu'un trouve une faille dans votre application :
| Option | Effet |
|---|---|
NoNewPrivileges=true | le processus et ses enfants ne peuvent jamais obtenir davantage de droits (pas non plus via sudo ou des programmes setuid) |
ProtectSystem=strict | l'ensemble du système de fichiers est en lecture seule pour le service, sauf ce que vous autorisez avec ReadWritePaths= |
ProtectHome=true | /home, /root et /run/user sont invisibles |
PrivateTmp=true | un /tmp propre et vide, que les autres programmes ne voient pas |
systemd peut évaluer votre unit :
anna@web1:~$ systemd-analyze security factuurapp
...
→ Overall exposure level for factuurapp.service: 4.9 OK 🙂Plus le chiffre est bas, mieux c'est. Un service sans options obtient environ 9 ("UNSAFE"). Inutile de viser zéro : chaque option ajoutée peut casser quelque chose dont l'application a besoin. Ajoutez-les une par une et testez après chaque étape. Si quelque chose ne fonctionne plus, la raison figure généralement dans le journal ("Read-only file system", "Permission denied").
Planifier des tâches : les timers
Pour une tâche qui doit s'exécuter à heures fixes (une sauvegarde, un script de nettoyage, un rapport), il vous faut un timer. Un timer systemd se compose de deux units portant le même nom : un .service qui indique ce qui doit se passer, et un .timer qui indique quand.
# /etc/systemd/system/opruimen.service
[Unit]
Description=Oude exportbestanden opruimen
[Service]
Type=oneshot
User=factuurapp
ExecStart=/usr/bin/find /opt/factuurapp/data/export -type f -mtime +30 -delete# /etc/systemd/system/opruimen.timer
[Unit]
Description=Dagelijks oude exportbestanden opruimen
[Timer]
OnCalendar=*-*-* 03:15:00
RandomizedDelaySec=10m
Persistent=true
[Install]
WantedBy=timers.targetType=oneshot signifie : le programme s'exécute puis se termine. OnCalendar est le calendrier : tous les jours à 03:15. Vous pouvez aussi écrire daily, weekly ou Mon *-*-* 02:00 ; systemd-analyze calendar "Mon *-*-* 02:00" indique quand aura lieu la prochaine exécution. Persistent=true est important : si le serveur était éteint à 03:15, la tâche s'exécute quand même juste après le démarrage. RandomizedDelaySec répartit légèrement le moment de démarrage.
On active le timer, pas le service :
anna@web1:~$ sudo systemctl daemon-reload
anna@web1:~$ sudo systemctl enable --now opruimen.timer
anna@web1:~$ systemctl list-timers opruimen
NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2026-11-10 03:19:42 CET 15h left - - opruimen.timer opruimen.service
anna@web1:~$ sudo systemctl start opruimen.service # tester maintenant une fois manuellement
anna@web1:~$ journalctl -u opruimen.serviceEt cron ?
cron est l'outil plus ancien et plus simple, et il fonctionne toujours très bien. Une ligne dans la crontab d'un utilisateur (crontab -e) :
15 3 * * * /usr/local/bin/opruimen.shLes cinq champs sont la minute, l'heure, le jour du mois, le mois et le jour de la semaine. Avantages et inconvénients côte à côte :
| cron | timer systemd | |
|---|---|---|
| Mise en place | une ligne | deux fichiers |
| Exécution manquée (serveur éteint) | ignorée | rattrapée avec Persistent=true |
| Sortie et erreurs | par e-mail, si le serveur peut envoyer des e-mails ; sinon perdues | dans le journal |
| Options de sécurité, utilisateur | via le script lui-même | tout ce qu'offre systemd (voir plus haut) |
| Savoir quand il a tourné | difficile | systemctl list-timers et le journal |
Pour un serveur que vous administrez sérieusement, les timers sont le meilleur choix, en particulier pour les sauvegardes (leçon 8.2) : vous voulez être certain qu'elles ont tourné et savoir ce qu'elles ont signalé. Une ligne cron qui échoue en silence, vous ne le découvrez qu'au moment où vous avez besoin de la sauvegarde.
Sur AlmaLinux et Rocky Linux
Les units et les timers fonctionnent exactement de la même façon. Une différence : SELinux peut bloquer un service personnalisé qui lit ou écrit des fichiers à un emplacement auquel SELinux ne s'attend pas, ou qui écoute sur un port inhabituel. Le message se trouve dans /var/log/audit/audit.log. Ne désactivez pas SELinux pour résoudre le problème ; cherchez le bon label ou la bonne règle (par exemple avec semanage).
Résumé
- Un programme qui doit tourner en permanence doit être un service systemd : démarrage automatique, redémarrage en cas de plantage, journaux dans le journal systemd.
- Vos propres units dans
/etc/systemd/system/, avecUser=un utilisateur dédié sans shell, les secrets dans unEnvironmentFileavec les droits 600. NoNewPrivileges,ProtectSystem=strictavecReadWritePaths,ProtectHomeetPrivateTmplimitent les dégâts en cas de faille ;systemd-analyze securityévalue l'unit.- Un timer, c'est un
.serviceplus un.timeravecOnCalendar;Persistent=truerattrape les exécutions manquées. Activez le timer, pas le service. - cron fonctionne toujours, mais les timers offrent de meilleurs journaux et ne manquent aucune exécution.
Quiz
Exercice
Exercice O6.3 : Un service personnalisé avec timer. Créez sur votre serveur un petit service personnalisé avec un timer, par exemple un script qui écrit chaque nuit l'espace disque libre et systemctl --failed dans un fichier ou dans le journal. Utilisez un utilisateur dédié, Type=oneshot, au moins deux options de sécurité et Persistent=true. Testez avec systemctl start, consultez la sortie dans le journal et notez le score de systemd-analyze security. Collez votre .service et votre .timer et répondez aux questions de la liste de contrôle.
Exercice O6.3: Un service personnalisé avec timer
Cet exercice, vous le faites vous-même ; personne ne le vérifie. Cochez ce que vous avez fait et marquez l'exercice comme terminé.
Avec Persistent=true, la tâche s'exécute quand même après le démarrage. Un score inférieur à 6 est bon pour un premier service ; droits d'écriture uniquement via ReadWritePaths.
Vos cases cochées et vos notes restent uniquement dans ce navigateur ; nous ne les envoyons nulle part. Si vous êtes connecté, nous enregistrons dans Mes formations uniquement le fait que vous avez terminé l'exercice.
Pour aller plus loin
- Guide DirectAdmin 15.1: Tâches CRON : exécuter automatiquement des tâches
- Manuel
systemd.exec(5)(User=, ProtectSystem=, NoNewPrivileges=) : https://manpages.debian.org/trixie/systemd/systemd.exec.5.en.html - Manuel
systemd.timer(5)(OnCalendar=, Persistent=) : https://manpages.debian.org/trixie/systemd/systemd.timer.5.en.html - Manuel
systemd.time(7)(calendriers) : https://manpages.debian.org/trixie/systemd/systemd.time.7.en.html
