“Demasiados archivos abiertos” es uno de esos mensajes que parece un ajuste simple y se convierte en toda una tarde de pistas falsas. Ejecutas ulimit -n 1048576, imprime el número grande que pediste, reinicias el servicio y sigue fallando bajo carga. Tu terminal dice “arreglado”. Producción dice “buen intento”.
En Debian 13, la solución correcta suele no ser más magia en el shell. Es systemd. Específicamente: la capa correcta de systemd, la unidad correcta y un paso de verificación que no te engañe. Hagámoslo bien, como querrías a las 02:00 con clientes mirando.
Qué significa realmente “Demasiados archivos abiertos” (y qué no)
En Linux, “Demasiados archivos abiertos” suele ser uno de dos errores:
- EMFILE: el proceso alcanzó su límite de descriptores por proceso (RLIMIT_NOFILE). Este es el que arreglas con systemd
LimitNOFILE=(o equivalente). Es personal. Un proceso se pasó del presupuesto. - ENFILE: el sistema alcanzó un límite global de la tabla de archivos. Es más raro en kernels modernos, pero ocurre en casos patológicos. Es comunitario. Todo el host se pasó del presupuesto.
Además: “archivos abiertos” es una frase imprecisa. Significa descriptores de archivo. Esos descriptores pueden representar archivos regulares, directorios, sockets, tuberías, eventfds, signalfds, watches de inotify, instancias de epoll y un puñado de otras cosas que solo tienen sentido después de una semana larga.
Dos consecuencias:
- Puedes quedarte sin FDs sin que esté “abierto” ningún archivo de registro. Los servicios con muchas conexiones mueren así todo el tiempo.
- Elevar límites a ciegas puede ocultar fugas, enmascarar un manejo pobre de conexiones y mover la falla a otro subsistema (como memoria, contabilidad del kernel o tu backend de almacenamiento).
Una cita que aún vale en operaciones, parafraseada porque no apuesto a la palabra perfecta: paraphrased idea
— “La esperanza no es una estrategia.” (a menudo atribuida en círculos de ingeniería a practicantes como Gene Kranz, y repetida sin parar en trabajo de fiabilidad). En este contexto: no esperes que tu ulimit haya hecho algo. Pruébalo, y luego arréglalo en la capa que realmente inicia el servicio.
Broma #1: “Demasiados archivos abiertos” es la forma en que Linux te dice que tu proceso tiene problemas de compromiso. Sigue abriendo cosas y se niega a cerrarlas.
Guía rápida de diagnóstico
Esta es la sección de “deja de desplazarte y haz estas tres comprobaciones”. Está ordenada como yo lo hago cuando el pager está caliente.
Primero: identifica si es por proceso (EMFILE) o a nivel sistema (ENFILE)
- Busca el error exacto en los logs (logs de la aplicación, journal). EMFILE/ENFILE es decisivo.
- Si no puedes obtener el errno exacto, comprueba rápidamente el uso de FDs por proceso y RLIMIT (ver tareas abajo).
Segundo: verifica el RLIMIT_NOFILE real del servicio en ejecución (no el de tu shell)
- Obtén el PID del proceso principal del servicio.
- Lee
/proc/<pid>/limits. Ese archivo es la realidad.
Tercero: encuentra quién estableció el límite (unidad systemd, drop-in, valores por defecto, PAM)
- Si el proceso es iniciado por systemd, la unidad (y los valores por defecto de systemd) ganan.
- Si es un servicio de sesión de usuario, el gestor de usuario tiene sus propios valores por defecto.
- Los límites de PAM a menudo afectan inicios de sesión interactivos y algunos servicios, pero no a los servicios típicos del sistema.
Si haces solo una cosa hoy: deja de confiar en la salida de ulimit de tu shell de inicio de sesión como evidencia de que tu demonio tiene ese límite. Así es como terminas “arreglando” las cosas tres veces.
La pila de límites en Debian 13: quién gana y por qué
Linux tiene límites de recurso (rlimits). systemd puede establecerlos para servicios. PAM puede aplicarlos a sesiones de login. Los shells pueden establecerlos para procesos hijos. Los contenedores pueden añadir sus propios límites. Y el kernel tiene máximos a nivel sistema.
Aquí está la jerarquía práctica con la que la mayoría tropieza:
- Máximos del kernel y tablas a nivel sistema (p. ej.,
fs.file-max). Son fronteras duras y recursos compartidos. - Rlimits por proceso (RLIMIT_NOFILE). Cada proceso tiene un límite “soft” y uno “hard”. El límite soft es el que golpeas. El hard es el techo que puedes subir al soft hasta, sin privilegio.
- Configuraciones del gestor de servicios systemd establecen rlimits en tiempo de exec, y se aplican al árbol de procesos del servicio.
- Sesiones de usuario (PAM + gestor de usuario) pueden fijar rlimits para shells interactivos y servicios de usuario.
- Shell
ulimites solo la vista del shell y lo que pasa a sus hijos. No hace nada para demonios que ya están en ejecución.
La realidad clave en Debian 13: la mayoría de los demonios de servidor son iniciados por systemd. Eso significa que el archivo de unidad y los valores por defecto de systemd importan más que cualquier cosa que teclees en un shell.
Además, systemd no es solo un init con esteroides. Es un supervisor de procesos que puede establecer límites, aislar recursos y controlar descriptores de archivos de maneras que burlan tus suposiciones. Si tu modelo mental es “/etc/security/limits.conf controla todo”, perderás tiempo.
La corrección correcta en systemd: LimitNOFILE, valores por defecto y drop-ins
Hay tres formas sensatas de aumentar los límites de FD para un servicio systemd en Debian 13. Elige según el radio de impacto.
Opción A (recomendada): establece LimitNOFILE en un drop-in para el servicio específico
Este es el arreglo quirúrgico. Cambia solo una unidad. Sobrevive a las actualizaciones del paquete. Es revisable.
Crea un drop-in:
cr0x@server:~$ sudo systemctl edit nginx.service
Luego añade:
cr0x@server:~$ sudo tee /etc/systemd/system/nginx.service.d/limits.conf >/dev/null <<'EOF'
[Service]
LimitNOFILE=262144
EOF
Recarga y reinicia:
cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart nginx.service
cr0x@server:~$ systemctl status nginx.service --no-pager
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/nginx.service.d
└─limits.conf
Active: active (running)
Si no ves el Drop-In listado, tu drop-in está en el lugar equivocado o olvidaste daemon-reload.
Opción B: cambiar los valores por defecto a nivel sistema para todos los servicios (martillo grande)
Puedes establecer límites por defecto en /etc/systemd/system.conf y/o /etc/systemd/user.conf:
cr0x@server:~$ sudo sed -n '1,120p' /etc/systemd/system.conf
# This file is part of systemd.
#DefaultLimitNOFILE=1024:524288
Descomenta y establece, por ejemplo:
cr0x@server:~$ sudo perl -0777 -pe 's/^#?DefaultLimitNOFILE=.*/DefaultLimitNOFILE=65536:1048576/m' -i /etc/systemd/system.conf
Luego:
cr0x@server:~$ sudo systemctl daemon-reexec
Advertencia de opinión: La Opción B es cómo accidentalmente “arreglas” un servicio dando permiso a todos los servicios para dañar el host de nuevas maneras. Úsala solo cuando tengas una política de flota y una razón.
Opción C: configura los propios límites de la aplicación (solo si aplica)
Algunos demonios tienen sus propios interruptores de límite (por ejemplo mediante worker_rlimit_nofile en nginx) que deben alinearse con RLIMIT_NOFILE. Estos no reemplazan los límites de systemd; se superponen. Si systemd limita a 8192, la configuración de tu app puede pedir 100k todo el día y aun así obtener 8192.
Así que el orden correcto es: primero establece LimitNOFILE en systemd, luego ajusta los límites a nivel de aplicación para aprovecharlo.
¿Y /etc/security/limits.conf?
No es inútil, solo que con frecuencia es irrelevante para los servicios del sistema. /etc/security/limits.conf lo aplica PAM en contextos de inicio de sesión. Si inicias un demonio vía systemd al arrancar, PAM no forma parte de esa historia. Debian 13 no es especial aquí; es solo que Linux moderno es descaradamente multilayer.
Broma #2: ulimit es como gritar tu salario deseado por el pasillo. Se siente bien, pero la nómina hará lo que esté configurada para hacer.
Verificación que no miente: /proc, systemctl y procesos en vivo
Si adoptas un hábito operacional de este artículo, que sea este: verifica límites en el proceso en ejecución, no en el shell que casualmente está en tu pantalla.
Tres ángulos confiables:
- /proc/<pid>/limits muestra los límites soft/hard actuales para ese proceso.
- systemctl show puede mostrar propiedades de la unidad y configuraciones efectivas (pero aun así verifica en /proc).
- Contar FDs abiertos vía
/proc/<pid>/fdte dice si te acercas al límite, si hay fugas o picos durante la carga.
Y recuerda: los servicios a menudo tienen múltiples procesos (maestro + workers). El que falla puede no ser el que miraste. Verifica el proceso que realmente emite EMFILE.
Tareas prácticas (comandos + qué significa la salida + la decisión que tomas)
Estas son tareas de campo. Ejecútalas en Debian 13. Cada una incluye: comando, salida de ejemplo, interpretación y una decisión.
Task 1: Confirmar el código de error en el journal
cr0x@server:~$ sudo journalctl -u nginx.service -n 50 --no-pager
Dec 28 10:12:01 server nginx[1423]: accept4() failed (24: Too many open files)
Dec 28 10:12:01 server nginx[1423]: worker_connections are not enough
Significado: errno 24 es EMFILE. Eso es RLIMIT_NOFILE por proceso, no tablas globales del kernel.
Decisión: Enfócate en LimitNOFILE para los procesos de nginx y en los propios ajustes de worker de nginx. No toques fs.file-max todavía.
Task 2: Encontrar el MainPID del servicio
cr0x@server:~$ systemctl show -p MainPID --value nginx.service
1423
Significado: PID 1423 es el proceso principal que systemd rastrea.
Decisión: Usa este PID para la verificación inicial, pero comprueba también los PIDs de los workers.
Task 3: Leer el RLIMIT_NOFILE real desde /proc
cr0x@server:~$ sudo awk '/Max open files/ {print}' /proc/1423/limits
Max open files 8192 8192 files
Significado: Soft=8192, Hard=8192. Chocarás con EMFILE alrededor de ~8192 descriptores en ese proceso.
Decisión: Súbelo con systemd (drop-in). Un ulimit en el shell no cambiará este proceso en ejecución.
Task 4: Confirmar lo que systemd piensa que es el límite de la unidad
cr0x@server:~$ systemctl show nginx.service -p LimitNOFILE
LimitNOFILE=8192
Significado: systemd actualmente aplica 8192 al servicio.
Decisión: Añade un drop-in con un LimitNOFILE mayor y luego reinicia el servicio.
Task 5: Crear y verificar un override drop-in
cr0x@server:~$ sudo systemctl edit nginx.service
cr0x@server:~$ sudo systemctl cat nginx.service
# /lib/systemd/system/nginx.service
[Unit]
Description=A high performance web server and a reverse proxy server
# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=262144
Significado: El drop-in está cargado y sobreescribe la unidad.
Decisión: Recarga y reinicia. Si el drop-in no aparece en systemctl cat, no está en efecto.
Task 6: Reiniciar y volver a comprobar los límites en /proc
cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart nginx.service
cr0x@server:~$ systemctl show -p MainPID --value nginx.service
1777
cr0x@server:~$ sudo awk '/Max open files/ {print}' /proc/1777/limits
Max open files 262144 262144 files
Significado: El proceso en ejecución ahora tiene el límite aumentado. Esa es la condición de victoria.
Decisión: Pasa a medir uso/presión: ¿sigues teniendo fugas de FDs, o el problema solo era un tope demasiado bajo?
Task 7: Contar FDs abiertos actuales para un proceso
cr0x@server:~$ sudo ls /proc/1777/fd | wc -l
412
Significado: 412 descriptores abiertos ahora mismo. Es una instantánea.
Decisión: Si esto sube constantemente sin bajar, sospecha una fuga. Si hace picos con el tráfico, ajusta la concurrencia máxima.
Task 8: Identificar qué descriptores predominan
cr0x@server:~$ sudo ls -l /proc/1777/fd | head -n 12
lrwx------ 1 www-data www-data 64 Dec 28 10:20 0 -> /dev/null
lrwx------ 1 www-data www-data 64 Dec 28 10:20 1 -> /var/log/nginx/access.log
lrwx------ 1 www-data www-data 64 Dec 28 10:20 2 -> /var/log/nginx/error.log
lrwx------ 1 www-data www-data 64 Dec 28 10:20 3 -> socket:[23451]
lrwx------ 1 www-data www-data 64 Dec 28 10:20 4 -> socket:[23452]
Significado: Muchos sockets implica carga de conexiones. Muchos archivos regulares podrían implicar manejo de logs/IO o fugas en operaciones de archivo.
Decisión: Si predominan los sockets, ajusta el manejo de conexiones (backlog, keepalive, número de workers). Si predominan archivos, busca fugas en el cierre de archivos o el comportamiento de rotación.
Task 9: Comprobar límites por usuario para sesiones interactivas (PAM)
cr0x@server:~$ ulimit -Sn
1024
cr0x@server:~$ ulimit -Hn
1048576
Significado: Tu shell de login tiene soft 1024, hard 1048576. Eso no se aplica automáticamente a los servicios de systemd.
Decisión: Si los desarrolladores ejecutan cargas manualmente (no vía systemd), ajusta límites de PAM. Si no, céntrate en límites a nivel de unidad.
Task 10: Comprobar presión de descriptores a nivel sistema
cr0x@server:~$ cat /proc/sys/fs/file-nr
2976 0 9223372036854775807
Significado: El primer número son manejadores de archivos asignados; el tercero es el máximo. En muchos sistemas el máximo es efectivamente enorme (o “casi infinito”).
Decisión: Si lo asignado está cerca del máximo (en sistemas con un tope real), tienes un problema a nivel host. De lo contrario, probablemente sea por proceso.
Task 11: Inspeccionar el máximo de open files por proceso del kernel (fs.nr_open)
cr0x@server:~$ cat /proc/sys/fs/nr_open
1048576
Significado: Este es el techo del kernel para RLIMIT_NOFILE. No puedes fijar límites por proceso por encima de esto.
Decisión: Si realmente necesitas > 1,048,576 FDs por proceso (raro, pero no imposible para cajas proxy), debes aumentar fs.nr_open vía sysctl y garantizar margen de memoria.
Task 12: Verificar los límites efectivos de la unidad después del reinicio
cr0x@server:~$ systemctl show nginx.service -p LimitNOFILE
LimitNOFILE=262144
Significado: systemd cree que aplica el nuevo límite.
Decisión: Si systemctl show indica el nuevo valor pero /proc/<pid>/limits no, no reiniciaste la cosa correcta, estás mirando el PID equivocado o el servicio usa un wrapper que ejecuta en otro PID. Sigue la cadena de PIDs.
Task 13: Encontrar todos los PIDs en el cgroup de la unidad y comprobar un worker
cr0x@server:~$ systemctl show -p ControlGroup --value nginx.service
/system.slice/nginx.service
cr0x@server:~$ cat /sys/fs/cgroup/system.slice/nginx.service/cgroup.procs | head
1777
1780
1781
1782
cr0x@server:~$ sudo awk '/Max open files/ {print}' /proc/1780/limits
Max open files 262144 262144 files
Significado: Los workers heredaron el mismo límite. Bien.
Decisión: Si los workers difieren, puede haber rutas de arranque mezcladas o wrappers de exec. Arregla la ruta de inicio del servicio.
Task 14: Capturar el crecimiento de FDs con el tiempo (chequeo pobre de fugas)
cr0x@server:~$ for i in 1 2 3 4 5; do date; sudo ls /proc/1777/fd | wc -l; sleep 5; done
Sun Dec 28 10:24:01 UTC 2025
412
Sun Dec 28 10:24:06 UTC 2025
418
Sun Dec 28 10:24:11 UTC 2025
430
Sun Dec 28 10:24:16 UTC 2025
446
Sun Dec 28 10:24:21 UTC 2025
462
Significado: El recuento está tendiendo al alza. Podría ser carga legítima en aumento o una fuga.
Decisión: Si el tráfico es estable pero los FDs crecen de forma monótona, investiga fugas (sockets upstream atascados, archivos no cerrados, bibliotecas con bugs). Subir límites solo compra tiempo.
Task 15: Confirmar que el servicio no está constreñido por un wrapper como su/sudo o un cron
cr0x@server:~$ ps -o pid,ppid,cmd -p 1777
PID PPID CMD
1777 1 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
Significado: PPID 1 indica systemd (o init) como padre. Esto es un servicio del sistema, por lo que aplican los límites de systemd.
Decisión: Si PPID es un shell, cron o un wrapper, arregla el método de arranque o asegura que ese wrapper fije rlimits correctamente (usualmente: deja de hacer eso, usa una unidad).
Tres micro-historias corporativas desde el terreno
Micro-historia 1: el incidente causado por una suposición equivocada
Una empresa SaaS mediana ejecutaba una capa API basada en Debian detrás de un proxy reverso. Durante un evento de marketing, las solicitudes empezaron a fallar con 502 esporádicos. El ingeniero on-call hizo lo de siempre: SSH, ejecutó ulimit -n, vio un número grande (lo habían ajustado meses antes) y concluyó “no es un límite de FD”. Buscaron en CPU y redes en su lugar.
El outage se alargó porque el síntoma era inconsistente. Solo algunos nodos fallaban. Solo durante el pico. Y los logs estaban ruidosos: resets de conexión, timeouts upstream, la sopa caótica habitual. La línea clave —accept4() failed (24: Too many open files)— estaba presente, pero ahogada entre otros mensajes. Cuando finalmente se enfocaron, miraron /proc/<pid>/limits para los workers del proxy y encontraron un RLIMIT diminuto comparado con el shell.
La suposición errónea fue simple: “Si mi shell tiene 200k, el servicio también.” Esa suposición fue cierta años atrás en algunas configuraciones donde los demonios se lanzaban por scripts en entornos parecidos a login. Bajo systemd, es mayormente falsa.
La solución fue un drop-in de unidad: LimitNOFILE=131072, seguido por un reinicio en oleadas. El postmortem del incidente no trató del número de FD; trató de disciplina de verificación. Su nueva regla fue brutalmente práctica: ningún incidente se cierra hasta que alguien pega /proc/<pid>/limits en el ticket.
Micro-historia 2: la optimización que salió mal
Un equipo de servicios financieros ejecutaba un gateway de mensajes con reutilización agresiva de conexiones. Alguien notó que aumentar el límite de FD “arreglaba” el EMFILE ocasional y decidió ir a lo grande. Subieron LimitNOFILE de decenas de miles a casi el máximo del kernel, lo desplegaron y declararon victoria.
En días, el host empezó a comportarse de forma extraña ante ráfagas. No era EMFILE—ahora era presión de memoria y picos de latencia esporádicos. Resultó que con el nuevo colchón, el gateway mantenía muchas más sockets vivos durante la lentitud upstream. Eso incrementó el uso de memoria del kernel para buffers de socket y estructuras de contabilidad. Su cuello de botella real no era “faltan FDs”, era “no aplicamos reducción de carga cuando upstream está lento”.
La optimización salió mal porque eliminó un fusible de seguridad. El tope de FDs forzaba una especie de retropresión accidental. Quitarlo sin añadir retropresión explícita movió el modo de falla a algo peor: latencia en la cola y reintentos en cascada.
Terminaron estableciendo un límite de FD moderado, más límites estrictos en pools de conexiones y timeouts. La lección no fue “no subas límites”. Fue “sube límites solo después de hacer explícito tu comportamiento de concurrencia”. Los límites son barandillas. Si las quitas, instala mejores barandillas primero.
Micro-historia 3: la práctica aburrida pero correcta que salvó el día
Un gran equipo de plataforma interna ejecutaba sistemas Debian con una mezcla de servicios del sistema y servicios a nivel usuario. Tenían una costumbre que algunos llamaban “paranoica”: cada cambio relacionado con rendimiento requería un fragmento de verificación en la solicitud de cambio. Para el ajuste de FD, ese fragmento siempre incluía (1) propiedades de la unidad systemd, (2) límites en /proc para el Main PID y un worker, y (3) un conteo de FDs antes/después durante una prueba de carga.
Una tarde, un despliegue rutinario introdujo una fuga sutil de FDs en una biblioteca cliente gRPC. No fue catastrófico de inmediato; tomó horas. Su monitorización captó la tendencia: FDs abiertos subiendo linealmente. Como su checklist de despliegue incluía “chequeo de tendencia de FDs”, lo notaron temprano en el canario.
Revirtieron antes de que la fuga alcanzara el hard cap. Sin outage. Sin puente de incidentes. Solo un ingeniero levemente molesto y un gráfico limpio.
No pasó nada heroico. Ese es el punto. Las prácticas aburridas salvan más sistemas que las ingeniosas. La verificación vence a las sensaciones.
Errores comunes: síntoma → causa raíz → solución
1) “Aumenté ulimit, pero el servicio sigue con Too many open files”
Síntoma: ulimit -n en tu shell se ve alto; los logs del demonio muestran EMFILE.
Causa raíz: El demonio es iniciado por systemd con un LimitNOFILE más bajo (o el valor por defecto).
Solución: Añade un drop-in de systemd con LimitNOFILE=, recarga, reinicia y verifica vía /proc/<pid>/limits.
2) “systemctl show dice LimitNOFILE es alto, pero /proc sigue mostrando bajo”
Síntoma: systemctl show -p LimitNOFILE imprime tu nuevo valor; el PID en ejecución no cambió y sigue limitado.
Causa raíz: No reiniciaste el servicio, o estás revisando el proceso equivocado (worker vs master), o el servicio hace fork/exec a otro PID que no inspeccionaste.
Solución: Reinicia la unidad, obtén el nuevo MainPID, inspecciona los PIDs del cgroup, comprueba /proc/<pid>/limits para el proceso que falla.
3) “Después de subir límites, el host se vuelve inestable bajo carga”
Síntoma: No más EMFILE, pero picos de latencia, presión de memoria o backlog de red crece.
Causa raíz: Límites de FD más altos permitieron mayor concurrencia sin retropresión. Buffers de socket y uso de memoria del kernel subieron.
Solución: Añade controles explícitos de concurrencia (pools de conexión, límites de workers, timeouts). Fija límites de FD acorde al diseño, no al ego.
4) “Solo fallan procesos ejecutados por usuarios; los servicios del sistema están bien”
Síntoma: Herramientas interactivas (jobs por lotes, scripts de dev) hacen EMFILE; demonios del sistema no.
Causa raíz: Los límites de PAM y las sesiones de usuario tienen valores por defecto bajos; las unidades systemd del sistema están afinadas por separado.
Solución: Ajusta límites de PAM (/etc/security/limits.conf o drop-ins en /etc/security/limits.d) y/o los valores por defecto del gestor de usuario en /etc/systemd/user.conf. Verifica en una sesión nueva.
5) “Pusimos LimitNOFILE a 2 millones y systemd se negó”
Síntoma: El servicio no arranca, o el límite se reduce a un número menor.
Causa raíz: El kernel fs.nr_open es más bajo. RLIMIT_NOFILE no puede excederlo.
Solución: Sube fs.nr_open vía sysctl (con precaución), luego fija un límite sensato. También valida margen de memoria.
6) “Falla solo después de la rotación de logs”
Síntoma: Tras la rotación, el recuento de FDs crece; eventualmente aparece EMFILE.
Causa raíz: La aplicación mantiene abiertos descriptores de archivos de logs antiguos (o un proceso auxiliar lo hace). No es una fuga clásica, pero lo parece.
Solución: Asegura reabrir logs correctamente (p. ej., HUP donde corresponda), o usa stdout/stderr hacia journald cuando sea posible. Verifica los destinos de FD en /proc/<pid>/fd.
Listas de verificación / plan paso a paso
Checklist A: arreglar un servicio systemd único limpiamente
- Confirma EMFILE vs ENFILE en logs/journal.
- Obtén el MainPID de la unidad:
systemctl show -p MainPID --value your.service. - Lee
/proc/<pid>/limits, registra el “Max open files” actual. - Crea un drop-in:
systemctl edit your.servicey fijaLimitNOFILE=. systemctl daemon-reload, luego reinicia el servicio.- Vuelve a comprobar
/proc/<pid>/limitsen el nuevo PID. - Cuenta los FDs e identifica qué son (
/proc/<pid>/fd). - Si el uso de FDs crece en carga estable, trátalo como una fuga hasta demostrar lo contrario.
Checklist B: decidir el número correcto (en vez de “hacerlo enorme”)
- Estima la concurrencia máxima: conexiones, archivos, tuberías, watchers.
- Añade margen, pero mantén una reserva de seguridad para comportamientos desbocados (no pongas “infinito”).
- Asegura que los techos del kernel (
fs.nr_open) estén por encima de tu objetivo. - Considera el impacto en memoria de muchos sockets y buffers. Los FDs no son gratis.
- Prueba con carga y observa: recuento de FDs, memoria, latencia, tasa de errores.
Checklist C: política de flota (si debes tocar valores por defecto)
- Documenta por qué necesitas un cambio por defecto y qué servicios se benefician.
- Fija
DefaultLimitNOFILEde forma conservadora. Usa overrides por servicio para excepciones. - Despliega gradualmente y monitoriza nuevos modos de fallo (memoria, latencia).
- Estandariza la verificación: propiedad de unidad + /proc limits + tendencia de recuento de FDs.
Hechos interesantes y contexto histórico
- Hecho 1: La idea original de Unix de “todo es un archivo” convirtió a los descriptores de archivo en el manejador universal—genial para la composabilidad, ocasionalmente problemático para outages.
- Hecho 2: Los primeros sistemas Unix tenían límites por defecto pequeños de FDs (a menudo 64 o 256). Muchos valores por defecto se mantuvieron conservadores durante décadas para proteger máquinas compartidas.
- Hecho 3: RLIMIT_NOFILE tiene valores “soft” y “hard”; el soft es lo que suele desencadenar EMFILE.
- Hecho 4: systemd no solo reemplazó scripts init; estandarizó cómo los servicios heredan límites y la pertenencia a cgroups, haciendo el comportamiento más predecible—una vez que aprendes dónde mirar.
- Hecho 5:
/proc/<pid>/limitses una de las fuentes de verdad más simples en Linux. Además, está criminalmente poco usada en respuesta a incidentes. - Hecho 6: “Demasiados archivos abiertos” suele tratarse de sockets, no de archivos. Demoniios de red con alta QPS pueden alcanzarlo sin tocar disco.
- Hecho 7: Subir límites de FD puede incrementar materialmente el uso de memoria del kernel porque sockets y estructuras de epoll/watch tienen sobrecosto por objeto.
- Hecho 8: Algunos servicios tienen límites internos (caps de worker, pools de conexión) que deben alinearse con los límites del SO o entras en un bucle de “arreglé el SO, sigue roto”.
Preguntas frecuentes
1) ¿Por qué ulimit -n no arregla mi servicio systemd?
Porque tu servicio systemd no es un hijo de tu shell interactivo. systemd establece rlimits al iniciar la unidad basándose en la unidad y sus valores por defecto. El ulimit de tu shell afecta solo a procesos lanzados desde ese shell.
2) ¿Se ignora /etc/security/limits.conf en Debian 13?
No. Se aplica donde PAM se aplica: sesiones de login (ssh, consola, algunas rutas de su/sudo según configuración). Muchos servicios del sistema no pasan por PAM, por lo que no heredarán esos límites.
3) ¿Debería establecer DefaultLimitNOFILE globalmente?
Normalmente no. Establece LimitNOFILE por servicio mediante drop-ins. Los valores por defecto globales son razonables solo si aplicas una política de flota y entiendes los efectos secundarios.
4) ¿Qué número debo elegir para LimitNOFILE?
Elige según el uso máximo medido de FDs más un margen, no por superstición. Para proxies ocupados, decenas a cientos de miles pueden ser normales. Para muchas apps, 8192–65536 es suficiente. Prueba con carga y mira /proc/<pid>/fd.
5) ¿Puedo subir LimitNOFILE sin reiniciar el servicio?
No. RLIMIT_NOFILE se establece por proceso. Debes reiniciar (o hacer un re-exec) para aplicar el nuevo límite. systemd no puede cambiar retroactivamente el rlimit de un proceso en ejecución.
6) Subí LimitNOFILE, pero los errores persisten. ¿Ahora qué?
Verifica que cambiaste el proceso que falla (revisa los workers). Luego inspecciona uso y tipos de FDs. Si los FDs crecen de forma sostenida, probablemente tengas una fuga. Si hace picos con tráfico, puede que necesites mejor control de concurrencia o ajustar límites internos de la aplicación.
7) ¿Cuál es la diferencia entre EMFILE y ENFILE otra vez?
EMFILE es por proceso: ese proceso alcanzó RLIMIT_NOFILE. ENFILE es a nivel sistema: se agotó la tabla de archivos del host. La mayoría de incidentes “Demasiados archivos abiertos” en servicios son EMFILE.
8) ¿Los contenedores cambian esta historia?
Sí, pero el principio se mantiene: el proceso tiene un RLIMIT real. Los runtimes de contenedores pueden establecerlo; systemd dentro de un contenedor puede establecerlo; el host puede restringirlo. Aun así, verifica con /proc/<pid>/limits dentro del namespace/entorno relevante.
9) ¿Un límite alto de FDs puede ser un riesgo de seguridad?
Puede ser un riesgo de disponibilidad. Si un servicio comprometido o con bugs puede abrir millones de FDs, puede agotar recursos del kernel y degradar el host. Los límites son parte del control del radio de explosión.
Conclusión: siguientes pasos que puedes hacer hoy
Arreglar “Demasiados archivos abiertos” en Debian 13 no es gritar más fuerte ulimit. Es fijar el límite donde nace el servicio: systemd. Usa un drop-in por servicio con LimitNOFILE. Recarga. Reinicia. Verifica en /proc/<pid>/limits. Luego mide el uso de FDs para saber si resolviste un problema de capacidad o simplemente pospusiste una fuga.
Pasos prácticos:
- Elige un servicio que alguna vez lanzó EMFILE y añade un drop-in con un
LimitNOFILEsensato. - Construye un pequeño snippet de runbook: MainPID → /proc limits → recuento de FDs.
- Durante tu próxima prueba de carga, registra el uso máximo de FDs y fija límites basados en esos datos, no en el folklore.
Si haces esto una vez, dejarás de tratar los descriptores de archivo como gremlins misteriosos y empezarás a tratarlos como lo que son: un presupuesto. Y los presupuestos, tristemente, siempre importan.