Gestion des ressources système avec la commande `ulimit` sur Linux
La commande `ulimit` est un utilitaire shell intégré aux systèmes Unix et Linux qui applique des limites de ressources par processus et par utilisateur, empêchant tout processus ou utilisateur unique d’épuiser les ressources système telles que le temps CPU, la mémoire, les descripteurs de fichiers ouverts et le nombre de processus. Elle fonctionne au niveau du noyau via l’appel système `setrlimit()`, ce qui en fait l’un des mécanismes les plus directs et les moins coûteux disponibles pour les administrateurs système en matière de gouvernance des ressources.
Pour tout serveur exécutant des charges de travail en production — qu’il s’agisse d’une application web à fort trafic, d’un moteur de base de données ou d’une pile de microservices conteneurisés — des paramètres `ulimit` mal configurés ou absents constituent l’une des principales causes de défaillances en cascade, de processus incontrôlés et de pannes système complètes. Configurer correctement ces limites n’est pas facultatif ; c’est une hygiène d’infrastructure fondamentale.
Fonctionnement interne de `ulimit`
Lorsqu’un processus shell appelle `ulimit`, il invoque les appels système `getrlimit()` et `setrlimit()` définis dans la norme POSIX. Chaque limite est représentée par une paire de valeurs : une limite logicielle et une limite matérielle. Ces valeurs sont stockées par processus dans le descripteur de processus du noyau et sont héritées par les processus enfants au moment de `fork()`.
Ce modèle d’héritage est essentiel à comprendre. Si vous définissez des valeurs `ulimit` dans une session shell, chaque processus engendré par ce shell — y compris les démons lancés via des scripts init — hérite de ces limites. À l’inverse, les limites définies dans `/etc/security/limits.conf` s’appliquent au moment de la connexion PAM, et non à l’exécution, ce qui signifie qu’elles ne prennent effet que pour les nouvelles sessions de connexion, et non pour les services déjà en cours d’exécution.
Limites logicielles vs. limites matérielles
| Propriété | Limite logicielle | Limite matérielle |
|---|---|---|
| — | — | — |
| Qui peut l’augmenter | Tout utilisateur non privilégié (jusqu’à la limite matérielle) | Uniquement root (`CAP_SYS_RESOURCE`) |
| Qui peut la réduire | Tout utilisateur | Tout utilisateur (irréversible sans root) |
| Application | Appliquée par le noyau | Sert de plafond pour la limite logicielle |
| Cas d’utilisation typique | Limite opérationnelle quotidienne | Maximum absolu pour la politique de sécurité |
| Indicateur dans `ulimit` | `-S` | `-H` |
Une erreur opérationnelle courante consiste à définir la limite matérielle égale à la limite logicielle. Cela supprime toute flexibilité pour qu’un processus puisse temporairement augmenter ses propres limites, ce que certaines applications (comme certaines implémentations JVM et moteurs de bases de données) font légitimement au démarrage.
Référence complète : indicateurs de ressources `ulimit`
| Indicateur | Ressource | Unité | Valeur courante en production |
|---|---|---|---|
| — | — | — | — |
| `-t` | Temps CPU | Secondes | `unlimited` pour les démons |
| `-f` | Taille maximale de fichier | Blocs de 512 octets | `unlimited` ou limite spécifique |
| `-d` | Taille du segment de données (tas) | KB | `unlimited` pour les applications Java |
| `-s` | Taille de la pile | KB | `8192` (par défaut) |
| `-c` | Taille du fichier core dump | Blocs de 512 octets | `0` (désactivé en production) |
| `-m` | Taille maximale de l’ensemble résident | KB | Rarement appliqué (utiliser cgroups) |
| `-v` | Mémoire virtuelle (espace d’adressage) | KB | `unlimited` pour la plupart des services |
| `-n` | Descripteurs de fichiers ouverts | Nombre | `65536` ou plus pour les serveurs très sollicités |
| `-u` | Nombre maximal de processus utilisateur | Nombre | `4096`–`65536` selon le rôle |
| `-l` | Mémoire verrouillée (mlock) | KB | Élevé pour Redis, Elasticsearch |
| `-i` | Signaux en attente | Nombre | La valeur système par défaut est généralement suffisante |
| `-q` | Octets de file de messages POSIX | Octets | Valeur système par défaut |
| `-r` | Priorité d’ordonnancement en temps réel | Priorité | `0` sauf pour les charges de travail RT |
| `-e` | Priorité d’ordonnancement maximale (nice) | Valeur nice | Valeur système par défaut |
Utilisation pratique de `ulimit` avec contexte réel
Affichage des limites actuelles
“`bash
ulimit -a # All soft limits for the current shell
ulimit -aH # All hard limits for the current shell
“`
Pour inspecter les limites d’un processus en cours d’exécution spécifique (PID), lisez directement depuis le système de fichiers proc — c’est la source faisant autorité et cela contourne les rapports au niveau du shell :
“`bash
cat /proc/<PID>/limits
“`
Cela est inestimable lors du dépannage d’un service démarré par systemd ou un script init, où `ulimit -a` au niveau du shell ne reflètera pas les limites réelles du processus.
Définition des limites logicielles et matérielles
“`bash
Set soft limit for open file descriptors
ulimit -Sn 65536
Set hard limit for open file descriptors
ulimit -Hn 131072
Set both simultaneously (soft = hard = value)
ulimit -n 65536
“`
Désactivation des core dumps en production
“`bash
ulimit -c 0
“`
Les core dumps peuvent consommer des gigaoctets d’espace disque en quelques secondes lorsqu’un processus à forte mémoire plante. Les désactiver en production est une pratique standard, sauf si vous déboguez activement. Pour les environnements de développement, définissez un chemin dédié en utilisant `sysctl kernel.core_pattern` avec une limite de core non nulle.
Restriction du temps CPU pour les processus non fiables
“`bash
ulimit -t 30
“`
Cela envoie `SIGXCPU` au processus lorsqu’il atteint la limite logicielle de temps CPU, et `SIGKILL` à la limite matérielle. Cela est particulièrement utile dans les environnements d’hébergement partagé ou lors de l’exécution de scripts soumis par des utilisateurs.
Augmentation de la limite de descripteurs de fichiers ouverts pour les services à haute concurrence
Nginx, HAProxy, PostgreSQL et Redis nécessitent tous un grand nombre de descripteurs de fichiers ouverts sous charge. La valeur système par défaut de 1024 est dangereusement basse pour la production :
“`bash
ulimit -n 65536
“`
Cependant, cela n’affecte que la session shell actuelle. Pour une configuration persistante, utilisez les méthodes décrites dans la section suivante.
Rendre les paramètres `ulimit` persistants
Méthode 1 : `/etc/security/limits.conf`
Il s’agit de l’approche PAM standard pour les limites persistantes au niveau utilisateur :
“`
/etc/security/limits.conf
<domain> <type> <item> <value>
- soft nofile 65536
- hard nofile 131072
nginx soft nproc 4096
nginx hard nproc 8192
postgres soft nofile 65536
postgres hard nofile 65536
postgres soft memlock unlimited
postgres hard memlock unlimited
“`
Le caractère générique `*` s’applique à tous les utilisateurs mais ne s’applique pas à root. Root nécessite une entrée explicite :
“`
root soft nofile 65536
root hard nofile 131072
“`
Assurez-vous que le module PAM est chargé. Vérifiez que `/etc/pam.d/common-session` (Debian/Ubuntu) ou `/etc/pam.d/system-auth` (RHEL/CentOS) contient :
“`
session required pam_limits.so
“`
Méthode 2 : fichiers drop-in `/etc/security/limits.d/`
Pour une gestion plus propre, notamment dans les systèmes de gestion de configuration comme Ansible ou Puppet, placez des fichiers de limites spécifiques aux services dans le répertoire drop-in :
“`bash
/etc/security/limits.d/99-nginx.conf
nginx soft nofile 65536
nginx hard nofile 131072
“`
Les fichiers de ce répertoire sont traités après `limits.conf` et le remplacent, ce qui les rend idéaux pour le réglage spécifique aux applications sans modifier la configuration de base.
Méthode 3 : unités de service systemd (la norme moderne)
Pour les services gérés par systemd — ce qui représente la majorité des distributions Linux modernes — `limits.conf` n’est pas appliqué par défaut. systemd gère ses propres limites de ressources par unité de service :
“`ini
/etc/systemd/system/nginx.service.d/limits.conf
[Service]
LimitNOFILE=65536
LimitNPROC=4096
LimitCORE=0
LimitMEMLOCK=infinity
“`
Après modification, rechargez et redémarrez :
“`bash
systemctl daemon-reload
systemctl restart nginx
“`
Vérifiez les limites appliquées :
“`bash
cat /proc/$(systemctl show -p MainPID nginx | cut -d= -f2)/limits
“`
Il s’agit de la méthode la plus fiable pour les services en production et devrait être l’approche par défaut sur tout système exécutant systemd (Ubuntu 16.04+, CentOS 7+, Debian 8+).
Méthode 4 : fichiers de profil shell
Pour les limites de session utilisateur qui s’appliquent de manière interactive, ajoutez des commandes `ulimit` à `/etc/profile` (système global) ou `~/.bashrc` / `~/.profile` (par utilisateur). Cette approche convient aux postes de travail des développeurs mais ne convient pas aux processus démons.
Profils de configuration `ulimit` basés sur les rôles
Différents rôles de serveur nécessitent des profils de limites de ressources fondamentalement différents. Appliquer des valeurs par défaut génériques à tous les types de serveurs est une source courante de défaillances subtiles et difficiles à diagnostiquer.
Serveur web (Nginx / Apache)
“`
nofile: 65536–131072 # High concurrency requires many open sockets + files
nproc: 4096 # Worker processes + threads
core: 0 # Disable core dumps in production
“`
Base de données relationnelle (PostgreSQL / MySQL)
“`
nofile: 65536 # Many concurrent connections = many file descriptors
memlock: unlimited # Required for shared memory and huge pages
nproc: 4096
stack: 8192 KB
core: 0
“`
Serveur d’applications Java (Tomcat / Spring Boot)
“`
nofile: 65536
nproc: 65536 # JVM thread-per-connection models spawn many threads
data: unlimited # JVM heap is allocated from the data segment
stack: 512 KB # Reduce stack size to fit more threads in memory
“`
Redis / Stockage de données en mémoire
“`
nofile: 65536
memlock: unlimited # Prevents swapping of memory-mapped data
“`
Pièges critiques et cas limites
La limite `nproc` compte les threads, pas seulement les processus. Sous Linux, les threads sont implémentés comme des processus légers (`clone()` avec mémoire partagée). Une application Java avec 500 threads compte pour 500 contre la limite `nproc`. Cela surprend de nombreux administrateurs qui définissent des valeurs `nproc` conservatrices et se demandent ensuite pourquoi leur JVM plante avec `OutOfMemoryError: unable to create new native thread`.
`ulimit -v` limite l’espace d’adressage virtuel, pas la RAM physique. De nombreux administrateurs définissent `-v` en pensant limiter l’utilisation de la mémoire. En réalité, ils limitent l’espace d’adressage virtuel, qui inclut les fichiers mappés en mémoire, les bibliothèques partagées et le métaspace JVM. Définir cette valeur trop basse provoquera des échecs `mmap()` et des erreurs d’application cryptiques.
`ulimit` ne s’applique pas rétroactivement. La modification des limites dans `limits.conf` ou un fichier d’unité systemd n’affecte pas les processus déjà en cours d’exécution. Vous devez redémarrer le service pour que les nouvelles limites prennent effet.
Les environnements de conteneurs contournent `ulimit` de manière inattendue. Dans Docker, les valeurs par défaut de `ulimit` sont définies au niveau du démon (`/etc/docker/daemon.json`) et peuvent être remplacées par conteneur avec `–ulimit`. Cependant, les limites du conteneur sont bornées par les limites du noyau hôte. Définir `nofile=1048576` dans un conteneur alors que l’hôte a `nofile=65536` reviendra silencieusement à la limite de l’hôte.
Le plafond système `nofile` est distinct des limites par processus. Le paramètre noyau `fs.file-max` (défini via `sysctl`) contrôle le nombre total de descripteurs de fichiers dans l’ensemble du système. Même si `nofile` par processus est défini à une valeur élevée, atteindre `fs.file-max` provoquera des erreurs `ENFILE` à l’échelle du système. Vérifiez et ajustez les deux :
“`bash
sysctl fs.file-max
sysctl -w fs.file-max=2097152
“`
`ulimit` vs. cgroups : choisir le bon outil
| Capacité | `ulimit` / `setrlimit` | cgroups v2 |
|---|---|---|
| — | — | — |
| Portée | Par processus (hérité par les enfants) | Par groupe de processus |
| Limitation de la mémoire | Espace d’adressage virtuel uniquement (`-v`) | Application réelle RSS + swap |
| Limitation du CPU | Budget de temps CPU (`-t`) | Contrôleur de bande passante CPU (% précis) |
| Limitation des E/S | Non pris en charge | Pondération et limites de débit des E/S bloc |
| Limitation réseau | Non pris en charge | Nécessite l’intégration tc + cgroup |
| Persistance | Via PAM ou systemd | Via les tranches systemd ou cgroupfs |
| Compatibilité avec les conteneurs | Limitée | Native (Docker, Kubernetes utilisent cgroups) |
| Granularité | Grossière | Fine |
`ulimit` reste le bon outil pour les limites rapides par session, les plafonds de descripteurs de fichiers et le contrôle des core dumps. Pour une isolation complète des ressources — notamment dans les environnements multi-locataires ou les charges de travail conteneurisées — cgroups v2 est le mécanisme supérieur. Sur un environnement VPS Hosting ou Serveur Dédié bien configuré, les deux mécanismes sont généralement utilisés en combinaison : `ulimit` pour les garde-fous par processus et cgroups pour les budgets de ressources agrégés.
Surveillance et validation des limites de ressources
Une surveillance proactive prévient les défaillances liées aux limites avant qu’elles ne deviennent des incidents en production.
Vérifier l’utilisation actuelle des descripteurs de fichiers à l’échelle du système :
“`bash
cat /proc/sys/fs/file-nr
Output: <allocated> <unused> <max>
“`
Trouver les processus approchant leur limite `nofile` :
“`bash
for pid in /proc/[0-9]*; do
pid_num=${pid##*/}
limit=$(awk '/Max open files/{print $4}' /proc/$pid_num/limits 2>/dev/null)
current=$(ls /proc/$pid_num/fd 2>/dev/null | wc -l)
[ -n "$limit" ] && [ "$limit" != "unlimited" ] &&
awk -v c=$current -v l=$limit -v p=$pid_num
'BEGIN{if(c/l>0.8) printf "PID %s: %d/%d (%.0f%%)n",p,c,l,c/l*100}'
done
“`
Outils pour la surveillance continue :
- `lsof -u <username>` — lister tous les fichiers ouverts pour un utilisateur
- `ss -s` — statistiques de socket (corrélées avec la pression `nofile`)
- `htop` avec vue arborescente des processus — visualiser le nombre de processus par utilisateur
- `sar -v` — utilisation historique des descripteurs de fichiers et des inodes via sysstat
- Prometheus `node_exporter` — expose les métriques `node_filefd_allocated` et `node_filefd_maximum` pour les alertes
Pour les environnements exécutant un VPS avec cPanel ou d’autres panneaux de contrôle, bon nombre de ces limites sont préconfigurées par l’installateur du panneau, mais elles nécessitent fréquemment une augmentation à mesure que le trafic croît. Vérifiez toujours les limites réelles via `/proc/<PID>/limits` plutôt que de faire confiance à la documentation du panneau.
Implications de sécurité de `ulimit`
Les limites de ressources constituent également un contrôle de sécurité. Sans elles, un processus compromis ou bogué peut exécuter une bombe fork (`:(){ :|:& };:`), épuisant tous les emplacements de processus disponibles et rendant le système non réactif. Une limite `nproc` conservatrice par utilisateur est la principale mesure d’atténuation :
“`
- hard nproc 4096
“`
De même, la désactivation des core dumps (`-c 0`) empêche que le contenu sensible de la mémoire — y compris les clés de chiffrement, les mots de passe et les jetons de session — ne soit écrit sur le disque dans un fichier lisible par tous.
Pour les environnements d’hébergement partagé ou tout serveur où plusieurs utilisateurs ont accès au shell, `ulimit` est une couche de sécurité obligatoire. Sur l’infrastructure d’Hébergement Web Partagé, ces limites sont généralement appliquées au niveau de la plateforme, mais les administrateurs gérant leur propre VPS multi-utilisateurs doivent les configurer explicitement.
Si votre serveur gère la terminaison SSL ou la gestion des certificats, assurez-vous que le processus gérant TLS (par exemple, Nginx, HAProxy) dispose de limites `nofile` suffisantes, car chaque connexion TLS nécessite plusieurs descripteurs de fichiers. Associez cela à des Certificats SSL correctement configurés pour éviter que les échecs liés aux certificats ne viennent aggraver les problèmes de ressources.
Pour les déploiements de serveurs de messagerie, Postfix et Dovecot sont particulièrement sensibles aux limites `nofile`, car chaque connexion e-mail simultanée et chaque accès à une boîte aux lettres consomme des descripteurs de fichiers. Si vous gérez votre propre infrastructure de messagerie plutôt que d’utiliser un Hébergement E-mail géré, régler `nofile` à au moins 65536 pour l’utilisateur mail est non négociable sur tout serveur modérément chargé.
Matrice de décision : quoi configurer et où
| Scénario | Méthode recommandée | Paramètres clés |
|---|---|---|
| — | — | — |
| Sessions utilisateur interactives | `/etc/security/limits.conf` | `nofile`, `nproc`, `core` |
| Service géré par systemd | Section `[Service]` de l’unité systemd | `LimitNOFILE`, `LimitNPROC`, `LimitCORE` |
| Conteneur Docker | Indicateur `–ulimit` ou `daemon.json` | `nofile`, `nproc` |
| Test shell ponctuel | Commande `ulimit` directement | Tout indicateur |
| Serveur partagé multi-locataires | `limits.conf` + application PAM | `nproc`, `nofile`, `fsize`, `cpu` |
| Pod Kubernetes | Contexte de sécurité du pod + cgroups | Géré par kubelet |
| Réglage spécifique à une application | Fichier drop-in `limits.d/` | Paramètres spécifiques au service |
Liste de contrôle des points clés techniques
- Vérifiez toujours les limites appliquées via `/proc/<PID>/limits`, et non via `ulimit -a` au niveau du shell, pour les services en cours d’exécution.
- Pour les services systemd, configurez les limites dans le fichier d’unité en utilisant les directives `Limit*` — `limits.conf` n’est pas lu par systemd par défaut.
- Définissez `nofile` à au minimum `65536` pour tout service gérant des connexions réseau ; `131072` ou plus pour les charges de travail à haute concurrence.
- Ne définissez jamais la limite matérielle égale à la limite logicielle, sauf si vous avez une exigence de sécurité spécifique — les applications ont besoin de marge pour s’auto-ajuster.
- Désactivez les core dumps (`LimitCORE=0`) en production ; activez-les avec un chemin contrôlé en préproduction.
- La limite `nproc` compte les threads sous Linux — tenez-en compte lors de la configuration des applications JVM ou Go runtime.
- Ajustez `fs.file-max` via `sysctl` en parallèle des limites `nofile` par processus pour éviter l’épuisement `ENFILE` à l’échelle du système.
- Dans les environnements conteneurisés, les limites du noyau hôte constituent le plafond absolu — les paramètres `ulimit` au niveau du conteneur ne peuvent pas les dépasser.
- Utilisez cgroups v2 pour l’application des limites de mémoire et d’E/S ; utilisez `ulimit` pour les plafonds de descripteurs de fichiers, les nombres de processus et le contrôle des core dumps.
- Après toute modification de limite dans `limits.conf` ou les fichiers d’unité systemd, redémarrez le service concerné et vérifiez avec `/proc/<PID>/limits`.
FAQ
`ulimit` s’applique-t-il aux processus root ?
Le caractère générique `*` dans `/etc/security/limits.conf` exclut explicitement root. Les processus root contournent également l’application des limites matérielles pour la plupart des types de ressources — root peut augmenter ses propres limites matérielles. Pour appliquer des limites à root, ajoutez une entrée explicite `root` dans `limits.conf`, bien que de nombreux services système s’exécutant en tant que root ignorent les limites appliquées par PAM s’ils sont démarrés en dehors d’une session de connexion.
Pourquoi ma modification de `limits.conf` n’a-t-elle aucun effet sur un service en cours d’exécution ?
`limits.conf` est appliqué par PAM au moment de la connexion. Les services démarrés par systemd, SysVinit ou Upstart ne passent pas par PAM et n’héritent donc pas des paramètres `limits.conf`. Configurez les limites directement dans le fichier d’unité systemd en utilisant `LimitNOFILE` et les directives associées, puis exécutez `systemctl daemon-reload && systemctl restart <service>`.
Quelle est la valeur maximale que je peux définir pour `nofile` ?
Le maximum par processus est borné par le paramètre noyau `fs.nr_open` (par défaut : 1 048 576 sur la plupart des noyaux). Le total à l’échelle du système est borné par `fs.file-max`. Vous pouvez augmenter `fs.nr_open` via `sysctl`, mais les valeurs supérieures à 1 048 576 nécessitent une recompilation du noyau sur les noyaux plus anciens. En pratique, 524 288 ou 1 048 576 couvre pratiquement tous les cas d’utilisation en production.
Comment vérifier si un processus a atteint sa limite `ulimit` ?
Consultez le journal du noyau avec `dmesg | grep -i "ulimit|RLIMIT|too many open|cannot allocate"`. Les journaux d’application afficheront généralement `EMFILE` (trop de fichiers ouverts), `ENOMEM` (échec d’allocation de mémoire) ou `EAGAIN` (ressource temporairement indisponible). Croisez avec `/proc/<PID>/limits` et le nombre actuel de descripteurs via `ls /proc/<PID>/fd | wc -l`.
`ulimit` est-il suffisant pour l’isolation des ressources dans un environnement multi-locataires ?
Non. `ulimit` fournit des garde-fous par processus et par utilisateur, mais n’applique pas de limites de bande passante mémoire, d’E/S disque ou de débit réseau. Pour une véritable isolation multi-locataires, combinez `ulimit` avec les contrôleurs de ressources cgroups v2, et envisagez l’isolation par espace de noms (espaces de noms utilisateur, espaces de noms PID) pour des frontières de sécurité plus solides. Sur une infrastructure gérée, ces contrôles sont généralement superposés au niveau de l’hyperviseur et du runtime de conteneur.
