Gestión de recursos del sistema con el comando `ulimit` en Linux
El comando `ulimit` es una utilidad de shell integrada en sistemas Unix y Linux que aplica límites de recursos por proceso y por usuario, evitando que un solo proceso o usuario agote los recursos del sistema, como el tiempo de CPU, la memoria, los descriptores de archivos abiertos y el recuento de procesos. Opera a nivel del kernel a través de la llamada al sistema `setrlimit()`, lo que lo convierte en uno de los mecanismos más directos y de bajo coste disponibles para los administradores de sistemas en materia de gestión de recursos.
Para cualquier servidor que ejecute cargas de trabajo en producción — ya sea una aplicación web de alto tráfico, un motor de base de datos o una pila de microservicios en contenedores — las configuraciones de `ulimit` incorrectas o ausentes son una causa principal de fallos en cascada, procesos descontrolados e interrupciones totales del sistema. Configurar correctamente estos límites no es opcional; es una higiene de infraestructura fundamental.
Cómo funciona `ulimit` internamente
Cuando un proceso de shell llama a `ulimit`, invoca las llamadas al sistema `getrlimit()` y `setrlimit()` definidas en el estándar POSIX. Cada límite se representa como un par de valores: un límite blando y un límite duro. Estos se almacenan por proceso en el descriptor de proceso del kernel y son heredados por los procesos hijos en el momento de `fork()`.
Este modelo de herencia es fundamental para comprender. Si establece valores de `ulimit` en una sesión de shell, todos los procesos generados desde ese shell — incluidos los demonios lanzados mediante scripts de inicio — heredan esos límites. Por el contrario, los límites establecidos en `/etc/security/limits.conf` se aplican en el momento del inicio de sesión PAM, no en tiempo de ejecución, lo que significa que solo tienen efecto para las nuevas sesiones de inicio de sesión, no para los servicios que ya están en ejecución.
Límites blandos vs. límites duros
| Propiedad | Límite blando | Límite duro |
|---|
| — | — | — |
|---|
| Quién puede aumentarlo | Cualquier usuario sin privilegios (hasta el límite duro) | Solo root (`CAP_SYS_RESOURCE`) |
|---|
| Quién puede reducirlo | Cualquier usuario | Cualquier usuario (irreversible sin root) |
|---|
| Aplicación | Aplicado por el kernel | Actúa como techo para el límite blando |
|---|
| Caso de uso típico | Límite operativo del día a día | Máximo absoluto para la política de seguridad |
|---|
| Indicador en `ulimit` | `-S` | `-H` |
|---|
Un error operativo común es establecer el límite duro igual al límite blando. Esto elimina toda flexibilidad para que un proceso aumente temporalmente sus propios límites, algo que algunas aplicaciones (como ciertas implementaciones de JVM y motores de bases de datos) hacen legítimamente durante el inicio.
Referencia completa: indicadores de recursos de `ulimit`
| Indicador | Recurso | Unidad | Valor común en producción |
|---|
| — | — | — | — |
|---|
| `-t` | Tiempo de CPU | Segundos | `unlimited` para demonios |
|---|
| `-f` | Tamaño máximo de archivo | Bloques de 512 bytes | `unlimited` o límite específico |
|---|
| `-d` | Tamaño del segmento de datos (heap) | KB | `unlimited` para aplicaciones Java |
|---|
| `-s` | Tamaño de la pila | KB | `8192` (predeterminado) |
|---|
| `-c` | Tamaño del archivo de volcado de núcleo | Bloques de 512 bytes | `0` (desactivado en producción) |
|---|
| `-m` | Tamaño máximo del conjunto residente | KB | Raramente aplicado (usar cgroups) |
|---|
| `-v` | Memoria virtual (espacio de direcciones) | KB | `unlimited` para la mayoría de los servicios |
|---|
| `-n` | Descriptores de archivos abiertos | Cantidad | `65536` o más para servidores con mucho tráfico |
|---|
| `-u` | Máximo de procesos de usuario | Cantidad | `4096`–`65536` según el rol |
|---|
| `-l` | Memoria bloqueada (mlock) | KB | Alto para Redis, Elasticsearch |
|---|
| `-i` | Señales pendientes | Cantidad | El valor predeterminado del sistema suele ser suficiente |
|---|
| `-q` | Bytes de cola de mensajes POSIX | Bytes | Valor predeterminado del sistema |
|---|
| `-r` | Prioridad de planificación en tiempo real | Prioridad | `0` salvo cargas de trabajo RT |
|---|
| `-e` | Prioridad máxima de planificación (nice) | Valor nice | Valor predeterminado del sistema |
|---|
Uso práctico de `ulimit` con contexto del mundo real
Visualización de los límites actuales
“`bash
ulimit -a # All soft limits for the current shell
ulimit -aH # All hard limits for the current shell
“`
Para inspeccionar los límites de un proceso en ejecución específico (PID), lea directamente desde el sistema de archivos proc — esta es la fuente autorizada y omite los informes a nivel de shell:
“`bash
cat /proc/<PID>/limits
“`
Esto es invaluable al solucionar problemas de un servicio iniciado por systemd o un script de inicio, donde `ulimit -a` a nivel de shell no reflejará los límites reales del proceso.
Establecimiento de límites blandos y duros
“`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
“`
Desactivación de volcados de núcleo en producción
“`bash
ulimit -c 0
“`
Los volcados de núcleo pueden consumir gigabytes de espacio en disco en segundos cuando falla un proceso con mucha memoria. Desactivarlos en producción es una práctica estándar a menos que esté depurando activamente. Para entornos de desarrollo, establezca una ruta dedicada usando `sysctl kernel.core_pattern` junto con un límite de núcleo distinto de cero.
Restricción del tiempo de CPU para procesos no confiables
“`bash
ulimit -t 30
“`
Esto envía `SIGXCPU` al proceso cuando alcanza el límite blando de tiempo de CPU, y `SIGKILL` en el límite duro. Esto es particularmente útil en entornos de alojamiento compartido o al ejecutar scripts enviados por usuarios.
Aumento del límite de descriptores de archivos abiertos para servicios de alta concurrencia
Nginx, HAProxy, PostgreSQL y Redis requieren un gran número de descriptores de archivos abiertos bajo carga. El valor predeterminado del sistema de 1024 es peligrosamente bajo para producción:
“`bash
ulimit -n 65536
“`
Sin embargo, esto solo afecta a la sesión de shell actual. Para una configuración persistente, utilice los métodos descritos en la siguiente sección.
Cómo hacer persistentes las configuraciones de `ulimit`
Método 1: `/etc/security/limits.conf`
Este es el enfoque estándar basado en PAM para límites persistentes a nivel de usuario:
“`
/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
“`
El comodín `*` se aplica a todos los usuarios pero no se aplica a root. Root requiere una entrada explícita:
“`
root soft nofile 65536
root hard nofile 131072
“`
Asegúrese de que el módulo PAM esté cargado. Verifique que `/etc/pam.d/common-session` (Debian/Ubuntu) o `/etc/pam.d/system-auth` (RHEL/CentOS) contenga:
“`
session required pam_limits.so
“`
Método 2: archivos adicionales de `/etc/security/limits.d/`
Para una gestión más ordenada, especialmente en sistemas de gestión de configuración como Ansible o Puppet, coloque archivos de límites específicos del servicio en el directorio adicional:
“`bash
/etc/security/limits.d/99-nginx.conf
nginx soft nofile 65536
nginx hard nofile 131072
“`
Los archivos en este directorio se procesan después de `limits.conf` y lo anulan, lo que los hace ideales para el ajuste específico de aplicaciones sin modificar la configuración base.
Método 3: unidades de servicio systemd (el estándar moderno)
Para los servicios gestionados por systemd — que es la mayoría de las distribuciones modernas de Linux — `limits.conf` no se aplica de forma predeterminada. systemd gestiona sus propios límites de recursos por unidad de servicio:
“`ini
/etc/systemd/system/nginx.service.d/limits.conf
[Service]
LimitNOFILE=65536
LimitNPROC=4096
LimitCORE=0
LimitMEMLOCK=infinity
“`
Después de editar, recargue y reinicie:
“`bash
systemctl daemon-reload
systemctl restart nginx
“`
Verifique los límites aplicados:
“`bash
cat /proc/$(systemctl show -p MainPID nginx | cut -d= -f2)/limits
“`
Este es el método más fiable para los servicios en producción y debería ser el enfoque predeterminado en cualquier sistema que ejecute systemd (Ubuntu 16.04+, CentOS 7+, Debian 8+).
Método 4: archivos de perfil de shell
Para los límites de sesión de usuario que se aplican de forma interactiva, añada comandos `ulimit` a `/etc/profile` (para todo el sistema) o `~/.bashrc` / `~/.profile` (por usuario). Este enfoque es adecuado para estaciones de trabajo de desarrolladores, pero no es apropiado para procesos de demonio.
Perfiles de configuración de `ulimit` basados en roles
Los diferentes roles de servidor requieren perfiles de límites de recursos fundamentalmente distintos. Aplicar valores predeterminados genéricos en todos los tipos de servidores es una fuente común de fallos sutiles y difíciles de diagnosticar.
Servidor 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 datos relacional (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
“`
Servidor de aplicaciones 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 / almacén de datos en memoria
“`
nofile: 65536
memlock: unlimited # Prevents swapping of memory-mapped data
“`
Errores críticos y casos límite
El límite `nproc` cuenta hilos, no solo procesos. En Linux, los hilos se implementan como procesos ligeros (`clone()` con memoria compartida). Una aplicación Java con 500 hilos cuenta como 500 contra el límite `nproc`. Esto sorprende a muchos administradores que establecen valores conservadores de `nproc` y luego se preguntan por qué su JVM falla con `OutOfMemoryError: unable to create new native thread`.
`ulimit -v` limita el espacio de direcciones virtuales, no la RAM física. Muchos administradores establecen `-v` pensando que están limitando el uso de memoria. En realidad, están limitando el espacio de direcciones virtuales, que incluye archivos mapeados en memoria, bibliotecas compartidas y el metaespacio de JVM. Establecer este valor demasiado bajo causará fallos de `mmap()` y errores crípticos en las aplicaciones.
`ulimit` no se aplica de forma retroactiva. Cambiar los límites en `limits.conf` o en un archivo de unidad systemd no afecta a los procesos que ya están en ejecución. Debe reiniciar el servicio para que los nuevos límites surtan efecto.
Los entornos de contenedores omiten `ulimit` de formas inesperadas. En Docker, los valores predeterminados de `ulimit` se establecen a nivel del demonio (`/etc/docker/daemon.json`) y pueden anularse por contenedor con `–ulimit`. Sin embargo, los límites del contenedor están acotados por los límites del kernel del host. Establecer `nofile=1048576` en un contenedor mientras el host tiene `nofile=65536` volverá silenciosamente al límite del host.
El techo del sistema `nofile` es independiente de los límites por proceso. El parámetro del kernel `fs.file-max` (establecido mediante `sysctl`) controla el número total de descriptores de archivos en todo el sistema. Incluso si `nofile` por proceso está configurado alto, alcanzar `fs.file-max` causará errores de `ENFILE` en todo el sistema. Verifique y ajuste ambos:
“`bash
sysctl fs.file-max
sysctl -w fs.file-max=2097152
“`
`ulimit` vs. cgroups: elegir la herramienta adecuada
| Capacidad | `ulimit` / `setrlimit` | cgroups v2 |
|---|
| — | — | — |
|---|
| Alcance | Por proceso (heredado por los hijos) | Por grupo de procesos |
|---|
| Limitación de memoria | Solo espacio de direcciones virtuales (`-v`) | Aplicación real de RSS + swap |
|---|
| Limitación de CPU | Presupuesto de tiempo de CPU (`-t`) | Controlador de ancho de banda de CPU (% preciso) |
|---|
| Limitación de E/S | No compatible | Peso de E/S de bloque y límites de velocidad |
|---|
| Limitación de red | No compatible | Requiere integración de tc + cgroup |
|---|
| Persistencia | Mediante PAM o systemd | Mediante segmentos systemd o cgroupfs |
|---|
| Compatibilidad con contenedores | Limitada | Nativa (Docker, Kubernetes usan cgroups) |
|---|
| Granularidad | Gruesa | Detallada |
|---|
`ulimit` sigue siendo la herramienta adecuada para límites rápidos por sesión, límites de descriptores de archivos y control de volcados de núcleo. Para un aislamiento de recursos completo — especialmente en entornos multiinquilino o cargas de trabajo en contenedores — cgroups v2 es el mecanismo superior. En un entorno de Alojamiento VPS o Servidor Dedicado bien configurado, ambos mecanismos se utilizan normalmente en combinación: `ulimit` para barreras por proceso y cgroups para presupuestos de recursos agregados.
Monitorización y validación de los límites de recursos
La monitorización proactiva evita que los fallos relacionados con los límites se conviertan en incidentes de producción.
Compruebe el uso actual de descriptores de archivos en todo el sistema:
“`bash
cat /proc/sys/fs/file-nr
Output: <allocated> <unused> <max>
“`
Encuentre procesos que se acercan a su límite `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
“`
Herramientas para la monitorización continua:
- `lsof -u <username>` — lista todos los archivos abiertos de un usuario
- `ss -s` — estadísticas de sockets (se correlaciona con la presión de `nofile`)
- `htop` con vista de árbol de procesos — visualiza el recuento de procesos por usuario
- `sar -v` — uso histórico de descriptores de archivos e inodos mediante sysstat
- `node_exporter` de Prometheus — expone métricas de `node_filefd_allocated` y `node_filefd_maximum` para alertas
Para entornos que ejecutan VPS con cPanel u otros paneles de control, muchos de estos límites están preconfigurados por el instalador del panel, pero con frecuencia necesitan ajustarse al alza a medida que crece el tráfico. Verifique siempre los límites reales con `/proc/<PID>/limits` en lugar de confiar en la documentación del panel.
Implicaciones de seguridad de `ulimit`
Los límites de recursos también son un control de seguridad. Sin ellos, un proceso comprometido o con errores puede ejecutar una bomba fork (`:(){ :|:& };:`), agotando todos los espacios de proceso disponibles y dejando el sistema sin respuesta. Un límite conservador de `nproc` por usuario es la mitigación principal:
“`
- hard nproc 4096
“`
Del mismo modo, deshabilitar los volcados de núcleo (`-c 0`) evita que el contenido sensible de la memoria — incluidas claves de cifrado, contraseñas y tokens de sesión — se escriba en disco en un archivo legible por todos.
Para entornos de alojamiento compartido o cualquier servidor donde múltiples usuarios tienen acceso al shell, `ulimit` es una capa de seguridad obligatoria. En la infraestructura de Alojamiento Web Compartido, estos límites suelen aplicarse a nivel de plataforma, pero los administradores que ejecutan su propio VPS multiusuario deben configurarlos explícitamente.
Si su servidor gestiona la terminación SSL o la gestión de certificados, asegúrese de que el proceso que maneja TLS (por ejemplo, Nginx, HAProxy) tenga límites de `nofile` suficientes, ya que cada conexión TLS requiere múltiples descriptores de archivos. Combínelo con Certificados SSL correctamente configurados para evitar que los fallos relacionados con certificados agraven los problemas de recursos.
Para implementaciones de servidores de correo, Postfix y Dovecot son especialmente sensibles a los límites de `nofile`, ya que cada conexión de correo electrónico simultánea y acceso a buzón consume descriptores de archivos. Si está ejecutando su propia infraestructura de correo en lugar de utilizar Alojamiento de Correo gestionado, ajustar `nofile` a al menos 65536 para el usuario de correo es innegociable en cualquier servidor con carga moderada.
Matriz de decisión: qué configurar y dónde
| Escenario | Método recomendado | Parámetros clave |
|---|
| — | — | — |
|---|
| Sesiones de usuario interactivas | `/etc/security/limits.conf` | `nofile`, `nproc`, `core` |
|---|
| Servicio gestionado por systemd | Sección `[Service]` de la unidad systemd | `LimitNOFILE`, `LimitNPROC`, `LimitCORE` |
|---|
| Contenedor Docker | Indicador `–ulimit` o `daemon.json` | `nofile`, `nproc` |
|---|
| Pruebas puntuales en shell | Comando `ulimit` directamente | Cualquier indicador |
|---|
| Servidor compartido multiinquilino | `limits.conf` + aplicación PAM | `nproc`, `nofile`, `fsize`, `cpu` |
|---|
| Pod de Kubernetes | Contexto de seguridad del pod + cgroups | Gestionado por kubelet |
|---|
| Ajuste específico de aplicación | Archivo adicional `limits.d/` | Parámetros específicos del servicio |
|---|
Lista de verificación de puntos clave técnicos
- Verifique siempre los límites aplicados mediante `/proc/<PID>/limits`, no mediante `ulimit -a` a nivel de shell, para los servicios en ejecución.
- Para los servicios systemd, configure los límites en el archivo de unidad usando directivas `Limit*` — `limits.conf` no es leído por systemd de forma predeterminada.
- Establezca `nofile` en un mínimo de `65536` para cualquier servicio que gestione conexiones de red; `131072` o más para cargas de trabajo de alta concurrencia.
- Nunca establezca el límite duro igual al límite blando a menos que tenga un requisito de seguridad específico — las aplicaciones necesitan margen para autoajustarse.
- Deshabilite los volcados de núcleo (`LimitCORE=0`) en producción; actívelos con una ruta controlada en entornos de prueba.
- El límite `nproc` cuenta hilos en Linux — téngalo en cuenta al configurar aplicaciones JVM o de tiempo de ejecución Go.
- Ajuste `fs.file-max` mediante `sysctl` junto con los límites de `nofile` por proceso para evitar el agotamiento de `ENFILE` en todo el sistema.
- En entornos en contenedores, los límites del kernel del host son el techo máximo — las configuraciones de `ulimit` a nivel de contenedor no pueden superarlos.
- Use cgroups v2 para la aplicación de memoria y E/S; use `ulimit` para límites de descriptores de archivos, recuentos de procesos y control de volcados de núcleo.
- Después de cualquier cambio de límite en `limits.conf` o en archivos de unidad systemd, reinicie el servicio afectado y verifique con `/proc/<PID>/limits`.
Preguntas frecuentes
¿Se aplica `ulimit` a los procesos root?
El comodín `*` en `/etc/security/limits.conf` excluye explícitamente a root. Los procesos root también omiten la aplicación de límites duros para la mayoría de los tipos de recursos — root puede aumentar sus propios límites duros. Para aplicar límites a root, añada una entrada explícita de `root` en `limits.conf`, aunque muchos servicios del sistema que se ejecutan como root ignorarán los límites aplicados por PAM si se inician fuera de una sesión de inicio de sesión.
¿Por qué mi cambio en `limits.conf` no tiene efecto en un servicio en ejecución?
`limits.conf` es aplicado por PAM en el momento del inicio de sesión. Los servicios iniciados por systemd, SysVinit o Upstart no pasan por PAM y, por lo tanto, no heredan la configuración de `limits.conf`. Configure los límites directamente en el archivo de unidad systemd usando `LimitNOFILE` y directivas relacionadas, luego ejecute `systemctl daemon-reload && systemctl restart <service>`.
¿Cuál es el valor máximo que puedo establecer para `nofile`?
El máximo por proceso está acotado por el parámetro del kernel `fs.nr_open` (predeterminado: 1.048.576 en la mayoría de los kernels). El total en todo el sistema está acotado por `fs.file-max`. Puede aumentar `fs.nr_open` mediante `sysctl`, pero los valores superiores a 1.048.576 requieren recompilación del kernel en kernels más antiguos. En la práctica, 524.288 o 1.048.576 cubre prácticamente todos los casos de uso en producción.
¿Cómo compruebo si un proceso ha alcanzado su límite de `ulimit`?
Compruebe el registro del kernel con `dmesg | grep -i "ulimit|RLIMIT|too many open|cannot allocate"`. Los registros de la aplicación normalmente mostrarán `EMFILE` (demasiados archivos abiertos), `ENOMEM` (fallo de asignación de memoria) o `EAGAIN` (recurso temporalmente no disponible). Contraste con `/proc/<PID>/limits` y el recuento actual de descriptores mediante `ls /proc/<PID>/fd | wc -l`.
¿Es `ulimit` suficiente para el aislamiento de recursos en un entorno multiinquilino?
No. `ulimit` proporciona barreras por proceso y por usuario, pero no aplica límites de ancho de banda de memoria, E/S de disco o rendimiento de red. Para un aislamiento multiinquilino real, combine `ulimit` con controladores de recursos cgroups v2, y considere el aislamiento de espacios de nombres (espacios de nombres de usuario, espacios de nombres PID) para límites de seguridad más estrictos. En infraestructura gestionada, estos controles suelen estar en capas a nivel del hipervisor y del tiempo de ejecución de contenedores.
