ITSkillsCenter
Blog

systemd services en production : tutoriel pratique 2026

10 دقائق للقراءة

Lecture : 9 minutes · Niveau : intermédiaire · Mise à jour : avril 2026

systemd est le gestionnaire de services par défaut sur Ubuntu, Debian, Rocky, Fedora et la plupart des distributions Linux modernes. Bien le maîtriser fait la différence entre un service qui plante silencieusement et un service auto-réparant, journalisé et sandboxé. Ce tutoriel couvre ce qu’on utilise vraiment au quotidien.

Voir aussi → Linux administration avancée pour PME : guide pratique.


Sommaire

  1. Anatomie d’un unit file
  2. Créer un service custom
  3. Logging avec journalctl
  4. Sandboxing : durcir un service
  5. systemd timers vs cron
  6. Dépendances et ordre de démarrage
  7. Debugging : pourquoi mon service plante ?
  8. FAQ

1. Anatomie d’un unit file

Un service systemd se définit dans un fichier .service, structuré en trois sections :

[Unit]
Description=Mon application Node.js
Documentation=https://exemple.com/docs
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=monapp
ExecStart=/usr/bin/node /opt/mon-app/server.js
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
  • [Unit] : métadonnées et dépendances (ce que ce service requiert et après quoi il démarre)
  • [Service] : comment lancer et superviser le processus
  • [Install] : sous quelle « cible » l’activer au boot

Les types de service

Type Usage
simple Processus qui reste au premier plan (90% des cas)
forking Processus qui se daemonise lui-même (vieux daemons)
oneshot Tâche qui s’exécute une fois et termine (utile pour timers)
notify Le processus signale systemd quand prêt (sd_notify)
idle Comme simple mais attend que les autres jobs finissent

Pour un service Node.js, Python (Gunicorn/Uvicorn), Go, Rust, ou un binaire moderne : Type=simple. Pour un script de backup déclenché par timer : Type=oneshot.


2. Créer un service custom

Préparer le système

# Créer un utilisateur dédié (jamais root pour une app)
sudo useradd --system --no-create-home --shell /usr/sbin/nologin monapp

# Préparer le dossier
sudo mkdir -p /opt/mon-app /var/log/mon-app
sudo chown -R monapp:monapp /opt/mon-app /var/log/mon-app

Écrire le fichier service

/etc/systemd/system/mon-app.service :

[Unit]
Description=Mon application Node.js
After=network.target

[Service]
Type=simple
User=monapp
Group=monapp
WorkingDirectory=/opt/mon-app
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=-/etc/mon-app/env
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Le - devant EnvironmentFile rend le fichier optionnel : pas d’erreur si absent.

Activer et démarrer

sudo systemctl daemon-reload
sudo systemctl enable --now mon-app
sudo systemctl status mon-app

enable --now active au boot ET démarre maintenant, en une commande.

Vérifier le bon fonctionnement

# Statut détaillé
sudo systemctl status mon-app

# Le processus est bien lancé sous le bon user
ps -ef | grep node

# Le port écoute
sudo ss -tlnp | grep :3000

3. Logging avec journalctl

systemd centralise les logs des services dans journald. Un service qui écrit sur stdout/stderr voit ses logs automatiquement collectés.

Commandes essentielles

# Suivi temps réel (tail -f sur les logs du service)
sudo journalctl -u mon-app -f

# Les 100 dernières lignes
sudo journalctl -u mon-app -n 100

# Depuis une heure
sudo journalctl -u mon-app --since "1 hour ago"

# Entre deux dates
sudo journalctl -u mon-app --since "2026-04-25 08:00" --until "2026-04-25 12:00"

# Uniquement les erreurs
sudo journalctl -u mon-app -p err

# Logs depuis le dernier boot
sudo journalctl -u mon-app -b

# Format JSON (pour parsing)
sudo journalctl -u mon-app -o json --since today

Limiter la taille des logs

Par défaut, journald peut consommer beaucoup d’espace disque. Configurer dans /etc/systemd/journald.conf :

[Journal]
SystemMaxUse=2G
SystemMaxFileSize=50M
MaxRetentionSec=2week

Puis :

sudo systemctl restart systemd-journald

# Forcer le nettoyage immédiat
sudo journalctl --vacuum-size=2G
sudo journalctl --vacuum-time=2weeks

4. Sandboxing : durcir un service

systemd offre des directives de sécurité puissantes qui limitent ce qu’un service peut faire en cas de compromission. À utiliser systématiquement.

[Service]
# ... directives standard ...

# === SANDBOXING ===
# Pas de privilèges supplémentaires
NoNewPrivileges=true

# /tmp privé (isolation)
PrivateTmp=true

# /home, /root, /run/user en lecture seule
ProtectHome=true

# /usr, /boot, /etc en lecture seule
ProtectSystem=strict

# Sauf ces chemins en écriture
ReadWritePaths=/var/log/mon-app /var/lib/mon-app

# Pas d'accès aux périphériques (sauf basiques)
PrivateDevices=true

# Bloquer accès kernel logs
ProtectKernelLogs=true

# Bloquer modules kernel
ProtectKernelModules=true

# Bloquer accès kernel tunables (/proc/sys, /sys)
ProtectKernelTunables=true

# Réseau : bloquer si vraiment pas nécessaire
# PrivateNetwork=true   # ne JAMAIS activer pour service web

# Capabilities Linux : enlever tout sauf ce qui est requis
CapabilityBoundingSet=

# Restreindre les syscalls dangereux
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM

Tester l’efficacité

# Analyse l'exposition d'un service
systemd-analyze security mon-app

Cette commande note le service de 0 (très exposé) à 10 (extrêmement durci). Viser 5+ pour des services en production exposés au réseau.

Voir aussi → Linux sécurité et hardening en production.


5. systemd timers vs cron

Pour les tâches planifiées (backups, nettoyage, sync), systemd timers offrent plusieurs avantages sur cron :

  • Logs intégrés via journalctl
  • Persistance : si le système était éteint à l’heure prévue, l’exécution est rattrapée
  • Dépendances explicites avec d’autres services
  • Reprise après échec contrôlable

Timer + service

/etc/systemd/system/backup.service :

[Unit]
Description=Backup quotidien

[Service]
Type=oneshot
User=admin
ExecStart=/opt/scripts/backup.sh

/etc/systemd/system/backup.timer :

[Unit]
Description=Lance backup.service quotidiennement à 02h

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

Persistent=true garantit l’exécution si la machine était éteinte au moment prévu. RandomizedDelaySec=300 ajoute un décalage aléatoire de 0 à 5 minutes (utile pour éviter les pics simultanés sur plusieurs serveurs).

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer

# Voir tous les timers actifs
sudo systemctl list-timers

# Tester le service immédiatement (sans attendre le timer)
sudo systemctl start backup.service
sudo journalctl -u backup -n 50

Syntaxe OnCalendar

*-*-* 02:00:00          → tous les jours à 02h
Mon..Fri *-*-* 09:00    → semaine à 09h
*-*-01 03:00            → 1er du mois à 03h
hourly                  → toutes les heures à HH:00
daily                   → tous les jours à 00:00
weekly                  → lundi 00:00

6. Dépendances et ordre de démarrage

After vs Requires

[Unit]
After=postgresql.service       # démarre APRÈS postgresql
Requires=postgresql.service    # nécessite postgresql actif (échoue sinon)
Wants=redis.service            # souhaite redis (n'échoue pas si absent)
  • After= : ordre seulement, n’implique pas que le service requis doit être actif
  • Requires= : si le service requis échoue, le mien échoue aussi
  • Wants= : tente de démarrer le service mais continue même en cas d’échec

Pour la plupart des cas : After=network.target postgresql.service + Wants=postgresql.service est sain (ne bloque pas le boot complet si Postgres a un souci).

Cibles (targets)

multi-user.target    → équivalent runlevel 3 (mode console)
graphical.target     → équivalent runlevel 5 (avec interface graphique)
network.target       → réseau de base configuré
network-online.target → réseau opérationnel (DNS, etc.)

Pour un service qui doit attendre que le DNS résolve : After=network-online.target + Wants=network-online.target.


7. Debugging : pourquoi mon service plante ?

Méthode systématique

# 1. Statut + dernières lignes de log
sudo systemctl status mon-app

# 2. Logs détaillés
sudo journalctl -u mon-app -n 200 --no-pager

# 3. Tester le ExecStart manuellement (avec le bon user)
sudo -u monapp /usr/bin/node /opt/mon-app/server.js

# 4. Vérifier les permissions des fichiers
sudo -u monapp ls -la /opt/mon-app /var/log/mon-app

# 5. Vérifier les variables d'environnement vues par systemd
sudo systemctl show mon-app | grep -i environment

Erreurs fréquentes

Symptôme Cause probable
status=203/EXEC ExecStart pointe vers un binaire qui n’existe pas ou n’est pas exécutable
status=217/USER L’utilisateur User= n’existe pas
Failed to load configuration Erreur de syntaxe dans le .service. Vérifier : systemd-analyze verify mon-app.service
Service redémarre en boucle Restart=always sans cause de plantage corrigée. Augmenter RestartSec et lire les logs
code=killed, signal=KILL (OOM) Pas assez de RAM. Vérifier dmesg et la limite mémoire système

Recharger après modification

À chaque édition d’un .service ou .timer :

sudo systemctl daemon-reload
sudo systemctl restart mon-app

Sans daemon-reload, systemd continue d’utiliser l’ancienne configuration en mémoire.


8. FAQ

Faut-il toujours créer un user dédié pour un service ?

Oui pour tout service qui traite des entrées non-fiables (web, API, parsing de fichiers). Jamais de User=root pour ce type de service. Pour un script interne uniquement déclenché par admin : un user système suffit ou l’admin lui-même.

Restart=always ou Restart=on-failure ?

  • on-failure : redémarre seulement en cas de plantage (exit code != 0). Recommandé pour 90% des cas
  • always : redémarre même en cas d’arrêt normal (utile pour des services qui doivent vraiment tourner en permanence, comme un proxy)

Comment passer des secrets à un service sans les mettre en clair ?

Trois approches :

  1. EnvironmentFile= pointant sur un fichier en chmod 600 root:monapp que seul le service peut lire
  2. systemd LoadCredential= (depuis systemd 250+) qui charge en mémoire éphémère
  3. Vault, AWS Secrets Manager, ou équivalent appelé au démarrage du service

Ne jamais mettre Environment=DB_PASSWORD=xxx directement dans le .service (lisible par tous via systemctl show).

Un service utilisateur (sans sudo) est-il possible ?

Oui : ~/.config/systemd/user/mon-app.service puis systemctl --user enable --now mon-app. Limité au compte utilisateur, ne survit pas au logout sauf si sudo loginctl enable-linger $USER.

journalctl consomme tout l’espace disque, que faire ?

Limiter via /etc/systemd/journald.conf (SystemMaxUse=, MaxRetentionSec=) puis journalctl --vacuum-size=2G. Voir section logging plus haut.

systemd timer ou cron : quel choix par défaut ?

Pour une nouvelle tâche : timer systemd. Logs centralisés, persistance, intégration. Pour un script existant déjà en cron qui marche : pas la peine de tout refaire. Cron reste valide, surtout pour des règles très simples.


Articles liés (cluster Linux administration)


Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité