Ubuntu 24.04 : Corriger « Too many open files » sur Nginx en augmentant les limites correctement (systemd)

Cet article vous a aidé ?

Ça commence par quelques 502. Puis vos graphiques ressemblent à un sismographe. Nginx est « up », la charge système est correcte, mais les clients subissent des réinitialisations de connexion et votre journal d’erreurs se met à répéter : Too many open files.

Sur Ubuntu 24.04, la solution n’est rarement « augmentez simplement ulimit ». Ce conseil mène à un joli chiffre dans un shell et à la même panne en production. La bonne approche concerne les limites du unit systemd, les plafonds propres à Nginx et le budget global de descripteurs de fichiers du noyau — plus la vérification de tout cela avec des commandes fiables.

Ce que signifie vraiment « Too many open files » sur Nginx

Le noyau renvoie EMFILE lorsqu’un processus atteint sa limite de descripteurs de fichiers par processus. Nginx logue cela comme « Too many open files » lorsqu’il ne peut pas ouvrir une socket, accepter une connexion, ouvrir un fichier ou créer une connexion vers un upstream.

Nuance importante : les « fichiers » de Nginx ne sont pas que des fichiers. Ce sont des sockets, des pipes, des eventfd, des signalfd et tout ce que le processus ouvre. Si Nginx est un reverse proxy chargé, la plupart des descripteurs sont des sockets : connexions clients + connexions upstream + sockets d’écoute + fichiers de logs. S’il sert des fichiers statiques, il y a aussi des handles de fichiers ouverts. Si vous utilisez open_file_cache, vous pouvez augmenter intentionnellement les handles ouverts. Si vous utilisez HTTP/2 ou HTTP/3, les schémas de connexion changent, mais la comptabilité des descripteurs reste cruciale.

Sur Ubuntu 24.04, Nginx est généralement géré par systemd. Cela signifie :

  • /etc/security/limits.conf ne s’applique pas de façon fiable aux services systemd.
  • Ce que vous définissez dans un shell interactif (ulimit -n) est sans effet sur le processus master de Nginx lancé par systemd.
  • Il existe plusieurs couches de limites : configuration Nginx, unité systemd, limites PAM pour les sessions utilisateur et plafonds du noyau.

Voici le seul modèle mental qui évite systématiquement la boucle « je l’ai augmenté, pourquoi c’est toujours cassé ? » :

  1. Plafond global du noyau : combien de descripteurs le système entier peut allouer (fs.file-max, file-nr), plus le maximum par processus autorisé à être défini (fs.nr_open).
  2. Plafond du service systemd : LimitNOFILE sur l’unité Nginx (ou une valeur par défaut systemd appliquée à tous les services).
  3. Plafond interne Nginx : worker_rlimit_nofile et le maximum implicite par worker_connections.
  4. Réalité : le nombre réel de descripteurs ouverts sous charge.

De plus, « too many open files » peut être le symptôme d’une fuite. Ce n’est pas toujours « il faut un nombre plus grand ». Si les descripteurs augmentent de manière continue avec un trafic constant, quelque chose n’est pas fermé. Cela peut être un problème d’upstream, un module défaillant ou un schéma de logging/caching qui ne libère jamais.

Blague n°1 : les descripteurs de fichiers sont comme les tasses à café dans la cuisine du bureau — si personne ne les ramène, vous finirez par ne servir personne, et quelqu’un accusera le lave-vaisselle.

Mode opératoire diagnostic rapide (quoi vérifier en priorité)

Voici la séquence qui trouve rapidement le goulot d’étranglement, sans se battre contre des suppositions.

1) Confirmer l’erreur et où elle se produit

  • Journal d’erreurs Nginx : échoue-t-il sur accept(), open() ou des sockets upstream ?
  • Journal systemd : voyez-vous « accept4() failed (24: Too many open files) » ou similaire ?

2) Vérifier les limites des processus Nginx vues par le noyau

  • Consultez /proc/<pid>/limits pour le master Nginx et un worker.
  • Si la limite est basse (1024/4096), c’est presque toujours une configuration systemd, pas Nginx.

3) Mesurer l’utilisation réelle des FD et l’évolution

  • Comptez les descripteurs ouverts par worker.
  • Recherchez une croissance monotone sous charge stable (odeur de fuite).

4) Valider la capacité système globale

  • sysctl fs.file-max et cat /proc/sys/fs/nr_open
  • cat /proc/sys/fs/file-nr pour voir alloué vs utilisé.

5) Ce n’est qu’ensuite qu’on ajuste

  • Augmentez LimitNOFILE dans un drop-in systemd.
  • Définissez worker_rlimit_nofile et alignez worker_connections.
  • Rechargez, vérifiez et testez en charge (ou au moins confirmez sous trafic réel).

Faits et contexte intéressants (pourquoi cela revient)

  • Fait 1 : Les descripteurs Unix préexistent au réseau moderne ; les sockets ont été conçues pour ressembler à des fichiers afin de réutiliser les API.
  • Fait 2 : La limite soft classique de 1024 vient d’une époque où 1 000 connexions simultanées semblaient de la science-fiction, pas un mardi.
  • Fait 3 : Le modèle événementiel de Nginx est efficace en partie parce qu’il maintient de nombreuses connexions ouvertes simultanément — ce qui signifie qu’il consommera volontiers des FD si vous le laissez faire.
  • Fait 4 : systemd n’hérite pas automatiquement du ulimit de votre shell ; les services obtiennent leurs limites depuis les fichiers d’unité et les valeurs par défaut de systemd.
  • Fait 5 : Sur Linux, fs.nr_open borne la valeur maximale pour les fichiers ouverts par processus que vous pouvez définir, même en root.
  • Fait 6 : « Too many open files » peut apparaître même si l’utilisation système globale des FD est correcte — car les limites par processus frappent en premier.
  • Fait 7 : Nginx peut atteindre les limites de FD plus rapidement lorsqu’il fait du proxying, car chaque connexion client peut impliquer une connexion upstream (parfois plus d’une).
  • Fait 8 : L’épuisement des ports éphémères est souvent mal diagnostiqué comme un épuisement des FD ; les deux donnent des échecs de connexion, mais ce sont des réglages différents.

Une idée paraphrasée d’une voix SRE notable : La fiabilité vient de l’apprentissage, pas du blâme — alors instrumentez le système et vérifiez vos hypothèses.

Tâches pratiques : commandes, sorties attendues et décisions (12+)

Ce sont des tâches « faites ça maintenant ». Chacune indique ce que la sortie signifie et la décision suivante. Exécutez en root ou avec sudo quand il faut.

Task 1: Confirm the error in Nginx logs

cr0x@server:~$ sudo tail -n 50 /var/log/nginx/error.log
2025/12/30 02:14:01 [crit] 2143#2143: *98123 accept4() failed (24: Too many open files)
2025/12/30 02:14:01 [alert] 2143#2143: open() "/var/log/nginx/access.log" failed (24: Too many open files)

Signification : Nginx atteint un plafond de FD par processus. accept4() en échec signifie qu’il ne peut pas accepter de nouvelles connexions clients. L’échec d’ouverture des logs est aussi un signal d’alerte : il est déjà en difficulté.

Décision : Vérifiez immédiatement les limites des processus Nginx et l’utilisation actuelle des FD avant de modifier quoi que ce soit.

Task 2: Check systemd journal for correlated messages

cr0x@server:~$ sudo journalctl -u nginx --since "30 minutes ago" | tail -n 30
Dec 30 02:14:01 server nginx[2143]: 2025/12/30 02:14:01 [crit] 2143#2143: *98123 accept4() failed (24: Too many open files)
Dec 30 02:14:02 server systemd[1]: nginx.service: Reloading.
Dec 30 02:14:03 server nginx[2143]: 2025/12/30 02:14:03 [alert] 2143#2143: open() "/var/log/nginx/access.log" failed (24: Too many open files)

Signification : Confirme que c’est Nginx et pas une rumeur côté client. Montre aussi si des redémarrages/reloads ont eu lieu pendant l’incident.

Décision : Si des reloads arrivent automatiquement (boucle de gestion de configuration), vous amplifiez peut‑être le problème. Pensez à suspendre l’automatisation jusqu’à correction des limites.

Task 3: Find the Nginx master PID

cr0x@server:~$ pidof nginx
2143 2144 2145 2146

Signification : Typiquement le premier PID est le master, les autres sont des workers (dépend des options de compilation).

Décision : Vérifiez lequel est le master et lesquels sont workers ; il faut inspecter les deux.

Task 4: Verify master and worker roles

cr0x@server:~$ ps -o pid,ppid,user,cmd -C nginx
  PID  PPID USER     CMD
 2143     1 root     nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
 2144  2143 www-data nginx: worker process
 2145  2143 www-data nginx: worker process
 2146  2143 www-data nginx: worker process

Signification : Le master est root, les workers sont www-data. Les limites peuvent différer si quelque chose d’étrange se passe, mais normalement ils héritent du master.

Décision : Inspectez les limites sur le master et les workers — ne présumez pas de l’héritage.

Task 5: Check current FD limits from /proc

cr0x@server:~$ sudo cat /proc/2143/limits | grep -i "open files"
Max open files            1024                 1024                 files

Signification : C’est l’élément révélateur : 1024 n’est pas une limite sérieuse en production pour un reverse proxy Nginx.

Décision : Corrigez LimitNOFILE dans systemd. Les ajustements ulimit en shell ne changeront rien.

Task 6: Count open file descriptors for a worker

cr0x@server:~$ sudo ls -1 /proc/2144/fd | wc -l
1007

Signification : Le worker est proche de la limite. Vous n’imaginez pas les choses.

Décision : Si l’utilisation est proche de la limite, augmentez-la. Si elle est loin, vous poursuivez peut‑être la mauvaise piste (rare, mais à vérifier).

Task 7: Identify what types of FDs are open (sockets vs files)

cr0x@server:~$ sudo lsof -p 2144 | awk '{print $5}' | sort | uniq -c | sort -nr | head
  812 IPv4
  171 IPv6
   12 REG
    5 FIFO

Signification : Principalement des sockets (IPv4/IPv6). C’est de la concurrence de connexions due à la charge, pas un gonflement statique de handles de fichiers.

Décision : Concentrez-vous sur les limites par worker, le comportement keepalive et la réutilisation des connexions upstream — pas uniquement « servir moins de fichiers ».

Task 8: Check Nginx config for worker limits

cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n 'worker_(processes|connections|rlimit_nofile)' | head -n 30
12:worker_processes auto;
18:worker_connections 768;

Signification : Aucun worker_rlimit_nofile défini, et worker_connections est modeste. Avec keepalive et du proxying, 768 peut toujours mettre sous pression, mais le problème majeur est la limite OS à 1024.

Décision : Alignez LimitNOFILE et worker_rlimit_nofile sur une cible réaliste. Ensuite réévaluez worker_connections en fonction de la concurrence attendue.

Task 9: Check current systemd limits applied to the nginx service

cr0x@server:~$ sudo systemctl show nginx -p LimitNOFILE
LimitNOFILE=1024

Signification : systemd impose 1024 pour le service. Ça annule vos espoirs et vos rêves.

Décision : Ajoutez un override systemd pour nginx avec une valeur LimitNOFILE plus élevée.

Task 10: Check kernel global file descriptor stats

cr0x@server:~$ cat /proc/sys/fs/file-max
9223372036854775807
cr0x@server:~$ cat /proc/sys/fs/file-nr
3648	0	9223372036854775807

Signification : Sur les Ubuntu modernes, file-max peut être effectivement « très élevé ». file-nr montre les handles alloués (~3648). Ce système est loin d’une exhaustion globale des FD.

Décision : Ne touchez pas encore à fs.file-max. Votre goulot est par-service/par-processus.

Task 11: Check fs.nr_open (per-process hard cap)

cr0x@server:~$ cat /proc/sys/fs/nr_open
1048576

Signification : Vous pouvez définir des limites par processus jusqu’à 1 048 576. Large marge.

Décision : Choisissez un nombre raisonnable (souvent 65535 ou 131072) plutôt que « infini ».

Task 12: Check how many connections Nginx is actually handling

cr0x@server:~$ sudo ss -s
Total: 2389 (kernel 0)
TCP:   1920 (estab 1440, closed 312, orphaned 0, timewait 311)

Signification : Si les connexions établies sont nombreuses, l’utilisation des FD sera élevée. TIME_WAIT n’est pas le même type de FD, mais indique les schémas de trafic et le comportement keepalive.

Décision : Si les connexions établies corrèlent avec l’erreur, augmenter les limites FD est pertinent. Si les établies sont faibles mais les FD élevés, suspectez des fuites ou des caches.

Task 13: Validate the active Nginx service unit file path and drop-ins

cr0x@server:~$ sudo systemctl status nginx | sed -n '1,12p'
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/nginx.service.d
             └─ override.conf
     Active: active (running) since Tue 2025-12-30 01:58:12 UTC; 16min ago

Signification : Montre d’où la unit est chargée et si des drop-ins existent. Si vous ne voyez pas de répertoire drop-in, vous n’avez encore rien surchargé.

Décision : Préférez un drop-in dans /etc/systemd/system/nginx.service.d/. Ne modifiez pas les units fournisseur dans /usr/lib.

Task 14: After changes, confirm the new limit is in effect

cr0x@server:~$ sudo systemctl show nginx -p LimitNOFILE
LimitNOFILE=65535
cr0x@server:~$ sudo cat /proc/$(pidof nginx | awk '{print $1}')/limits | grep -i "open files"
Max open files            65535                65535                files

Signification : systemd accorde maintenant 65535 et le processus master en cours d’exécution l’a. C’est l’étape de vérification que les gens sautent, puis s’étonnent après.

Décision : Si les valeurs ne correspondent pas, vous n’avez pas redémarré correctement, votre override n’a pas chargé, ou une autre unité prévaut.

Corriger correctement : LimitNOFILE systemd pour Nginx

Ubuntu 24.04 utilise systemd. Nginx tourne probablement comme nginx.service. La bonne approche est un override d’unité (drop-in), pas d’éditer les fichiers d’unité fournisseur et pas de compter sur limits.conf.

Choisir un nombre sensé

Valeurs de production courantes :

  • 65535 : classique, largement utilisé, généralement suffisant pour un Nginx single-node faisant du proxying normal.
  • 131072 : pour très haute concurrence ou charges multi-upstream.
  • 262144+ : rare ; justifié seulement avec des mesures et une raison (et habituellement d’autres goulots apparaissent avant).

Ne la définissez pas à « un million » juste parce que vous pouvez. Une grosse limite FD peut masquer une fuite plus longtemps et augmente l’impact si quelque chose part en vrille.

Créer un override systemd (drop-in)

cr0x@server:~$ sudo systemctl edit nginx
# (an editor opens)

Ajoutez ceci :

cr0x@server:~$ cat /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65535

Décision : Si vous exécutez aussi Nginx en conteneur ou via une unité différente (par ex. un wrapper personnalisé), appliquez l’override au bon nom d’unité, pas à celui que vous souhaiteriez utiliser.

Reload systemd et redémarrer Nginx

cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart nginx
cr0x@server:~$ sudo systemctl is-active nginx
active

Signification : daemon-reload force systemd à relire les units. Un restart est nécessaire pour que les nouvelles limites prennent effet ; les reloads n’appliquent pas de façon fiable les limites de ressources à un processus déjà lancé.

Vérifier, ne pas supposer

cr0x@server:~$ sudo systemctl show nginx -p LimitNOFILE
LimitNOFILE=65535

Si cela affiche la bonne valeur mais que /proc/<pid>/limits ne le montre pas, vous regardez probablement un PID ancien (le service n’a pas redémarré) ou Nginx est lancé par autre chose.

Quid de DefaultLimitNOFILE ?

systemd peut définir des limites par défaut pour tous les services via /etc/systemd/system.conf (et user.conf pour les services utilisateur). C’est tentant en entreprise car « ça corrige tout ». Cela change aussi tout.

Mon avis : pour Nginx, utilisez un override par service à moins d’avoir une baseline mature et de connaître l’impact sur chaque démon. Mettre un grand défaut peut permettre à d’autres processus d’ouvrir d’énormes nombres de fichiers, ce qui n’est pas automatiquement une bonne chose.

Corriger les limites côté Nginx : worker_rlimit_nofile, worker_connections, keepalive

Systemd peut accorder 65k FD à Nginx, mais Nginx doit encore les utiliser intelligemment.

worker_rlimit_nofile : aligner Nginx sur la limite OS

Ajoutez dans le contexte principal (top-level) de /etc/nginx/nginx.conf :

cr0x@server:~$ sudo grep -n 'worker_rlimit_nofile' /etc/nginx/nginx.conf || true
cr0x@server:~$ sudoedit /etc/nginx/nginx.conf
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Exemple de snippet à ajouter :

cr0x@server:~$ sudo awk 'NR==1,NR==30{print}' /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 8192;
}

Signification : worker_rlimit_nofile fixe RLIMIT_NOFILE pour les processus workers (et parfois pour le master, selon la compilation/comportement). Si vous ne le définissez pas, Nginx peut quand même tourner avec la limite fournie par le système, mais une aligment explicite évite les surprises.

Décision : Mettez worker_rlimit_nofile à la même valeur (ou légèrement inférieure) que LimitNOFILE de systemd. Si vous le fixez au‑dessus de ce que le système permet, Nginx ne dépassera pas la limite OS.

worker_connections : ce n’est pas « combien d’utilisateurs », c’est combien de sockets

Dans le bloc events, worker_connections définit le nombre maximal de connexions simultanées par worker. En gros :

  • Connexions clients max ≈ worker_processes * worker_connections
  • Mais le proxy inverse double souvent l’usage de sockets : une socket client + une socket upstream.
  • Plus l’overhead : sockets d’écoute, logs, pipes internes, sockets du resolver, etc.

Si vous mettez worker_connections à 50k et que la limite FD est à 65k, vous faites des maths avec l’enthousiasme d’un enfant et la précision d’un brouillard.

Keepalive et proxying : le multiplicateur sournois de FD

Le keepalive est génial — jusqu’à un certain point. Avec le keepalive, clients et upstream gardent des sockets ouvertes plus longtemps. Cela améliore la latence et réduit le coût des handshakes, mais augmente l’usage steady-state des FD. Sous trafic bursty, cela peut transformer « un pic » en « 30 secondes de pression soutenue sur les descripteurs ».

Points à vérifier :

  • keepalive_timeout et keepalive_requests (côté client)
  • proxy_http_version 1.1 et proxy_set_header Connection "" (correction du keepalive upstream)
  • keepalive dans les blocs upstream (pools de connexions)

Blague n°2 : Le keepalive, c’est comme laisser la salle de réunion « réservée » parce que vous pourriez l’utiliser plus tard — à la fin, personne ne peut travailler, mais l’agenda est magnifique.

Recharger en sécurité

cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx
cr0x@server:~$ sudo systemctl status nginx | sed -n '1,10p'
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/nginx.service.d
             └─ override.conf
     Active: active (running) since Tue 2025-12-30 02:20:11 UTC; 2min ago

Signification : Reload applique les changements de config sans couper les connexions (dans la plupart des cas), mais le changement de limite FD nécessitait un redémarrage plus tôt. Vous itérez maintenant sur les réglages Nginx.

Limites du noyau et système : fs.file-max, fs.nr_open, ports éphémères

La plupart des incidents « Too many open files » sur Nginx sont dus à des limites par processus. Mais vous devez comprendre les leviers système globaux parce que ce sont les prochains modes de panne à l’échelle.

Capacité système globale de descripteurs

Indicateurs clés :

  • /proc/sys/fs/file-max : maximum global de fichiers ouverts (peut être énorme sur systèmes modernes).
  • /proc/sys/fs/file-nr : alloué, inutilisé, maximum. Une allocation qui monte rapidement peut signaler des pics ou des fuites à l’échelle du système.
  • fs.nr_open : valeur maximale pour les limites par processus.

Si l’allocation globale est proche du maximum, augmenter la limite par processus de Nginx n’aidera pas. Vous déplacerez la panne vers d’autres services, et le noyau commencera à refuser des allocations de façons plus créatives.

Ports éphémères : l’autre « trop plein »

Quand Nginx est reverse proxy, chaque connexion upstream utilise un port éphémère local. Si vous surchargez un petit ensemble d’upstreams avec beaucoup de connexions courtes, vous pouvez épuiser les ports éphémères ou rester bloqué en TIME_WAIT. Les symptômes peuvent ressembler à une pression sur les FD : échecs de connexion, timeouts upstream, augmentation des 499/502/504.

Vérifications rapides :

cr0x@server:~$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768	60999
cr0x@server:~$ ss -tan state time-wait | wc -l
311

Signification : La plage éphémère par défaut est ~28k ports. Le compte TIME_WAIT indique combien de connexions récemment fermées subsistent. Tout TIME_WAIT n’est pas mauvais, mais des comptes élevés avec un fort churn peuvent nuire.

Décision : Si la pression sur les ports est réelle, corrigez-la par la réutilisation des connexions (keepalive upstream), la répartition de charge et parfois l’ajustement de la plage de ports — pas seulement en gonflant les limites FD.

Faut‑il toucher /etc/security/limits.conf ?

Pour les services systemd : généralement non. Ce fichier s’applique via PAM aux sessions utilisateur (SSH, login, etc.). Nginx lancé par systemd ne le lit pas. Vous pouvez le définir pour cohérence, mais n’espérez pas que cela résolve la panne.

Vérification de sanity dans un shell interactif :

cr0x@server:~$ ulimit -n
1024

Signification : C’est la limite de votre shell, pas celle de Nginx. Utile seulement pour confirmer ce que vous pouvez faire, pas ce que le service peut faire.

Décision : Ne « corrigez » pas la production en collant ulimit -n 65535 dans un shell et en vous sentant accompli.

Planification de capacité : de combien de FD avez-vous réellement besoin ?

La planification de capacité pour les descripteurs de fichiers est ennuyeuse. C’est pour cela que ça marche.

Comptabilité de règle empiriques

Commencez par ce modèle approximatif :

  • Par connexion client active : ~1 FD (la socket client)
  • Par requête proxifiée : souvent +1 FD (socket upstream), parfois plus avec des retries/multiples upstreams
  • Par baseline worker : quelques dizaines de FD (logs, eventfd, pipes, sockets d’écoute partagés, etc.)

Si vous servez seulement du contenu statique et utilisez sendfile, vous ouvrez encore des handles de fichiers, mais ils peuvent être de courte durée. Si vous activez un cache de fichiers, ils peuvent être long-terme.

Calculer une cible pratique

Exemple : vous attendez jusqu’à 20 000 connexions clients concurrentes sur une machine, et vous proxifiez vers des upstreams avec keepalive. Supposez prudemment 2 FD par client au pic (client + upstream) : 40 000. Ajoutez l’overhead : disons 2 000. Maintenant divisez par workers ? Pas exactement — les connexions se distribuent, mais l’irrégularité existe. Avec 8 workers, un worker peut temporairement avoir plus que la moyenne selon l’accept et l’ordonnancement.

C’est pourquoi vous vous donnez de la marge. Un LimitNOFILE de 65535 par processus est une zone d’équilibre courante : assez grand pour éviter des pannes stupides, assez petit pour que les fuites apparaissent avant la fin des temps.

Mesurez, ne devinez pas

Après avoir fixé les limites, mesurez en continu. Pour un worker actif :

cr0x@server:~$ sudo bash -c 'for p in $(pgrep -u www-data nginx); do echo -n "$p "; ls /proc/$p/fd | wc -l; done'
2144 1842
2145 1760
2146 1791

Signification : Comptes FD par worker sous charge. Si ces nombres tournent près de votre limite, vous avez besoin soit de limites plus élevées, soit de moins de sockets longue durée (tuning keepalive/upstream), soit de plus de capacité (plus d’instances).

Décision : Si les comptes sont stables et confortablement en dessous des limites, arrêtez d’ajuster. Déployez le correctif et passez à autre chose. L’ingénierie n’est pas un sport où vous gagnez en changeant plus de boutons.

Trois mini-récits des tranchées en entreprise

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

L’équipe avait migré d’une vieille version d’Ubuntu vers Ubuntu 24.04 dans le cadre d’un rafraîchissement de baseline sécurité. La config Nginx n’avait pas changé. Le profil de trafic n’avait pas changé. Pourtant, en quelques jours, ils ont eu des échecs de connexion sporadiques lors des pics prévisibles.

L’ingénieur on-call a fait ce que tout Linuxien a fait au moins une fois : s’est connecté en SSH, a exécuté ulimit -n 65535, a redémarré Nginx à la main, a vu le problème disparaître, et est allé se coucher. Le lendemain, c’était revenu. Ils « le corrigeaient » encore et encore. C’est devenu un rituel.

La mauvaise hypothèse était subtile : ils croyaient que le ulimit de leur shell s’appliquait au service après redémarrage. Ce n’était pas le cas. Nginx était démarré par systemd lors de redémarrages non supervisés et revenait toujours à LimitNOFILE=1024. La correction manuelle ne fonctionnait que quand Nginx était lancé depuis un shell qui portait la limite élevée.

La correction durable a pris dix minutes : un drop-in systemd avec LimitNOFILE, puis vérification via /proc/<pid>/limits. Le vrai gain a été social : ils ont noté l’étape de vérification dans le template d’incident. Les futurs on-call ont arrêté de considérer « ulimit » comme une incantation.

Mini-récit 2 : L’optimisation qui a mal tourné

Un ingénieur axé performance voulait réduire la latence upstream. Il a activé des réglages keepalive agressifs côté client et upstream. Le changement était excellent en test synthétique : moins de handshakes, p95 en baisse, tableaux de bord heureux.

Puis la production a fait son travail. Le service avait beaucoup de clients à queue longue (réseaux mobiles, proxys d’entreprise) qui maintenaient les connexions ouvertes comme s’ils payaient un loyer. Avec le keepalive poussé, Nginx a gardé bien plus de sockets concurrentes qu’avant. Les descripteurs restaient alloués plus longtemps, et le système a dérivé vers la limite par processus pendant les heures de pointe.

Pire : l’équipe avait aussi activé un open_file_cache plus large pour les assets statiques sur les mêmes nœuds, ajoutant plus de handles de fichiers longue durée. L’effet combiné a fait apparaître l’erreur dans des endroits étranges — parfois pendant un deploy (rotation des logs), parfois lors de pics de trafic.

La correction n’était pas « désactiver le keepalive », car cela aurait réintroduit un coût de latence. La correction a été la supervision adulte : augmenter proprement LimitNOFILE, plafonner worker_connections à un nombre défendable, et ajuster les timeouts keepalive pour coller au comportement réel des clients. Ils ont aussi séparé les rôles : les nœuds orientés statique ont des réglages cache différents des nœuds orientés proxy. Les optimisations fonctionnent ; elles ont juste besoin d’un budget.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une autre organisation avait une runbook simple : chaque fois qu’ils changeaient des réglages de concurrence Nginx, ils enregistraient trois nombres dans le ticket de changement : LimitNOFILE, worker_rlimit_nofile et le pic observé de /proc/<pid>/fd. Pas glamour. Pas « innovant ». Efficace.

Un soir, une dépendance upstream a commencé à timeouter de façon intermittente. Les connexions clients se sont accumulées pendant que Nginx attendait des réponses upstream. La concurrence a monté. C’est normal lors d’un ralentissement upstream : des files se forment au niveau du proxy.

Mais le proxy n’est pas tombé. Pourquoi ? Parce que leurs limites FD avaient de la marge, et ils avaient une surveillance qui alertait à 70 % de la limite FD par worker. L’on-call a vu l’alerte, reconnu que c’était une rétention de sockets induite par l’upstream, et a escaladé vers l’équipe de dépendance tout en limitant le débit d’un endpoint bruyant. Pas d’échec en cascade. Pas de « Nginx mort ».

Le compte rendu post-incident était presque ennuyeux, ce qui est le plus grand compliment qu’on puisse faire à une pratique opérationnelle.

Erreurs courantes : symptôme → cause racine → correctif

1) Symptôme : Vous mettez ulimit à 65535 mais Nginx logge encore EMFILE

Cause racine : Nginx est géré par systemd ; les limites du shell ne s’appliquent pas. systemd a toujours LimitNOFILE=1024.

Fix : Ajoutez un drop-in systemd pour nginx.service avec LimitNOFILE=65535, redémarrez, vérifiez via /proc/<pid>/limits.

2) Symptôme : systemctl show indique un LimitNOFILE élevé, mais /proc montre bas

Cause racine : Vous avez changé l’unité mais n’avez pas redémarré le service ; ou vous inspectez le mauvais PID ; ou Nginx est lancé par une autre unité/wrapper.

Fix : systemctl daemon-reload, puis systemctl restart nginx. Re-vérifiez le PID master et lisez les limites depuis ce PID.

3) Symptôme : Augmenter LimitNOFILE a aidé un jour, puis les erreurs sont revenues

Cause racine : Une fuite ou une augmentation progressive de la concurrence due à un upstream lent ou au comportement client. Vous avez traité le symptôme, pas la tendance.

Fix : Suivez l’usage FD au fil du temps par worker. Corrélez avec la latence upstream et les connexions actives. Corrigez keepalive, timeouts, pooling upstream ou la dépendance upstream.

4) Symptôme : Erreurs seulement pendant la rotation des logs ou les reloads

Cause racine : Quand vous êtes près de la limite FD, des actions bénignes comme rouvrir les logs demandent des descripteurs libres. Vous n’avez pas de marge.

Fix : Augmentez les limites et assurez-vous que le compte FD normal reste sous ~70–80 % de la limite au pic.

5) Symptôme : Nginx accepte des connexions mais le proxying upstream échoue

Cause racine : Vous avez peut-être assez de FD pour les clients mais pas pour les sockets upstream, ou les ports éphémères sont contraints.

Fix : Augmentez la limite FD et assurez-vous que le keepalive upstream fonctionne. Vérifiez la plage de ports éphémères et le churn TIME_WAIT ; favorisez la réutilisation des connexions.

6) Symptôme : Un seul worker atteint la limite et fond

Cause racine : Distribution inégale des connexions, peut‑être à cause des réglages accept mutex, du pinning CPU ou des schémas de trafic.

Fix : Assurez-vous que worker_processes auto correspond au CPU, revoyez les réglages d’accept et vérifiez la distribution de charge. Souvent le vrai correctif est simplement plus de marge + s’assurer que l’upstream ne force pas des maintiens longs.

7) Symptôme : Vous augmentez massivement worker_connections et tout empire

Cause racine : Vous avez augmenté la concurrence théorique sans garantir mémoire, capacité upstream et limites FD correspondantes. Vous avez invité un effet boule de neige plus grand.

Fix : Dimensionnez worker_connections à ce que le système peut soutenir. Utilisez du rate limiting, du queueing ou la montée en charge plutôt qu’une concurrence infinie.

Listes de vérification / plan étape par étape

Étape par étape : chemin de correction sûr en production

  1. Capturer des preuves : tail des logs d’erreurs Nginx et du journal pour EMFILE.
  2. Trouver les PIDs : identifier master et workers.
  3. Lire les limites en live : /proc/<pid>/limits pour le master et un worker.
  4. Mesurer l’utilisation : compter /proc/<pid>/fd pour les workers ; échantillonner sous charge.
  5. Vérifier la limite systemd : systemctl show nginx -p LimitNOFILE.
  6. Implémenter override systemd : systemctl edit nginxLimitNOFILE=65535.
  7. Redémarrer (pas reload) : appliquer les limites avec restart.
  8. Vérifier à nouveau : systemd montre la nouvelle limite, /proc montre la nouvelle limite.
  9. Aligner la config Nginx : définir worker_rlimit_nofile, ajuster worker_connections selon la concurrence mesurée.
  10. Recharger la config Nginx : nginx -t puis systemctl reload nginx.
  11. Surveiller les régressions : surveiller les FD ouverts par worker ; alerter à 70–80 % de la limite.
  12. Investiger les causes racines : si l’usage FD augmente dans le temps, chassez les fuites ou la lenteur upstream, ne relevez pas juste les chiffres encore et encore.

Checklist : à quoi ressemble un « bon » après correction

  • systemctl show nginx -p LimitNOFILE renvoie votre valeur cible.
  • /proc/<masterpid>/limits correspond.
  • Les comptes /proc/<pid>/fd des workers ont de la marge au pic.
  • Les logs d’erreurs Nginx n’affichent plus accept4() failed (24: Too many open files).
  • Les déploiements/reloads et rotations de logs ne provoquent plus d’échecs de connexion.

Checklist : quand augmenter les limites est la mauvaise solution

  • Le compte FD monte de façon monotone sur des heures avec trafic constant (pattern fuite).
  • L’upstream est lent et les connexions s’accumulent ; il faut des timeouts, du backpressure, ou de la montée en charge.
  • Les échecs correspondent à un grand nombre de TIME_WAIT et une petite plage de ports (épuisement de ports).
  • La mémoire est déjà sous pression ; augmenter la concurrence aggravera latence et échecs.

FAQ (questions réelles posées à 02:00)

1) Pourquoi /etc/security/limits.conf ne corrige-t-il pas Nginx sur Ubuntu 24.04 ?

Parce que Nginx tourne comme service systemd. Les limites PAM s’appliquent aux sessions de login. systemd applique ses propres limites depuis les fichiers d’unité et les valeurs par défaut.

2) Dois‑je définir à la fois LimitNOFILE et worker_rlimit_nofile ?

Oui, en pratique. LimitNOFILE est le plafond imposé par l’OS pour le service. worker_rlimit_nofile fait que Nginx demande/propague explicitement une limite correspondante pour les workers. Alignez‑les pour éviter l’ambiguïté « ça devrait aller ».

3) Quelle est une valeur raisonnable pour LimitNOFILE pour Nginx ?

Courant : 65535. Les proxies à fort trafic peuvent utiliser 131072. Montez plus haut seulement si les mesures l’exigent et avec de la surveillance, car cela peut masquer des fuites et augmenter l’impact d’un incident.

4) Si j’augmente la limite, Nginx gérera-t-il automatiquement plus de connexions ?

Pas automatiquement. Vous avez aussi besoin d’un worker_connections approprié, CPU, mémoire, capacité upstream et parfois des réglages noyau. Plus de concurrence sans capacité n’est qu’une file d’attente plus grande pour l’échec.

5) J’ai relevé les limites mais je vois encore parfois « Too many open files ». Pourquoi ?

Soit vous n’avez pas appliqué la limite au processus en cours (vérifiez /proc), soit vous atteignez légitimement la nouvelle limite lors des pics, soit un autre composant (upstream, resolver, logging) consomme des descripteurs de manière inattendue. Mesurez les FD par worker et corrélez avec trafic et latence.

6) « Too many open files » peut‑il être causé par un bug ou une fuite ?

Oui. Si l’usage des FD augmente continuellement sans croissance du trafic, suspectez des fuites ou une mauvaise configuration qui conserve des descripteurs (keepalive trop agressif, caching, connexions upstream bloquées).

7) Est‑il sûr de changer LimitNOFILE sans downtime ?

Changer la limite demande de redémarrer le service, ce qui peut causer une brève interruption sauf si vous avez plusieurs instances derrière un load balancer. Planifiez un restart en rolling si possible.

8) Comment alerter avant que ce soit une panne ?

Alertez sur l’usage des FD en pourcentage de la limite par worker (ex. 70 % warning, 85 % critique). Vous pouvez le calculer depuis les comptes /proc/<pid>/fd et /proc/<pid>/limits.

9) Et si la limite système file-max est le goulot ?

Alors vous êtes en situation « tout l’hôte manque de descripteurs ». Identifiez les plus gros consommateurs par process. Augmenter la limite Nginx n’aidera pas si le noyau ne peut pas allouer davantage. C’est rare sur les defaults Ubuntu modernes mais possible sur des nœuds multi‑tenant denses.

10) HTTP/2 réduit‑il l’usage des FD grâce au multiplexing ?

Parfois, mais ne comptez pas uniquement dessus. HTTP/2 peut réduire le nombre de connexions TCP client, mais le comportement upstream, les pools keepalive et les sidecars peuvent toujours dominer l’usage des FD.

Conclusion : prochaines étapes durables

Quand Nginx sur Ubuntu 24.04 atteint « Too many open files », la bonne correction n’est pas une commande héroïque. C’est une chaîne vérifiée : systemd accorde la limite, Nginx est configuré pour l’utiliser en sécurité, et le noyau a suffisamment de capacité globale.

Faites cela ensuite, dans cet ordre :

  1. Vérifiez la limite live via /proc/<pid>/limits. Si c’est 1024, arrêtez tout et corrigez systemd LimitNOFILE.
  2. Posez un drop-in systemd pour nginx.service et redémarrez. Vérifiez encore.
  3. Alignez Nginx avec worker_rlimit_nofile et définissez worker_connections selon la concurrence mesurée, pas selon l’impression.
  4. Mesurez les comptes FD par worker au pic. Ajoutez des alertes sur la marge. Si l’usage FD monte dans le temps, enquêtez sur des fuites ou la lenteur upstream.

Après cela, vous aurez le luxe rare d’un serveur web qui tombe pour des raisons intéressantes, pas parce qu’il a manqué de poignées numérotées.

← Précédent
ZFS pour MySQL : éviter les effondrements de latence lors des rafales d’écriture
Suivant →
Proxmox pveproxy.service en échec : 7 causes courantes et l’ordre de correction

Laisser un commentaire