Il existe une catégorie de pannes particulièrement traîtresse dans l’administration système : celles qui ne se manifestent pas là où on les cherche. Pas de crash spectaculaire, pas d’alerte rouge sur le tableau de bord, juste un utilisateur qui dit « je n’arrive pas à envoyer mon fichier ». C’est précisément ce scénario qui s’est déroulé récemment sur une infrastructure cloud personnelle, et son diagnostic révèle une cascade de causes imbriquées que même un administrateur expérimenté peut manquer.
L’infrastructure en question
L’architecture est classique et représentative de milliers de déploiements auto-hébergés : une instance Nextcloud 32 tourne sur un serveur Debian derrière un proxy inverse Nginx géré par Nginx Proxy Manager Plus (NPM+), lui-même conteneurisé via Docker. Un outil d’analyse de logs en temps réel, GoAccess, tourne en parallèle dans le même container pour produire des tableaux de bord de trafic. Le tout est hébergé sur une VM Proxmox.
L’utilisateur concerné tente d’uploader un fichier de 130 Ko depuis Firefox sur Windows vers un dossier partagé Nextcloud. Résultat : une erreur 500 XHR. Pas de message explicite, pas d’indication sur la cause.
Premier réflexe : chercher au mauvais endroit
La première hypothèse naturelle est la limite de taille d’upload PHP. C’est le suspect habituel numéro un dans tout problème d’upload Nextcloud. Une vérification rapide confirme que upload_max_filesize est effectivement resté à sa valeur par défaut de 2 Mo — non corrigée depuis l’installation. Mais l’utilisateur l’a su instinctivement : son fichier fait 130 Ko.
La valeur sera quand même portée à 1 Go dans /etc/php/8.4/fpm/php.ini, PHP-FPM redémarré — bonne pratique indépendante du problème. Mais la vraie enquête commence.
Ce que les logs ne disent pas
La méthodologie standard veut qu’on cherche l’erreur 500 dans les logs Apache. Problème : il n’y en a aucune. Les logs Nextcloud (/var/nextcloud-data/data/nextcloud.log) ne montrent rien d’anormal. Les logs Apache non plus. Du côté du navigateur Windows, on observe bien des requêtes PROPFIND (navigation dans les dossiers) avec des réponses 207, mais aucune requête PUT — l’upload ne parvient tout simplement pas au serveur applicatif.
Ce silence est le premier indice décisif : l’erreur 500 est générée par le proxy, pas par Nextcloud. Elle est produite en amont, avant même que la requête ne soit transmise au backend.
Le proxy comme boîte noire
La connexion SSH sur le serveur proxy révèle l’environnement attendu : Alpine Linux, Docker, Nginx Proxy Manager Plus. Mais une simple tentative d’exécuter docker exec pour inspecter le container retourne une erreur inattendue :
OCI runtime exec failed: open /tmp/runc-process553403219: no space left on device
No space left on device. Voilà le coupable. Mais le df -h affiche 24 % d’utilisation, avec 7,8 Go disponibles. Le disque n’est pas plein.
Inodes : le quota invisible
En administration système, il existe deux ressources distinctes sur un système de fichiers : l’espace disque (les blocs) et les inodes (les entrées d’annuaire). Un inode est créé pour chaque fichier ou répertoire. Il est tout à fait possible d’avoir de l’espace disque disponible mais de ne plus pouvoir créer de nouveaux fichiers si la table des inodes est saturée.
$ df -i /
Filesystem Inodes IUsed IFree IUse% Mounted on
overlay 720896 458901 261995 64% /
64 % des inodes sont consommés. Ce n’est pas critique en soi — mais une investigation du répertoire /tmp à l’intérieur du container révèle la réalité :
$ ls /tmp/ | grep goaccess | wc -l
366090
366 090 fichiers nommés goaccess_fifo_XXXXXXXX dans /tmp. Des pipes nommés (FIFOs Unix), créés un par un, jamais supprimés.
GoAccess et le syndrome de la fuite de FIFOs
GoAccess est un analyseur de logs web en temps réel, open source et performant. Configuré avec le drapeau --real-time-html, il ouvre une connexion WebSocket vers le navigateur pour mettre à jour son tableau de bord HTML en direct. Pour chaque connexion WebSocket entrante, GoAccess crée un FIFO dans /tmp comme canal de communication interne.
Le problème : ces FIFOs ne sont pas supprimés lorsque la connexion se termine, ou lorsque le processus GoAccess est redémarré de manière non propre (via SIGKILL plutôt que SIGTERM). Dans l’architecture en question, le script de démarrage du container utilise une boucle while true :
while true; do
goaccess --real-time-html --output=/tmp/goa/index.html \
--unix-socket=/run/goaccess.sock ...
done
GoAccess est relancé immédiatement après chaque sortie. Sur 13 jours de fonctionnement continu — avec des visiteurs qui consultent le tableau de bord GoAccess, chacun créant potentiellement plusieurs FIFOs — 366 000 fichiers fantômes se sont accumulés, représentant 64 % des inodes disponibles.
Résultat : lorsque Nginx tente de créer un fichier temporaire pour buffériser le corps d’une requête HTTP (un upload), le noyau refuse car il ne peut plus créer de nouveaux inodes. Nginx retourne alors une erreur 500, sans aucun log côté Nextcloud.
La cascade complète
Utilisateur (Windows/Firefox)
│
│ PUT /remote.php/dav/files/... (upload 130 Ko)
▼
┌─────────────────────┐
│ Nginx Proxy │ ← Tente de créer un fichier temporaire
│ Manager Plus │ → ECHEC : no space left on device (inodes épuisés)
│ (Docker/Alpine) │ → Retourne HTTP 500
└─────────────────────┘
│
│ (requête jamais transmise)
▼
┌─────────────────────┐
│ Apache / PHP-FPM │ ← Ne voit rien. Aucun log.
│ Nextcloud 32 │
└─────────────────────┘
La chaîne causale complète :
- GoAccess crée des FIFOs à chaque connexion WebSocket sans les nettoyer
- Après 13 jours, 366 000 FIFOs saturent 64 % des inodes du container
- Nginx ne peut plus créer de fichier temporaire pour buffériser l’upload
- Le proxy retourne HTTP 500 avant de transmettre la requête à Nextcloud
- Les logs Nextcloud et Apache ne voient rien — l’erreur est invisible côté backend
- L’utilisateur voit une erreur XHR 500 sans explication
À ce point, une deuxième anomalie est découverte : le fichier de log JSON du container Docker pèse 2,8 Go. Nginx Proxy Manager journalise chaque requête vers tous les domaines proxifiés, et sans configuration de rotation, ce fichier grossit indéfiniment.
Les corrections appliquées
Corrections immédiates
# Suppression des 366 090 FIFOs orphelins
docker exec npmplus find /tmp -maxdepth 1 -name 'goaccess_fifo_*' -delete
# Troncature du log Docker de 2,8 Go
truncate -s 0 /var/lib/docker/containers/<id>/<id>-json.log
Configuration de la rotation des logs Docker
Fichier /etc/docker/daemon.json :
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
Script de nettoyage automatique (Alpine cron)
Fichier /etc/periodic/15min/cleanup-goaccess-fifos :
#!/bin/sh
CONTAINER="npmplus"
FIFO_COUNT=$(docker exec "$CONTAINER" \
sh -c 'ls /tmp/goaccess_fifo_* 2>/dev/null | wc -l' 2>/dev/null || echo 0)
if [ "$FIFO_COUNT" -gt 500 ]; then
docker exec "$CONTAINER" \
find /tmp -maxdepth 1 -name 'goaccess_fifo_*' -delete 2>/dev/null
fi
Fichier /etc/periodic/hourly/cleanup-npmplus :
#!/bin/sh
CONTAINER="npmplus"
LOG=$(docker inspect --format='{{.LogPath}}' "$CONTAINER" 2>/dev/null)
if [ -n "$LOG" ] && [ -f "$LOG" ]; then
SIZE=$(stat -c%s "$LOG" 2>/dev/null || echo 0)
if [ "$SIZE" -gt 209715200 ]; then # 200 Mo
truncate -s 0 "$LOG"
fi
fi
Correction PHP (indépendante)
; /etc/php/8.4/fpm/php.ini
upload_max_filesize = 1G
post_max_size = 1G
Les chiffres après intervention
| Indicateur | Avant | Après |
|---|---|---|
| Espace disque utilisé | ~80 % | 23 % |
| Inodes utilisés | 64 % | 13 % |
| FIFOs GoAccess | 366 000 | < 200 (stable) |
| Log Docker | 2,8 Go | 0 (limité à 100 Mo) |
| CPU (pendant la crise) | 80–100 % | 0 % (idle) |
Les leçons à retenir
1. Surveiller les inodes, pas seulement l’espace disque
La quasi-totalité des outils de monitoring (Grafana, Zabbix, Prometheus) incluent une alerte sur l’espace disque. Beaucoup oublient les inodes. Un système de fichiers avec 95 % d’espace libre peut devenir inutilisable si les inodes sont épuisés. La commande df -i devrait faire partie de tout runbook de diagnostic.
2. Les erreurs 500 côté proxy sont invisibles côté application
Nextcloud, Apache, PHP-FPM n’ont rien vu. Aucune entrée dans leurs logs respectifs. L’erreur existait dans une couche intermédiaire que personne ne regardait. Dans toute architecture en couches (client → CDN → proxy → serveur applicatif → base de données), il faut savoir à quelle couche appartient réellement une erreur avant de chercher.
3. Les logs Docker n’ont pas de limite par défaut
Par défaut, Docker utilise le driver json-file sans aucune limite de taille. Sur un proxy qui journalise des milliers de requêtes par jour, un fichier de plusieurs gigaoctets peut apparaître en quelques semaines. La configuration max-size / max-file devrait être systématique dans tout daemon.json.
4. Méfiez-vous des outils « accessoires » qui tournent en boucle
GoAccess est un outil utile, mais sa configuration dans ce contexte — boucle de redémarrage sans nettoyage — l’a transformé en bombe à retardement silencieuse. Tout démon avec un comportement de persistance sur le filesystem mérite une stratégie de nettoyage explicite. La règle : si un processus crée des fichiers temporaires, il doit aussi les supprimer.
5. L’absence dans les logs est aussi un signal
C’est l’absence de requête PUT dans les logs Apache qui a orienté le diagnostic vers le proxy comme source du problème. Savoir lire ce que les logs ne contiennent pas est aussi important que de lire ce qu’ils contiennent.
Épilogue
L’upload du fichier de 130 Ko a fonctionné quelques minutes après l’intervention. Le serveur Nextcloud tourne désormais avec une infrastructure assainie : 87 % d’inodes libres, 77 % d’espace disque disponible, et des gardes-fous automatiques empêchent une récidive.
La panne aura duré plusieurs heures, causée par 366 000 pipes nommés oubliés dans un coin de /tmp. C’est peut-être la meilleure illustration du principe fondamental de l’administration système : les problèmes les plus sérieux ne viennent pas de ce qu’on voit, mais de ce qu’on ne surveille pas.
Infrastructure : Nextcloud 32 · Apache 2.4 · PHP-FPM 8.4 · Nginx Proxy Manager Plus · Docker · Alpine Linux 3.23 · Proxmox VE
Commentaires
Aucun commentaire pour l'instant. Soyez le premier !
Laisser un commentaire