Le disque plein ne prévient personne
Le scénario revient sans cesse. Un VPS client tourne depuis des mois, la base de données écrit ses journaux, les sauvegardes s'empilent, et un matin le service s'arrête. Aucune alerte n'est partie, le client l'apprend avant vous. Sur PostgreSQL, un cas rapporté sur la liste officielle du projet montre la ligne exacte : PANIC: could not write to file "pg_wal/xlogtemp.11399": No space left on device. Un PANIC n'est pas un simple avertissement : le serveur ne peut plus journaliser ses écritures, il s'arrête, et il ne redémarre proprement qu'une fois de la place libérée.
La difficulté n'est pas de détecter un disque plein, c'est de le détecter avant. Entre 80 % d'occupation et la panne, il reste souvent des heures ou des jours, et c'est exactement la fenêtre qu'une alerte bien réglée doit capter. Ce guide ne refait pas l'installation d'une stack de supervision : il pose le minimum utile sur chaque VPS, en shell, sans second serveur à entretenir.
Les six alertes minimales et le battement de cœur, sur chaque VPS
- Disque — deux seuils, 80 % en avertissement et 90 % en critique, sur chaque point de montage utile.
- Inodes — un disque peut refuser d'écrire alors qu'il reste de la place en octets, si les inodes sont épuisés.
- Mémoire et OOM — détecter qu'un processus a été tué par le noyau, pas seulement que la RAM est haute.
- Services systemd — être prévenu quand la base, le serveur web ou le proxy passe à l'état failed.
- Certificats — alerter 21 jours avant l'échéance, quand le renouvellement automatique a échoué en silence.
- Sauvegardes — alerter sur l'absence de succès récent, pas seulement sur une erreur explicite.
- Le contrôle lui-même — un battement de cœur externe qui prévient quand les alertes cessent de partir.
Prérequis chiffrés : presque rien
Tout ce qui suit tient dans quelques dizaines de lignes de shell POSIX. Il faut un VPS Linux avec curl, cron ou les timers systemd, un accès root pour écrire dans /usr/local/bin et /etc, et environ 1 Mo de disque.
Deux éléments externes complètent le dispositif : un canal de réception (un bot Telegram, ou n'importe quel webhook accessible en HTTP) et un service de battement de cœur, qui reçoit un ping régulier et vous prévient quand il s'arrête. Pour ce second point, Healthchecks.io propose un plan gratuit limité à 20 jobs et 100 entrées de journal par job, ce qui couvre largement un parc de petite taille ; vérifiez ces limites à la source avant de vous y fier. Le mode push d'Uptime Kuma remplit le même rôle sur une machine à vous.
Disque et inodes : les seuils et ce qui les déplace
La commande utile est df -h --output=pcent,target : elle n'affiche que le pourcentage et le point de montage, ce qui se lit et se parse facilement. Pour les inodes, la même idée devient df --output=ipcent,target. Un dossier de sessions ou de petits fichiers de cache peut consommer tous les inodes d'un système de fichiers ext4 alors que df -h affiche encore 40 % d'occupation : l'écriture échoue pourtant avec le même No space left on device.
Il y a une seconde subtilité. Sur ext4, 5 % des blocs sont réservés par défaut aux processus privilégiés, d'après la page de manuel de tune2fs ; c'est ce qui permet aux démons système de continuer à écrire quand un utilisateur ordinaire a rempli le disque. Conséquence : un service qui tourne sous un compte non privilégié, comme la base de données, voit df afficher 100 % alors que le disque est occupé à environ 95 %. D'où les seuils retenus : 80 % pour avoir le temps d'agir, 90 % pour agir tout de suite. Attendre 100 % revient à alerter après la panne.
Poser le dispositif en une heure
Créer le canal d'alerte
Créez un bot Telegram avec BotFather, notez son jeton et l'identifiant de la conversation qui doit recevoir les messages. Toutes les requêtes de l'API Bot suivent la forme
https://api.telegram.org/bot<token>/METHOD_NAME, et la méthode d'envoi estsendMessage.Écrire le fichier d'environnement
sudo mkdir -p /var/lib/alerte-vpssudo install -m 600 /dev/null /etc/alerte-vps.env
Puis ajoutez-y quatre lignes :TG_TOKEN=...,TG_CHAT=...,HC_URL=https://hc-ping.com/<uuid>(check toutes les cinq minutes) etHC_URL_JOUR=https://hc-ping.com/<autre-uuid>(check quotidien). Le mode 600 garde le jeton lisible par root seul.Écrire le script d'envoi avec anti-répétition
Enregistrez ces lignes dans
/usr/local/bin/alerte-envoipuis rendez le fichier exécutable avecsudo chmod 755. Le fichier de verrou par message évite d'être réveillé toutes les cinq minutes pour la même alerte : un message identique n'est renvoyé qu'après une heure.#!/bin/sh. /etc/alerte-vps.envf=/var/lib/alerte-vps/$(echo "$1" | md5sum | cut -c1-12)[ -n "$(find "$f" -mmin -60 2>/dev/null)" ] && exit 0touch "$f"curl -fsS -m 10 --retry 3 "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" -d chat_id="${TG_CHAT}" --data-urlencode text="$(hostname) $1"Contrôler disque et inodes
Dans
/usr/local/bin/alerte-rapide(shebang#!/bin/sh), ajoutez les deux boucles suivantes. Le filtregrep -vécarte les systèmes de fichiers qui n'exposent pas d'inodes et affichent un tiret.df --output=pcent,target -x tmpfs -x devtmpfs | tail -n +2 | while read p m; do n=${p%\%}; if [ "$n" -ge 90 ]; then alerte-envoi "CRIT disk $m"; elif [ "$n" -ge 80 ]; then alerte-envoi "WARN disk $m"; fi; donedf --output=ipcent,target -x tmpfs -x devtmpfs | tail -n +2 | grep -v '^ *-' | while read p m; do [ "${p%\%}" -ge 90 ] && alerte-envoi "INODES $m"; doneDétecter les processus tués par l'OOM killer
Le compteur
oom_killde/proc/vmstatne fait que croître depuis le démarrage. On le compare à la valeur précédente, conservée dans un fichier ; après un redémarrage le compteur repart de zéro, la comparaison est fausse une fois, puis se rétablit.n=$(awk '/^oom_kill/ {print $2}' /proc/vmstat)old=$(cat /var/lib/alerte-vps/oom 2>/dev/null || echo 0)[ "$n" -gt "$old" ] && alerte-envoi "OOM kill"echo "$n" > /var/lib/alerte-vps/oomAjouter le battement de cœur et planifier
Terminez
alerte-rapidepar le ping. Il n'est atteint que si le script a tourné jusqu'au bout ; si cron s'arrête, si le disque est en lecture seule ou si le script plante, le ping cesse et le service externe vous prévient. Ajoutez ensuite les lignes de cron dans/etc/cron.d/alerte-vps.. /etc/alerte-vps.env; curl -fsS -m 10 --retry 5 -o /dev/null "$HC_URL"PATH=/usr/local/bin:/usr/bin:/bin*/5 * * * * root /usr/local/bin/alerte-rapide30 7 * * * root /usr/local/bin/alerte-quotidienContrôler certificats et sauvegardes une fois par jour
Dans
/usr/local/bin/alerte-quotidien, ajoutez ces lignes. L'option-checkendd'openssl x509échoue si le certificat expire dans le nombre de secondes donné ; 1814400 secondes valent 21 jours. Le test de sauvegarde alerte quand aucun fichier.gznon vide n'est plus récent que 1560 minutes, soit 26 heures. Le script se termine par son propre ping, vers le second check : sans lui, un contrôle quotidien tombé en silence passerait inaperçu.echo | openssl s_client -servername votre-domaine.com -connect votre-domaine.com:443 2>/dev/null | openssl x509 -noout -checkend 1814400 || alerte-envoi "CERT votre-domaine.com"find /var/backups/app -name '*.gz' -mmin -1560 -size +1k | grep -q . || alerte-envoi "BACKUP absent". /etc/alerte-vps.env; curl -fsS -m 10 --retry 5 -o /dev/null "$HC_URL_JOUR"Brancher les services critiques sur OnFailure
Créez
/etc/systemd/system/[email protected]avec le contenu suivant, puissudo systemctl daemon-reload.[Unit]Description=Alerte echec de %i[Service]Type=oneshotExecStart=/usr/local/bin/alerte-envoi "FAILED %i"
Pour chaque service à surveiller, ouvrez un drop-in avecsudo systemctl edit mariadb.serviceet ajoutez ces deux lignes.[Unit]OnFailure=notify-failure@%n.serviceForcer une alerte pour tester, puis nettoyer
Une alerte jamais déclenchée n'est pas une alerte. Vérifiez d'abord l'espace libre, créez un fichier assez gros pour franchir le seuil, lancez le script à la main, puis supprimez le fichier. Si le message n'arrive pas, le défaut est dans le canal, et il vaut mieux le découvrir maintenant.
df -h --output=pcent,target /var/tmpsudo fallocate -l 2G /var/tmp/test-alerte.imgsudo /usr/local/bin/alerte-rapidesudo rm /var/tmp/test-alerte.img
Mémoire : OOM du noyau ou limite de cgroup
Deux mécanismes tuent un processus pour manque de mémoire, et ils ne se lisent pas au même endroit. Le premier est l'OOM killer du noyau, quand la machine entière manque de RAM : le journal du noyau contient alors un message du type Out of memory: Killed process, que journalctl -k permet de retrouver. Le second est la limite d'un cgroup. Selon la documentation de systemd, quand l'usage d'une unité ne peut pas être contenu sous MemoryMax=, l'OOM killer est invoqué à l'intérieur de l'unité : le reste de la machine peut avoir de la mémoire libre.
Le compteur oom_kill de /proc/vmstat couvre le premier cas à l'échelle de la machine. Pour une unité précise, le fichier memory.events de son cgroup expose un compteur oom_kill propre, défini par la documentation du noyau comme le nombre de processus du cgroup tués par n'importe quel type d'OOM killer. Sur un VPS qui héberge une application gourmande, comme celle décrite dans le guide n8n et OOM en production, c'est ce second compteur qui explique les arrêts que free -h ne montre jamais.
OnFailure : ce qu'il couvre et ce qu'il ne couvre pas
Selon la documentation de systemd, OnFailure= liste les unités activées quand l'unité entre dans l'état failed. Le gabarit [email protected] reçoit le nom de l'unité fautive grâce au spécificateur %n, et le message d'alerte peut y joindre la sortie de systemctl status ou de journalctl -u pour le contexte.
La limite est structurelle : un service qui tourne mais ne répond plus n'est jamais passé par l'état failed, donc OnFailure= ne dit rien. Un service relancé par Restart= n'y arrive qu'une fois la limite de démarrage atteinte. Pour ces cas, ajoutez une sonde applicative simple, par exemple un curl sur l'adresse de santé de l'application, dans le script rapide. La syntaxe des timers et de cron est traitée dans le guide cron et timers systemd, et il n'y a pas lieu de la répéter ici.
Certificats et sauvegardes : alerter sur l'absence de succès
Un renouvellement automatique de certificat peut échouer en silence pendant des semaines : un enregistrement DNS modifié, un port bloqué, un plugin cassé. Le test openssl interroge ce que le visiteur reçoit réellement, ce qui vaut mieux que de lire un fichier sur le disque. Sur un serveur géré par Certbot, certbot certificates liste les certificats et leur échéance, et certbot renew --dry-run vérifie que le renouvellement futur aboutira.
Même logique pour les sauvegardes. Une alerte déclenchée par l'échec d'un script de sauvegarde ne voit pas le cas où le script ne s'est jamais lancé. L'alerte correcte se pose sur l'absence : aucun fichier récent, ou un fichier de taille nulle. C'est ce que fait la ligne find -mmin du script quotidien, et c'est pourquoi elle reste muette quand tout va bien et bavarde dès que la chaîne de sauvegarde s'arrête, quelle qu'en soit la raison.
Scripts maison ou stack de supervision
Faites défiler le tableau
| Critère | Scripts shell et OnFailure | Beszel ou Uptime Kuma |
|---|---|---|
| Mise en place | Une heure, quelques fichiers dans `/usr/local/bin` | Un hub à héberger, des agents à installer sur chaque VPS |
| Parc visé | 1 à 5 VPS, jusqu'à 10 si les seuils sont identiques | Au-delà de 5 à 10 VPS, ou dès qu'il faut comparer |
| Historique et courbes | Aucun, seulement l'instant de l'alerte | Graphiques, tendances, comparaison entre machines |
| Entretien | Rien à mettre à jour hors du shell | Un second serveur et ses mises à jour à tenir |
| Services applicatifs | Sonde `curl` à écrire soi-même | Sondes HTTP, TCP et page de statut prêtes à l'emploi |
| Panne du moniteur | Détectée par le battement de cœur externe | À surveiller séparément, le hub est lui-même une machine |
| Quand le choisir | Alertes minimales, parc réduit, zéro serveur en plus | Historique voulu, parc en croissance, équipe partagée |
Surveillez le surveillant
Le piège de ces scripts est de devenir une fausse assurance : ils se taisent parce que tout va bien, ou parce qu'ils sont cassés, et le silence ressemble dans les deux cas. Le ping de battement de cœur règle le problème. Réglez la période attendue du check un peu au-dessus de l'intervalle de cron, avec une marge de tolérance. Gardez aussi /etc/alerte-vps.env en mode 600 et ne versionnez jamais le jeton.
Dépannage : les erreurs qui reviennent
Les alertes elles-mêmes tombent en panne de façon prévisible. Chaque cas se vérifie à la main avant de chercher plus loin.
Messages et symptômes avec leur remède
PANIC: could not write to file "pg_wal/...": No space left on device— le volume qui porte les journaux de PostgreSQL est plein. Libérez d'abord de la place hors de PostgreSQL (journaux tournants, anciennes sauvegardes), ne supprimez jamais les fichiers depg_walà la main, puis redémarrez le service.No space left on devicealors quedf -hannonce de la place — les inodes sont épuisés. Lancezdf --output=ipcent,target, repérez le dossier qui contient des millions de petits fichiers (sessions, cache) et purgez-le.- Disque plein mais
dune retrouve pas l'espace — des fichiers supprimés restent ouverts par un processus.sudo lsof +L1les liste ; redémarrer le processus concerné libère l'espace. oom_killaugmente, rien dansjournalctl -k— le noyau a roulé ses journaux ou la mort vient d'une limiteMemoryMax=. Lisezmemory.eventsdans le répertoire du cgroup de l'unité etjournalctl -upour l'unité.- Une unité tourne mais ne répond plus, aucune alerte — c'est la limite de
OnFailure=. Ajoutez une sondecurlsur l'adresse de santé et alertez sur le code de retour. - Aucun message Telegram, aucune erreur visible — cron envoie la sortie du script par courrier local, que personne ne lit. Lancez
alerte-envoi testà la main avecsudoet lisez la réponse decurl. - Le certificat approche de l'échéance alors que Certbot est installé — lancez
certbot renew --dry-runet lisez l'erreur de validation, puis corrigez le DNS ou le port concerné.
La documentation officielle
Chaque comportement décrit ici vient d'une source primaire. Pour OnFailure=, la page de manuel de systemd.unit ; pour MemoryMax=, systemd.resource-control ; pour les compteurs oom et oom_kill, la documentation des cgroups v2 du noyau ; pour les 5 % réservés, la page de manuel de tune2fs. Côté envoi, l'API Bot de Telegram décrit la forme des requêtes, et la documentation de l'API Healthchecks les adresses de ping, avec la page des tarifs pour les limites du plan gratuit. Pour le renouvellement, la documentation de Certbot. Le message PostgreSQL cité vient d'un fil de la liste officielle du projet.
Des alertes simples, la même sur chaque VPS
Six alertes et un battement de cœur ne remplacent pas une stack de supervision, ils couvrent ce qui fait réellement tomber un serveur : disque, inodes, mémoire, services, certificats, sauvegardes. Le gain principal est l'homogénéité : mêmes seuils et mêmes messages sur chaque machine. Quand le parc grandit ou que l'historique devient nécessaire, la bascule vers une stack se prépare dans le guide Beszel et dans le guide des alertes proactives Beszel et Uptime Kuma, qui traitent l'installation de bout en bout.