📍 Cluster : Sysadmin systemd. Articles frères en fin d’article.
Introduction
Sur tout VPS Linux moderne, les logs ne sont plus dispersés dans /var/log/auth.log, /var/log/syslog, /var/log/messages et autres fichiers texte plats : ils sont centralisés dans systemd-journald, un journal binaire structuré, indexé, persistant ou volatile selon configuration. La commande journalctl est l’unique point d’entrée pour interroger ce journal — filtrage par service, par priorité, par fenêtre temporelle, par utilisateur, par cgroup. C’est l’équivalent moderne de tail -f /var/log/syslog | grep ... mais en infiniment plus puissant et structuré.
Apprendre journalctl en profondeur paie immédiatement et durablement. Lors d’un incident production à 2h du matin, savoir taper journalctl -u nginx --since "1 hour ago" -p err -o cat et obtenir en 200 ms les seules erreurs nginx de la dernière heure — au lieu de chercher manuellement dans des fichiers texte de 500 Mo — change la donne. Ce tutoriel couvre les usages quotidiens (consulter les logs d’un service, suivre en temps réel, filtrer par priorité), les usages avancés (export structuré, corrélation entre services, jq sur le format JSON), la gestion du stockage (rétention, compression, persistance), et l’intégration avec les outils d’observabilité modernes (Loki/Promtail, Vector, Wazuh).
Prérequis
- VPS Linux avec systemd ≥ 245 (Debian 11+, Ubuntu 20.04+, Rocky/Alma 8+) — toutes les distributions modernes
- Accès
sudoou appartenance au groupesystemd-journal(sur Debian/Ubuntu, l’utilisateur courant est généralement déjà membre viaadm) - Connaissance de base de
grep,awket des pipes shell — utile pour combiner avec journalctl - Pour la section observabilité : un VPS distinct (ou Docker local) pour Loki + Grafana
- Niveau attendu : intermédiaire
- Temps estimé : 25 à 35 minutes de lecture, plusieurs heures de pratique pour intégrer
Étape 1 — Les commandes journalctl quotidiennes
La majorité des consultations de logs se résument à une dizaine de commandes. Les maîtriser permet de couvrir 90 % des besoins d’investigation. La commande journalctl sans argument affiche tout le journal depuis le dernier boot, paginé via less — utile pour explorer mais rarement la commande de premier réflexe.
# Logs d'un service spécifique
journalctl -u nginx
journalctl -u postgresql
# Suivre en temps réel (équivalent tail -f)
journalctl -u nginx -f
# Logs depuis le dernier boot uniquement
journalctl -b
# Logs du boot précédent (utile après crash + reboot)
journalctl -b -1
# Logs des dernières N minutes / heures / jours
journalctl --since "5 minutes ago"
journalctl --since "1 hour ago" --until "30 minutes ago"
journalctl --since today
journalctl --since "2026-04-27 14:00:00" --until "2026-04-27 15:00:00"
# Logs filtrés par priorité (0=emerg, 7=debug)
journalctl -p err # Erreurs uniquement
journalctl -p warning..err # Warnings + erreurs
journalctl -p crit # Critique uniquement
# Logs d'un binaire/processus spécifique
journalctl _COMM=sshd
journalctl _COMM=postgres
# Combinaison (service + priorité + temps)
journalctl -u nginx -p err --since "1 hour ago"
Trois conventions à retenir. L’option -u est le filtre le plus utile au quotidien : elle restreint aux logs émis par une unit systemd (service, timer, socket). Les expressions de temps acceptent du naturel (« yesterday », « 30 min ago », « 2 hours ago ») en plus du format ISO. Les priorités suivent la convention syslog : 0=emerg, 1=alert, 2=crit, 3=err, 4=warning, 5=notice, 6=info, 7=debug. Le filtre -p err inclut tout ce qui est ≤ 3 (donc emerg, alert, crit, err).
Étape 2 — Sortie et formatage avancés
Par défaut, journalctl affiche les logs au format short (date + hostname + service[pid]: message). Plusieurs autres formats existent, chacun adapté à un usage : cat pour ne garder que le message brut, json pour piper vers jq, verbose pour voir tous les champs structurés cachés, export pour transférer vers un autre journald.
# Format compact (uniquement le message)
journalctl -u nginx --since "1 hour ago" -o cat
# Format verbose : tous les champs structurés (souvent surprenant)
journalctl -u nginx -n 1 -o verbose
# Format JSON : un objet par ligne, parfait pour jq
journalctl -u nginx --since today -o json | jq '.MESSAGE'
# Format JSON pretty (multi-lignes lisible)
journalctl -u nginx -n 5 -o json-pretty
# Pas de pagination (utile pour pipes)
journalctl -u nginx --no-pager
# Reverse (les plus récents en premier)
journalctl -u nginx -r
# Limiter le nombre de lignes
journalctl -u nginx -n 100 # 100 dernières
journalctl -u nginx -n 100 -r # 100 plus récentes en haut
Le format json est la clé pour l’analyse avancée. Chaque entrée du journal est en réalité un objet structuré avec des dizaines de champs (PID, UID, GID, COMM, EXE, CMDLINE, BOOT_ID, MACHINE_ID, HOSTNAME, SYSTEMD_UNIT, SYSTEMD_CGROUP, MESSAGE, PRIORITY, FACILITY, …). Avec jq, on peut extraire et corréler. Exemple : trouver les processus tués par OOM-killer cette semaine et leur cmdline complète.
# OOM-killer events depuis 7 jours, avec PID et cmdline
journalctl --since "7 days ago" -o json | \
jq -r 'select(.MESSAGE | test("Killed process")) | "\(.MESSAGE) | exe=\(.SYSTEMD_USER_UNIT // ._SYSTEMD_UNIT)"'
# Top 10 services les plus bavards en logs cette heure
journalctl --since "1 hour ago" -o json | \
jq -r '.SYSTEMD_UNIT // empty' | \
sort | uniq -c | sort -rn | head -10
# Tous les sudo executions de la semaine, avec utilisateur et commande
journalctl _COMM=sudo --since "7 days ago" -o json | \
jq -r 'select(.MESSAGE | test("COMMAND=")) | "\(.HOSTNAME) \(.MESSAGE)"'
Ces requêtes jq sont l’équivalent moderne et plus puissant des combinaisons grep | awk | sort | uniq classiques. Elles s’écrivent une fois, se gardent dans un fichier ~/.bash_aliases ou un script shared, et s’exécutent en quelques centaines de millisecondes même sur des journaux de plusieurs Go.
Étape 3 — Configuration de la persistance et de la rétention
Par défaut, sur de nombreuses distributions, journald stocke les logs en mode volatile (RAM, perdu au reboot). Pour conserver les logs entre redémarrages — essentiel pour investiguer un crash —, il faut activer le mode persistant. La configuration globale est dans /etc/systemd/journald.conf, complétée par des drop-ins dans /etc/systemd/journald.conf.d/.
# Vérifier le mode actuel
ls -ld /var/log/journal /run/log/journal
# Si /var/log/journal existe → persistant
# Si seulement /run/log/journal → volatile
# Activer la persistance manuellement
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
# Configuration recommandée pour VPS de production
# /etc/systemd/journald.conf.d/01-storage.conf
[Journal]
Storage=persistent
Compress=yes
SystemMaxUse=2G
SystemKeepFree=10%
SystemMaxFileSize=128M
RuntimeMaxUse=200M
MaxRetentionSec=30d
ForwardToSyslog=no
Trois choix structurants. SystemMaxUse=2G limite l’espace disque utilisé par journald — sans cette limite, sur un VPS bavard, les logs peuvent saturer /var en quelques semaines. MaxRetentionSec=30d impose une rétention de 30 jours maximum : suffisant pour investiguer la quasi-totalité des incidents, sans gonfler indéfiniment. ForwardToSyslog=no désactive la duplication vers rsyslog (qui doublerait l’espace consommé) — à adapter si vous avez un rsyslog qui forward vers un SIEM externe.
Étape 4 — Vérifier l’état et le volume du journal
Régulièrement, faire un audit du journal : taille, période couverte, intégrité des fichiers. Cela permet de détecter une dérive (logs qui explosent à cause d’un service mal configuré qui spam toutes les secondes) ou une corruption (rare mais possible après crash disque).
# État global du journal
journalctl --disk-usage
# Archived and active journals take up 1.4G in the file system.
# Période couverte
journalctl --list-boots
# Liste les boots avec timestamps de début/fin
# Vérifier l'intégrité des fichiers journal
journalctl --verify
# Vérifie chaque fichier .journal — détecte corruptions
# Forcer une rotation manuelle (utile pour archiver)
sudo journalctl --rotate
# Effacer les logs plus anciens que 7 jours (manuel, normalement pas nécessaire)
sudo journalctl --vacuum-time=7d
# Vacuuming done, freed 412.0M of archived journals on disk.
# Effacer pour tomber à 500M total
sudo journalctl --vacuum-size=500M
# Voir quels services consomment le plus
journalctl -o json --since "24 hours ago" | jq -r '._SYSTEMD_UNIT // empty' | sort | uniq -c | sort -rn | head -20
Pattern fréquent observé : un service mal configuré (souvent une boucle de retry) qui inonde les logs avec 10-100 messages par seconde finit par dominer le journal en quelques heures. Le repérer rapidement (via journalctl -o json --since "1 hour" | jq '_SYSTEMD_UNIT' | sort | uniq -c | sort -rn) puis corriger la cause (souvent un endpoint externe down, une base de données injoignable, un certificat expiré) évite la saturation. journalctl --vacuum-time nettoie en attendant.
Étape 5 — Tagger ses propres logs applicatifs vers journald
Pour les applications custom (script Python qui tourne en cron, daemon Node.js, batch en Go), envoyer les logs vers journald plutôt que dans un fichier texte permet de bénéficier de toutes les capacités de filtrage et corrélation. Trois méthodes selon le contexte : la commande logger pour les scripts shell, l’API systemd.journal en Python, le standard JSON-via-stderr pour les services systemd modernes.
# Depuis un script bash (logger envoie vers journald)
logger -t mon-backup -p info "Backup terminé en 142 secondes"
logger -t mon-backup -p err "Backup échoué : disque plein"
# Récupérer les logs taggés
journalctl -t mon-backup --since today
# Depuis Python (paquet systemd-python)
# pip install systemd-python
import logging
from systemd.journal import JournalHandler
log = logging.getLogger('mon-app')
log.addHandler(JournalHandler(SYSLOG_IDENTIFIER='mon-app'))
log.setLevel(logging.INFO)
log.info("Démarrage worker pid=%d", os.getpid())
log.error("Erreur paiement reservation_id=%s", res_id, extra={'RESERVATION_ID': res_id})
# Pour les services systemd : il suffit d'écrire sur stdout/stderr
# systemd capture automatiquement et tagge avec le nom du service
# Format JSON structuré reconnu par journald (depuis stdout)
echo '{"MESSAGE":"User logged in","PRIORITY":6,"USER_ID":"42"}' > /dev/stderr
L’avantage de la méthode JSON structurée : chaque clé devient un champ requêtable. Ainsi journalctl USER_ID=42 retrouve instantanément tous les événements liés à l’utilisateur 42, peu importe quel service les a émis. C’est le fondement de l’observabilité moderne — tracer les requêtes à travers plusieurs services via des champs corrélés (request_id, trace_id, user_id), tout cela accessible nativement via journalctl sans agent supplémentaire.
Étape 6 — Forwarder vers Loki/Promtail pour observabilité multi-VPS
Pour un parc de plusieurs VPS, consulter les logs sur chaque machine devient inefficace. La solution moderne : forwarder les logs journald vers une stack Loki centralisée, requêtable via Grafana avec le langage LogQL (similaire à PromQL). Promtail est l’agent officiel Grafana qui scrape les logs locaux (fichiers, journald) et les pousse vers Loki.
# Installation Promtail (Debian/Ubuntu)
sudo apt install -y promtail
# Configuration /etc/promtail/config.yml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: https://loki.votre-entreprise.com/loki/api/v1/push
basic_auth:
username: vps-hotel-saint-louis
password: ${LOKI_PASSWORD}
scrape_configs:
- job_name: journal
journal:
max_age: 12h
labels:
job: systemd-journal
host: vps-hotel-saint-louis
relabel_configs:
- source_labels: ['__journal__systemd_unit']
target_label: 'unit'
- source_labels: ['__journal_priority_keyword']
target_label: 'level'
# Démarrer
sudo systemctl enable --now promtail
sudo systemctl status promtail
# Côté Loki/Grafana, requête LogQL exemple :
# {host="vps-hotel-saint-louis", unit="nginx.service", level="error"} |= "timeout"
Avec cette configuration, tous les logs journald sont automatiquement labellés (host, unit, level) et envoyés à Loki. Côté Grafana, on requête en LogQL avec une syntaxe moderne et performante. Pour 10 VPS qui génèrent ~1 Go de logs/jour chacun, une instance Loki sur un VPS Hetzner CX31 (4 vCPU, 8 Go RAM) gère sans difficulté avec rétention de 30 jours.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
| « No journal files were found » | Mode volatile + reboot ; logs perdus | Activer la persistance (mkdir /var/log/journal + Storage=persistent) |
| journalctl -u nginx vide alors que nginx tourne | nginx logue dans /var/log/nginx/*.log et pas vers stderr | Modifier nginx.conf : error_log stderr; ou utiliser access_log syslog:server=unix:/dev/log; |
| Saturation disque par /var/log/journal | SystemMaxUse non défini, service bavard | Définir SystemMaxUse=2G, identifier le service spam, journalctl --vacuum-size |
| Logs absents pour un service tiers Docker | Container avec --log-driver=json-file par défaut, pas journald | Lancer avec --log-driver=journald ou config /etc/docker/daemon.json |
| journalctl très lent (10+ secondes) | Journaux corrompus, ou index corrompu | journalctl --verify ; si corruption : journalctl --rotate + supprimer les fichiers anciens |
| « User does not have permission to view journal » | Utilisateur pas dans le groupe systemd-journal/adm | sudo usermod -aG systemd-journal $USER + relog |
Adaptation au contexte ouest-africain
Trois ajustements pratiques. Premièrement, gérer le timezone correctement : par défaut journalctl affiche les timestamps en UTC sur les VPS Hetzner. Pour debugger un incident à 14h heure Dakar, il faut mentalement convertir ou ajuster. Deux options : (1) timedatectl set-timezone Africa/Dakar sur le VPS pour forcer toutes les timestamps en heure locale, (2) garder UTC et utiliser journalctl --utc avec une habitude mentale « UTC = heure Dakar (GMT+0) » qui marche pour toute l’Afrique de l’Ouest. Pour une équipe au Cameroun (GMT+1) ou en Guinée Équatoriale (GMT+1), garder les VPS en UTC est moins déroutant.
Deuxièmement, optimiser pour la connectivité limitée : pour un parc de VPS dont l’admin se connecte en 4G/3G depuis Abidjan, télécharger 2 Go de logs via scp est impraticable. Préférer toujours journalctl --since X --until Y -p err pour pré-filtrer côté serveur. Pour les sessions SSH lentes, aliaser jc='journalctl --no-pager -n 200' pour avoir l’output direct sans pagination.
Troisièmement, intégrer avec un Wazuh self-hosted : pour une PME ouest-africaine, déployer Wazuh sur un VPS dédié (4 Go RAM minimum) capture toutes les événements journald de tous les VPS du parc, applique des règles de détection (échecs SSH multiples, modifications de fichiers critiques, escalade de privilèges), et alerte par email/webhook. C’est l’équivalent open source d’un SOC manageable par une seule personne. Voir le tutoriel Wazuh dédié.
Tutoriels frères
- Créer un service systemd : tutoriel 2026 — packager une application en service géré
- systemd secrets et environnement — gestion sécurisée des credentials applicatifs
- systemd timer vs cron : comparatif 2026 — choisir entre les deux schedulers
- Grafana Promtail collecter logs — détail du pipeline Loki/Promtail
FAQ
journalctl ou tail -f /var/log/messages : lequel utiliser ?
Sur un système systemd moderne, journalctl dans tous les cas. tail -f /var/log/messages ne capture que les messages forwardés vers rsyslog, et seulement si rsyslog est installé et configuré (souvent absent par défaut sur Debian 12, Ubuntu 24.04, Rocky 9). journalctl est la source de vérité ; rsyslog devient un consommateur optionnel pour forwarder vers un syslog server externe.
Mes logs persistent au reboot, mais comment être sûr qu’ils ne se perdent pas en cas de crash disque ?
Le journal local n’est jamais une garantie en cas de crash disque sévère. Pour des logs critiques (audit sécurité, paiements, événements compliance), forwarder en temps réel vers un journal central distant : Loki sur un autre VPS, ou un SIEM type Wazuh/Graylog. Promtail forwardé sur un VPS distinct = single point of failure résolu. Pour les exigences réglementaires (PCI-DSS, RGPD), prévoir aussi des backups chiffrés des journaux sur stockage objet (Hetzner Storage Box, S3-compatible).
Comment exporter une période de logs spécifique pour analyse offline ?
Trois méthodes. (1) journalctl --since X --until Y -o json > logs.json exporte au format JSON exploitable par jq, ELK, ou tout outil moderne. (2) journalctl --since X --output=export > logs.export exporte au format binaire interne — peut être ré-importé sur un autre système avec systemd-journal-remote. (3) Pour un export léger : journalctl --since X -p err -o cat > errors.txt ne garde que les messages d’erreur en texte plat, pratique pour partage par email ou ticket support.
Comment détecter les patterns suspects automatiquement ?
Pour des règles simples (échecs SSH, OOM, segfault), Fail2ban ou Logwatch suffisent. Pour des règles complexes (corrélation entre services, anomaly detection statistique), Wazuh ou Graylog. Pour le très avancé : Loki + Grafana avec des alertes LogQL (équivalent Prometheus alerting), ou Vector + un backend ClickHouse pour de la SQL sur logs. Le choix dépend de la taille du parc et de la complexité des règles — en général, commencer simple et monter en gamme quand le besoin émerge.
Pour aller plus loin
- Documentation officielle systemd-journald : freedesktop.org/software/systemd/man/systemd-journald.service.html
- Documentation journalctl : freedesktop.org/software/systemd/man/journalctl.html
- Promtail (agent Loki) : grafana.com/docs/loki/latest/clients/promtail
- Vector (alternative à Promtail, plus performant) : vector.dev/docs
- Tutoriel suivant suggéré : déployer la collecte Promtail/Loki pour centraliser un parc
Mots-clés secondaires : journalctl, systemd-journald, logs structurés, jq journald, observabilité Linux, Loki Promtail, Wazuh, persistance journald, rotation logs, SystemMaxUse, MaxRetentionSec.