Systemd est le système d'init et gestionnaire de services par défaut sur la plupart des distributions Linux modernes. Ce guide explique comment créer, gérer et débugger des services systemd personnalisés.
Comprendre Systemd
Qu'est-ce que Systemd ?
Systemd est le premier processus (PID 1) qui démarre au boot et gère tous les autres services système.
Remplace : SysVinit, Upstart Avantages :
- Démarrage parallèle (plus rapide)
- Gestion dépendances
- Logs centralisés (journald)
- Socket activation
- Timers (remplace cron)
Architecture Systemd
systemd (PID 1)
├── Units (services, timers, sockets, etc.)
├── Targets (groupes de services)
├── journald (logs)
├── udevd (devices)
└── logind (sessions utilisateurs)
Types d'Units
| Type | Extension | Description |
| Service | .service | Daemon/process |
| Timer | .timer | Scheduled tasks |
| Socket | .socket | IPC/Network socket |
| Target | .target | Groupe d'units |
| Mount | .mount | Point de montage |
| Path | .path | Surveillance fichier |
Commandes systemctl Essentielles
Gestion Services
# Démarrer service
sudo systemctl start nginx
# Arrêter
sudo systemctl stop nginx
# Redémarrer
sudo systemctl restart nginx
# Reload config (sans interruption)
sudo systemctl reload nginx
# Reload ou restart si reload impossible
sudo systemctl reload-or-restart nginx
# Status détaillé
systemctl status nginx
# Activer au boot
sudo systemctl enable nginx
# Désactiver au boot
sudo systemctl disable nginx
# Activer + démarrer
sudo systemctl enable --now nginx
# Vérifier si activé
systemctl is-enabled nginx
# Vérifier si actif
systemctl is-active nginx
Lister Services
# Tous services
systemctl list-units --type=service
# Services actifs seulement
systemctl list-units --type=service --state=running
# Tous (actifs + inactifs)
systemctl list-units --type=service --all
# Services échoués
systemctl --failed
# Services activés au boot
systemctl list-unit-files --type=service --state=enabled
Analyser Démarrage
# Temps boot total
systemd-analyze
# Temps par service (top 10 plus lents)
systemd-analyze blame | head -10
# Chaîne critique (bottleneck)
systemd-analyze critical-chain
# Graphe dépendances (SVG)
systemd-analyze plot > boot.svg
# Vérifier config service
systemd-analyze verify /etc/systemd/system/myapp.service
Pour une analyse approfondie du temps de démarrage, consultez notre guide dédié à systemd-analyze.
Créer Service Personnalisé
Structure Unit File
[Unit]
Description=Mon application web
Documentation=https://example.com/docs
After=network.target
Wants=mysql.service
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Section [Unit]
[Unit]
# Description courte
Description=My Web Application
# Documentation
Documentation=man:myapp(1) https://example.com/docs
# Dépendances (doit démarrer après)
After=network.target postgresql.service
# Dépendances (doit démarrer avant)
Before=nginx.service
# Dépendances strictes (échoue si absent)
Requires=postgresql.service
# Dépendances souples (continue si absent)
Wants=redis.service
# Conflits (ne peut pas tourner en même temps)
Conflicts=apache2.service
# Conditions
ConditionPathExists=/var/www/myapp
ConditionFileNotEmpty=/etc/myapp/config.yml
Section [Service]
[Service]
# Type de service
Type=simple # Défaut, process au premier plan
# Type=forking # Daemon qui fork (PIDFile requis)
# Type=oneshot # Exécution unique puis arrêt
# Type=notify # Notifie systemd quand prêt
# Type=idle # Attend que autres services démarrent
# Commandes
ExecStart=/usr/bin/myapp --config /etc/myapp.conf
ExecStartPre=/usr/bin/myapp-check # Avant start
ExecStartPost=/usr/bin/notify-ready # Après start
ExecReload=/bin/kill -HUP $MAINPID # Reload config
ExecStop=/usr/bin/myapp-stop # Arrêt custom
ExecStopPost=/usr/bin/cleanup.sh # Après arrêt
# User/Group
User=myapp
Group=myapp
SupplementaryGroups=ssl-cert
# Répertoires
WorkingDirectory=/var/lib/myapp
RootDirectory=/chroot/myapp # chroot
# Environment
Environment="NODE_ENV=production"
Environment="PORT=3000"
EnvironmentFile=/etc/myapp/env
# Restart
Restart=always # Toujours redémarrer
# Restart=on-failure # Seulement si échec
# Restart=on-abnormal # Seulement si crash
RestartSec=10 # Délai avant restart
# Timeouts
TimeoutStartSec=30 # Max temps démarrage
TimeoutStopSec=10 # Max temps arrêt
# PID (pour Type=forking)
PIDFile=/run/myapp.pid
# Logs
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
Section [Install]
[Install]
# Activé avec quel target
WantedBy=multi-user.target # Boot normal
# WantedBy=graphical.target # GUI démarré
# Alias
Alias=webapp.service
# Requis par (inverse de Requires)
RequiredBy=nginx.service
Exemples Services Réels
Service Node.js
# /etc/systemd/system/myapp.service
[Unit]
Description=My Node.js Application
Documentation=https://github.com/user/myapp
After=network.target
[Service]
Type=simple
User=nodejs
Group=nodejs
WorkingDirectory=/opt/myapp
# Node app
ExecStart=/usr/bin/node /opt/myapp/server.js
# Environment
Environment="NODE_ENV=production"
Environment="PORT=3000"
# Restart on failure
Restart=on-failure
RestartSec=10
# Logs
StandardOutput=journal
StandardError=journal
# Security
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Service Python (Gunicorn)
# /etc/systemd/system/webapp.service
[Unit]
Description=Gunicorn WSGI Application
After=network.target
[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/var/www/webapp
# Gunicorn
ExecStart=/var/www/webapp/venv/bin/gunicorn \
--bind 127.0.0.1:8000 \
--workers 4 \
--timeout 60 \
app:application
# Reload gracefully
ExecReload=/bin/kill -s HUP $MAINPID
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Service Docker Container
# /etc/systemd/system/docker-myapp.service
[Unit]
Description=My App Docker Container
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/docker start myapp
ExecStop=/usr/bin/docker stop myapp
[Install]
WantedBy=multi-user.target
Service Backup Script
# /etc/systemd/system/backup.service
[Unit]
Description=Daily Backup Script
After=network.target
[Service]
Type=oneshot
User=backup
Group=backup
ExecStart=/usr/local/bin/backup.sh
# Logs
StandardOutput=journal
StandardError=journal
# Timeout 1 heure
TimeoutStartSec=3600
Activer et Tester Service
Workflow Complet
# 1. Créer unit file
sudo nano /etc/systemd/system/myapp.service
# 2. Recharger systemd (lecture nouveaux files)
sudo systemctl daemon-reload
# 3. Vérifier syntaxe
systemd-analyze verify /etc/systemd/system/myapp.service
# 4. Démarrer service
sudo systemctl start myapp
# 5. Vérifier status
systemctl status myapp
# 6. Voir logs
journalctl -u myapp -f
# 7. Si OK, activer au boot
sudo systemctl enable myapp
# 8. Tester reboot
sudo reboot
# 9. Après reboot, vérifier
systemctl status myapp
Debugging Service
# Status détaillé
systemctl status myapp -l --no-pager
# Dernières lignes logs
journalctl -u myapp -n 50
# Logs temps réel
journalctl -u myapp -f
# Logs depuis boot
journalctl -u myapp -b
# Logs période spécifique
journalctl -u myapp --since "2026-01-24 10:00" --until "2026-01-24 11:00"
# Logs avec priorité error
journalctl -u myapp -p err
# Voir commande exacte exécutée
systemctl show myapp -p ExecStart
# Voir toutes propriétés
systemctl show myapp
# Tester commande manuellement
sudo -u myapp /usr/bin/node /opt/myapp/server.js
Systemd Timers : Alternative Cron
Pourquoi Timers ?
Avantages sur cron :
- Logs dans journald
- Dépendances services
- Calendrier complexe
- Randomisation (eviter pics)
- Meilleure gestion erreurs
Créer Timer
Deux fichiers requis :
- Service (
backup.service)
[Unit]
Description=Backup Script
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
- Timer (
backup.timer)
[Unit]
Description=Daily Backup Timer
Requires=backup.service
[Timer]
# Tous les jours à 2h
OnCalendar=daily
# Ou OnCalendar=*-*-* 02:00:00
# Délai aléatoire (eviter pic)
RandomizedDelaySec=30min
# Persister si système éteint
Persistent=true
[Install]
WantedBy=timers.target
Syntaxe OnCalendar
# Exemples syntaxe
OnCalendar=daily # Tous les jours 00:00
OnCalendar=weekly # Tous les lundis 00:00
OnCalendar=monthly # 1er du mois 00:00
OnCalendar=hourly # Toutes les heures
# Format complet : DOW YYYY-MM-DD HH:MM:SS
OnCalendar=Mon *-*-* 09:00:00 # Lundis 9h
OnCalendar=*-*-01 02:00:00 # 1er du mois 2h
OnCalendar=Mon..Fri 08:00 # Lun-Ven 8h
OnCalendar=*-*-* 00/2:00 # Toutes les 2h
# Multiples moments
OnCalendar=06:00
OnCalendar=18:00
# Tester syntaxe
systemd-analyze calendar "Mon *-*-* 09:00:00"
# Output: Next elapse: Mon 2026-01-26 09:00:00
Les systemd timers remplacent avantageusement cron. Pour une comparaison détaillée, consultez notre guide sur la planification des tâches avec cron.
Gérer Timers
# Activer timer
sudo systemctl enable --now backup.timer
# Lister timers actifs
systemctl list-timers
# Output exemple:
# NEXT LEFT LAST PASSED UNIT ACTIVATES
# Mon 2026-01-26 02:00:00 6h left n/a n/a backup.timer backup.service
# Status timer
systemctl status backup.timer
# Déclencher manuellement
sudo systemctl start backup.service
# Voir dernière exécution
systemctl list-timers --all
Sécurité Services
Sandboxing
[Service]
# Isolation
PrivateTmp=true # /tmp isolé
PrivateDevices=true # Pas accès /dev
ProtectSystem=strict # Filesystem read-only
ProtectHome=true # Pas accès /home
# Capacités
NoNewPrivileges=true # Pas escalade privilèges
CapabilityBoundingSet=CAP_NET_BIND_SERVICE # Seulement bind ports
# Namespace
PrivateNetwork=false # Réseau isolé (true = pas réseau)
Limitations Ressources
[Service]
# CPU
CPUQuota=50% # Max 50% CPU
CPUWeight=100 # Priorité CPU (100-10000)
# Mémoire
MemoryMax=512M # Max RAM
MemorySwapMax=0 # Pas de swap
# I/O
IOWeight=100 # Priorité I/O
IOReadBandwidthMax=/dev/sda 10M # Max 10MB/s lecture
# Tasks
TasksMax=10 # Max 10 processus/threads
# Files
LimitNOFILE=1024 # Max fichiers ouverts
Ces limites fonctionnent via cgroups. Pour une isolation avancée des ressources, découvrez notre guide sur l'isolation des ressources avec cgroups et systemd.
Dépendances et Ordre
Types Dépendances
[Unit]
# Requires (strict) : Échoue si dépendance échoue
Requires=postgresql.service
# Wants (souple) : Continue même si dépendance échoue
Wants=redis.service
# Requisite : Dépendance doit déjà tourner
Requisite=network.target
# BindsTo : Arrêté si dépendance arrêtée
BindsTo=docker.service
# PartOf : Inverse de BindsTo
PartOf=httpd.service
# Conflicts : Ne peut pas tourner avec
Conflicts=apache2.service
Ordre Démarrage
[Unit]
# After : Démarre après (mais pas dépendance)
After=network.target
# Before : Démarre avant
Before=nginx.service
# Exemple : MySQL avant webapp
# mysql.service :
[Unit]
Before=webapp.service
# webapp.service :
[Unit]
After=mysql.service
Requires=mysql.service
Templates Services
Service Template
# /etc/systemd/system/worker@.service
[Unit]
Description=Worker %i
After=network.target
[Service]
Type=simple
User=worker
ExecStart=/usr/bin/worker --instance %i
Restart=always
[Install]
WantedBy=multi-user.target
Instancier Template
# Créer instances
sudo systemctl start worker@1
sudo systemctl start worker@2
sudo systemctl start worker@3
# Activer au boot
sudo systemctl enable worker@1
sudo systemctl enable worker@2
# Status toutes instances
systemctl status 'worker@*'
# %i remplacé par numéro instance
Socket Activation
Pourquoi Socket Activation ?
- Service démarre seulement quand connexion
- Économise ressources
- Boot plus rapide
- Restart sans perte connexions
Créer Socket
Socket file (myapp.socket)
[Unit]
Description=My App Socket
[Socket]
ListenStream=3000
# Ou
ListenStream=/run/myapp.sock # Unix socket
[Install]
WantedBy=sockets.target
Service (myapp.service)
[Unit]
Description=My App
Requires=myapp.socket
[Service]
Type=notify
ExecStart=/usr/bin/myapp
StandardInput=socket
[Install]
# Pas WantedBy (démarré par socket)
Activer Socket
# Activer socket (pas service)
sudo systemctl enable --now myapp.socket
# Service démarre automatiquement à première connexion
curl http://localhost:3000
# Vérifier
systemctl status myapp.socket
systemctl status myapp.service
Targets : Groupes Services
Targets Principaux
# Lister targets
systemctl list-units --type=target
# Targets courants :
poweroff.target # Arrêt système
rescue.target # Mode rescue (single user)
multi-user.target # Mode console (runlevel 3)
graphical.target # Mode graphique (runlevel 5)
reboot.target # Reboot
# Target actuel
systemctl get-default
# Changer target défaut
sudo systemctl set-default multi-user.target
# Passer à autre target
sudo systemctl isolate rescue.target
Créer Target Personnalisé
# /etc/systemd/system/myapp-stack.target
[Unit]
Description=My Application Stack
Requires=mysql.service redis.service nginx.service
After=mysql.service redis.service
# Démarrer tout le stack
sudo systemctl start myapp-stack.target
Monitoring et Logs
Journalctl Avancé
# Logs service
journalctl -u nginx
# Temps réel
journalctl -u nginx -f
# Dernières 100 lignes
journalctl -u nginx -n 100
# Depuis date
journalctl -u nginx --since "2026-01-24"
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --since "10:00" --until "11:00"
# Par priorité
journalctl -u nginx -p err # Errors only
journalctl -u nginx -p warning # Warnings+
# Par boot
journalctl -b # Boot actuel
journalctl -b -1 # Boot précédent
journalctl --list-boots
# Export
journalctl -u nginx -o json > logs.json
journalctl -u nginx -o json-pretty
Pour aller plus loin dans la gestion des logs, consultez notre guide complet sur les logs Linux.
Surveiller Services
# Services échoués
systemctl --failed
# Status tous services
systemctl status
# Services par state
systemctl list-units --state=failed
# Watch status
watch -n 2 'systemctl status myapp'
# Alertes
# Script monitoring.sh
#!/bin/bash
if ! systemctl is-active --quiet myapp; then
echo "Service myapp down!" | mail -s "Alert" admin@example.com
fi
Troubleshooting
Service ne Démarre pas
# 1. Vérifier status
systemctl status myapp -l
# 2. Voir logs complets
journalctl -u myapp -xe
# 3. Vérifier syntaxe unit
systemd-analyze verify /etc/systemd/system/myapp.service
# 4. Tester commande manuellement
sudo -u myuser /path/to/command
# 5. Vérifier permissions
ls -la /path/to/executable
ls -la /path/to/workdir
# 6. Vérifier dépendances
systemctl list-dependencies myapp
# 7. Mode debug
# Ajouter dans [Service]
Environment="DEBUG=true"
Service Crash/Restart Loop
# Voir historique restarts
journalctl -u myapp | grep -i restart
# Augmenter timeout
# Dans myapp.service
[Service]
TimeoutStartSec=300
# Désactiver restart temporairement
sudo systemctl set-property myapp Restart=no
# Voir core dumps
coredumpctl list
coredumpctl info <PID>
Bonnes Pratiques
Organisation Files
# Emplacement system (fourni par packages)
/lib/systemd/system/
# Emplacement admin (customs)
/etc/systemd/system/
# Override (modifications)
/etc/systemd/system/nginx.service.d/override.conf
# User services
~/.config/systemd/user/
Override Service Existant
# Créer override (ne modifie pas original)
sudo systemctl edit nginx
# Ajouter modifications
[Service]
MemoryMax=1G
CPUQuota=50%
# Reload
sudo systemctl daemon-reload
sudo systemctl restart nginx
# Voir override
systemctl cat nginx
Checklist Service
□ Description claire
□ Dépendances correctes (After, Requires)
□ User/Group non-root si possible
□ WorkingDirectory défini
□ Restart policy appropriée
□ Timeouts configurés
□ Logs dans journal
□ Sécurité (sandboxing si possible)
□ Testé après reboot
□ Documentation ajoutée
Conclusion
Systemd offre un contrôle complet sur les services Linux :
Créer service :
# Unit file
sudo nano /etc/systemd/system/myapp.service
# Reload + start
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
Commandes essentielles :
systemctl start/stop/restart service
systemctl enable/disable service
systemctl status service
journalctl -u service -f
Alternatives cron :
# Timer + service = tâches planifiées
OnCalendar=daily
Sécurité :
PrivateTmp=true
NoNewPrivileges=true
MemoryMax=512M
Avec systemd, vous gérez services professionnellement avec logs centralisés, dépendances, et sécurité renforcée !


