Empieza con algunos 502. Luego tus gráficas parecen un sismógrafo. Nginx está «activo», la carga del sistema es aceptable, pero los clientes reciben restablecimientos de conexión y tu registro de errores comienza a repetir: Too many open files.
En Ubuntu 24.04, la solución rara vez es «simplemente aumentar ulimit». Ese consejo es la razón por la que terminas con un número bonito en una shell y el mismo corte de servicio en producción. La solución correcta trata sobre límites de la unidad systemd, los propios techos de Nginx y el presupuesto global de descriptores de archivos del kernel —además de verificar todo con comandos que no mienten.
Qué significa realmente “Too many open files” en Nginx
El kernel devuelve EMFILE cuando un proceso alcanza su límite de descriptores de archivos por proceso. Nginx registra eso como “Too many open files” cuando no puede abrir un socket, aceptar una conexión, abrir un archivo o crear una conexión a upstream.
Matiz importante: los «archivos» de Nginx no son solo archivos. Son sockets, pipes, eventfd, signalfd y cualquier otro descriptor que el proceso abra. Si Nginx es un proxy inverso con mucho tráfico, la mayoría de los descriptores son sockets: conexiones de clientes + conexiones a upstream + sockets de escucha + archivos de registro. Si sirve contenidos estáticos, también hay manejadores de archivos abiertos. Si usas open_file_cache, puedes aumentar intencionalmente los manejadores de archivos abiertos. Si ejecutas HTTP/2 o HTTP/3, los patrones de conexión cambian, pero la contabilidad de descriptores sigue importando.
En Ubuntu 24.04, Nginx normalmente se gestiona con systemd. Eso significa:
/etc/security/limits.confno se aplica de forma fiable a servicios gestionados por systemd.- Lo que estableces en una shell interactiva (
ulimit -n) es irrelevante para el proceso maestro de Nginx lanzado por systemd. - Hay múltiples capas de límites: configuración de Nginx, unidad systemd, límites PAM para sesiones de usuario y máximos del kernel.
Este es el único modelo mental que previene de forma consistente el bucle “Lo subí, ¿por qué sigue roto?”:
- Techo global del kernel: cuántos descriptores puede asignar todo el sistema (
fs.file-max,file-nr), además del máximo por proceso que se puede fijar (fs.nr_open). - Techo de servicio systemd:
LimitNOFILEen la unidad de Nginx (o un valor por defecto de systemd aplicado a todos los servicios). - Techo interno de Nginx:
worker_rlimit_nofiley el máximo implícito porworker_connections. - Realidad: el número real de descriptores abiertos bajo carga.
Además, “Too many open files” puede ser síntoma de una fuga. No siempre es «necesitas un número más grande». Si los descriptores crecen de forma sostenida con tráfico constante, algo no se está cerrando. Podría ser un problema en un upstream, un módulo que se comporta mal o un patrón de logging/caching que nunca libera.
Broma #1: Los descriptores de archivos son como las tazas de café en la cocina de la oficina—si nadie las devuelve, al final no puedes servir a nadie y alguien culpará al lavavajillas.
Guion de diagnóstico rápido (qué comprobar primero)
Esta es la secuencia que encuentra el cuello de botella rápidamente, sin discutir con corazonadas.
1) Confirma el error y dónde ocurre
- Registro de errores de Nginx: ¿falla en
accept(),open()o en sockets a upstream? - journal de systemd: ¿ves “accept4() failed (24: Too many open files)” u mensajes similares?
2) Comprueba los límites de proceso de Nginx tal como los ve el kernel
- Lee
/proc/<pid>/limitspara el maestro de Nginx y para un worker. - Si el límite es bajo (1024/4096), casi siempre es configuración de systemd, no de Nginx.
3) Mide el uso y crecimiento real de FDs
- Cuenta descriptores abiertos por worker.
- Busca crecimiento monótono bajo carga constante (olor a fuga).
4) Valida la capacidad a nivel del sistema
sysctl fs.file-maxycat /proc/sys/fs/nr_opencat /proc/sys/fs/file-nrpara ver asignados vs usados.
5) Solo entonces ajusta
- Eleva
LimitNOFILEen un drop-in de systemd. - Configura
worker_rlimit_nofiley alineaworker_connections. - Recarga, verifica y prueba de carga (o al menos confirma con tráfico real).
Hechos y contexto interesantes (por qué vuelve a ocurrir)
- Hecho 1: Los descriptores de archivos en Unix preceden a las redes modernas; los sockets se diseñaron para parecer archivos y reutilizar APIs.
- Hecho 2: El límite suave clásico de 1024 viene de una época en que 1.000 conexiones simultáneas parecían ciencia ficción, no un martes por la mañana.
- Hecho 3: El modelo orientado a eventos de Nginx es eficiente en parte porque mantiene muchas conexiones abiertas de forma concurrente—lo que significa que consumirá FDs si se lo permites.
- Hecho 4: systemd no hereda automáticamente el
ulimitde tu shell; los servicios obtienen límites de archivos desde archivos de unidad y valores por defecto de systemd. - Hecho 5: En Linux,
fs.nr_openlimita el máximo de archivos abiertos por proceso que puedes fijar, incluso como root. - Hecho 6: “Too many open files” puede aparecer aunque el uso a nivel de sistema esté bien—porque los límites por proceso son los que primero muerden.
- Hecho 7: Nginx puede alcanzar límites de FD más rápido al hacer proxy porque cada conexión de cliente puede implicar una conexión upstream (a veces más de una).
- Hecho 8: La agotación de puertos efímeros suele diagnosticarse mal como agotamiento de FDs; ambos parecen fallos de conexión, pero son perillas distintas.
Una idea parafraseada de una voz notable en SRE: La fiabilidad viene del aprendizaje, no de la culpa—así que instrumenta el sistema y verifica las suposiciones.
Tareas prácticas: comandos, salida esperada y decisiones (12+)
Estas son tareas «haz esto ahora». Cada una incluye qué significa la salida y qué decisión tomar a continuación. Ejécutaselas como root o con sudo donde corresponda.
Task 1: Confirmar el error en los logs de Nginx
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)
Significado: Nginx está alcanzando un techo de FD por proceso. accept4() fallando significa que no puede aceptar nuevas conexiones de cliente. Fallar al abrir logs es también una señal clara: ya está en problemas.
Decisión: Comprueba inmediatamente los límites del proceso Nginx y el uso actual de FDs antes de cambiar nada.
Task 2: Revisar el journal de systemd para mensajes correlacionados
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)
Significado: Confirma que es Nginx y no un rumor del cliente. También muestra si se hicieron reinicios/recargas durante el evento.
Decisión: Si las recargas ocurren automáticamente (bucles de gestión de configuración), podrías estar amplificando el problema. Considera pausar la automatización hasta corregir los límites.
Task 3: Encontrar el PID maestro de Nginx
cr0x@server:~$ pidof nginx
2143 2144 2145 2146
Significado: Típicamente el primer PID es el maestro, el resto son workers (depende de la compilación/opciones).
Decisión: Verifica qué PID es maestro y cuáles son workers; necesitas inspeccionar ambos.
Task 4: Verificar roles maestro y workers
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
Significado: El maestro es root, los workers son www-data. Los límites pueden diferir si hay algo extraño, pero normalmente heredan del maestro.
Decisión: Inspecciona los límites tanto del maestro como de los workers—no asumas que la herencia es como piensas.
Task 5: Comprobar límites actuales desde /proc
cr0x@server:~$ sudo cat /proc/2143/limits | grep -i "open files"
Max open files 1024 1024 files
Significado: Esto es la pistola humeante: 1024 no es un límite serio en producción para un proxy inverso Nginx.
Decisión: Arregla LimitNOFILE en systemd. Las modificaciones de ulimit en la shell no importan.
Task 6: Contar descriptores abiertos para un worker
cr0x@server:~$ sudo ls -1 /proc/2144/fd | wc -l
1007
Significado: El worker está casi en el límite. No te lo estás imaginando.
Decisión: Si el uso está cerca del límite, elévalo. Si está lejos, podrías estar persiguiendo el error equivocado (raro, pero revisa).
Task 7: Identificar qué tipos de FDs están abiertos (sockets vs archivos)
cr0x@server:~$ sudo lsof -p 2144 | awk '{print $5}' | sort | uniq -c | sort -nr | head
812 IPv4
171 IPv6
12 REG
5 FIFO
Significado: Mayormente sockets (IPv4/IPv6). Esto es concurrencia de conexiones impulsada por carga, no hinchazón por manejadores de archivos estáticos.
Decisión: Enfócate en límites por worker, comportamiento de keepalive y reutilización de upstreams—no solo en «servir menos archivos».
Task 8: Revisar la configuración de Nginx para límites de worker
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;
Significado: No hay worker_rlimit_nofile establecido, y worker_connections es modesto. Con keepalive y proxying, 768 aún puede causar presión, pero el problema mayor es el límite OS de 1024.
Decisión: Alinea LimitNOFILE y worker_rlimit_nofile con un objetivo realista. Luego revisa worker_connections según la concurrencia esperada.
Task 9: Comprobar límites actuales de systemd aplicados al servicio nginx
cr0x@server:~$ sudo systemctl show nginx -p LimitNOFILE
LimitNOFILE=1024
Significado: systemd está aplicando 1024 para el servicio. Esto anula tus esperanzas y sueños.
Decisión: Añade un override drop-in de systemd para nginx con un LimitNOFILE más alto.
Task 10: Comprobar estadísticas globales de descriptores del kernel
cr0x@server:~$ cat /proc/sys/fs/file-max
9223372036854775807
cr0x@server:~$ cat /proc/sys/fs/file-nr
3648 0 9223372036854775807
Significado: En Ubuntu moderno, file-max puede ser efectivamente «muy alto». file-nr muestra manejadores asignados (~3648). Este sistema está lejos de la agotación global de FDs.
Decisión: No toques fs.file-max aún. Tu cuello de botella es por proceso/servicio.
Task 11: Comprobar fs.nr_open (límite duro por proceso)
cr0x@server:~$ cat /proc/sys/fs/nr_open
1048576
Significado: Puedes fijar límites por proceso hasta 1.048.576. Suficiente margen.
Decisión: Elige un número razonable (a menudo 65535 o 131072) en lugar de ir por «infinito».
Task 12: Comprobar cuántas conexiones gestiona realmente Nginx
cr0x@server:~$ sudo ss -s
Total: 2389 (kernel 0)
TCP: 1920 (estab 1440, closed 312, orphaned 0, timewait 311)
Significado: Si las conexiones establecidas son altas, el uso de FDs también lo será. TIME_WAIT no es FDs de la misma forma, pero indica patrones de tráfico y comportamiento de keepalive.
Decisión: Si las conexiones establecidas correlacionan con el error, aumentar los límites es válido. Si las establecidas son bajas pero los FDs son altos, sospecha fugas o caches.
Task 13: Validar la ruta de la unidad activa de Nginx y los 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
Significado: Muestra de dónde se carga la unidad y si existen drop-ins. Si no ves un directorio drop-in, todavía no has sobrescrito nada.
Decisión: Prefiere un override drop-in en /etc/systemd/system/nginx.service.d/. No edites unidades del proveedor en /usr/lib.
Task 14: Después de los cambios, confirma que el nuevo límite está en efecto
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
Significado: systemd ahora concede 65535 y el proceso maestro en ejecución lo tiene. Ese es el paso de verificación que la gente omite y luego se sorprende.
Decisión: Si los valores no coinciden, no reiniciaste correctamente, tu override no cargó o otra configuración de unidad está ganando.
Solución correcta: LimitNOFILE de systemd para Nginx
Ubuntu 24.04 ejecuta systemd. Nginx probablemente corre como nginx.service. El enfoque correcto es un override de unidad (drop-in), no editar archivos de unidad del proveedor y no confiar en limits.conf.
Elige un número sensato
Valores comunes en producción:
- 65535: clásico, ampliamente usado, normalmente suficiente para un Nginx por nodo haciendo proxy normal.
- 131072: para concurrencia muy alta o cargas con múltiples upstreams.
- 262144+: raro; justificado solo con mediciones y una razón (y usualmente aparecen otros cuellos de botella primero).
No lo pongas en «un millón» solo porque puedas. Un límite grande puede ocultar una fuga por más tiempo y aumenta el radio de desastre cuando algo se vuelve patológico.
Crear un override drop-in de systemd
cr0x@server:~$ sudo systemctl edit nginx
# (an editor opens)
Añade esto:
cr0x@server:~$ cat /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65535
Decisión: Si también ejecutas Nginx en un contenedor o mediante otra unidad (por ejemplo, un wrapper personalizado), aplica el override al nombre de unidad correcto, no al que desearías estar usando.
Recargar systemd y reiniciar Nginx
cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart nginx
cr0x@server:~$ sudo systemctl is-active nginx
active
Significado: daemon-reload hace que systemd vuelva a leer las unidades. Es necesario reiniciar para aplicar nuevos límites; las recargas no re-aplican de forma fiable límites de recursos a un proceso ya en ejecución.
Verifica, no supongas
cr0x@server:~$ sudo systemctl show nginx -p LimitNOFILE
LimitNOFILE=65535
Si eso muestra el valor correcto pero /proc/<pid>/limits no lo hace, probablemente estás mirando un PID antiguo (el servicio no reinició) o Nginx se lanzó por otro servicio.
¿Y DefaultLimitNOFILE?
systemd puede fijar límites por defecto para todos los servicios vía /etc/systemd/system.conf (y user.conf para servicios de usuario). Esto es tentador en entornos corporativos porque «arregla todo». También cambia todo.
Mi opinión: para Nginx, usa un override por servicio salvo que tengas una línea base madura y conozcas el impacto en cada demonio. Establecer un valor por defecto alto puede permitir que otros procesos abran grandes cantidades de archivos sin control, lo que no es automáticamente bueno.
Ajustes en Nginx: worker_rlimit_nofile, worker_connections, keepalive
systemd puede otorgar a Nginx 65k FDs, pero Nginx aún debe usarlos con inteligencia.
worker_rlimit_nofile: alinea Nginx con el límite del SO
Añade en el contexto principal (nivel superior) 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
Ejemplo de fragmento que podrías añadir:
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;
}
Significado: worker_rlimit_nofile establece RLIMIT_NOFILE para los procesos worker (y a veces para el maestro, dependiendo de la compilación/comportamiento). Si no lo estableces, Nginx puede seguir ejecutándose con el límite proporcionado por el sistema, pero la alineación explícita evita sorpresas.
Decisión: Pon worker_rlimit_nofile al mismo valor (o ligeramente menor) que LimitNOFILE de systemd. Si lo fijas más alto que lo permitido, Nginx no superará mágicamente el límite del SO.
worker_connections: no es «cuántos usuarios», es cuántos sockets
En el bloque events, worker_connections define el máximo de conexiones simultáneas por proceso worker. Aproximadamente:
- Máx de conexiones cliente ≈
worker_processes * worker_connections - Pero el proxy inverso suele duplicar el uso de sockets: un socket cliente + un socket upstream.
- Además hay overhead: sockets de escucha, logs, pipes internos, sockets del resolvedor, etc.
Si fijas worker_connections a 50k y el límite de FD en 65k, estás haciendo cálculos con el entusiasmo de un niño y la precisión de una máquina de niebla.
Keepalive y proxying: el multiplicador de FDs que no se ve
Keepalive es genial—hasta que no lo es. Con keepalive, clientes y upstreams mantienen sockets abiertos más tiempo. Esto mejora la latencia y reduce costos de handshake, pero incrementa el uso sostenido de FDs. Bajo tráfico con ráfagas, puede convertir «tuvimos un pico» en «tenemos 30 segundos de presión sostenida en descriptores».
Lugares a revisar:
keepalive_timeoutykeepalive_requests(lado cliente)proxy_http_version 1.1yproxy_set_header Connection ""(corrección de keepalive en upstream)keepaliveen bloques upstream (pools de conexión)
Broma #2: Keepalive es como dejar «reservada» la sala de reuniones porque podrías tener más reuniones luego—eventualmente nadie puede trabajar, pero el calendario se ve fantástico.
Recarga segura
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
Significado: Reload aplica cambios de configuración sin cortar conexiones (en su mayoría), pero los cambios en límites de FD requieren reinicio previo. Ahora iteras sobre ajustes de Nginx.
Límites del kernel y del sistema: fs.file-max, fs.nr_open, puertos efímeros
La mayoría de incidentes «Too many open files» en Nginx son por límites por proceso. Pero debes entender las palancas a nivel sistema porque son el siguiente modo de fallo cuando escalas.
Capacidad de descriptores a nivel sistema
Indicadores clave:
/proc/sys/fs/file-max: máximo global de archivos abiertos (puede ser enorme en sistemas modernos)./proc/sys/fs/file-nr: asignados, no usados, máximo. Un aumento rápido de los asignados puede señalar picos o fugas en todo el sistema.fs.nr_open: valor máximo para límites por proceso.
Si la asignación global está cerca del máximo, aumentar el límite por proceso de Nginx no ayudará. Solo moverás el fallo a otros servicios, y el kernel comenzará a rechazar asignaciones de formas más creativas.
Puertos efímeros: el otro «demasiados»
Cuando Nginx es proxy inverso, cada conexión a upstream consume un puerto efímero local. Si atacas a un conjunto pequeño de upstreams con muchas conexiones de corta duración, puedes agotar puertos efímeros o quedarte atascado con patrones TIME_WAIT. Los síntomas pueden parecer presión de FDs: fallos de conexión, timeouts a upstream, aumento de 499/502/504.
Comprobaciones rápidas:
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
Significado: El rango efímero por defecto es ~28k puertos. El conteo TIME_WAIT te indica cuántas conexiones cerradas recientemente están pendientes. No todo TIME_WAIT es malo, pero conteos altos con alta rotación pueden afectar.
Decisión: Si la presión de puertos es real, lo arreglas con reutilización de conexiones (keepalive upstream), distribución de carga y a veces ajuste del rango de puertos—no inflando solo los límites de FDs.
¿Necesitas tocar /etc/security/limits.conf?
Para servicios systemd: normalmente no. Ese archivo se aplica vía PAM a sesiones de usuario (SSH, login, etc.). Nginx lanzado por systemd no lo lee. Aún puedes dejarlo por consistencia, pero no esperes que arregle el incidente.
Chequeo de cordura en una shell interactiva:
cr0x@server:~$ ulimit -n
1024
Significado: Este es el límite de tu shell, no el de Nginx. Útil solo para confirmar lo que tú puedes hacer, no lo que el servicio puede hacer.
Decisión: No «arregles» producción pegando ulimit -n 65535 en una shell y sintiéndote realizado.
Planificación de capacidad: ¿cuántos FDs necesitas realmente?
Planificar capacidad para descriptores de archivos es aburrido. Por eso funciona.
Reglas nemotécnicas
Comienza con este modelo aproximado:
- Por conexión activa de cliente: ~1 FD (el socket cliente)
- Por petición proxied: a menudo +1 FD (socket upstream), a veces más con reintentos/múltiples upstreams
- Por worker, línea base: unas pocas decenas de FDs (logs, eventfd, pipes, sockets de escucha compartidos, etc.)
Si sirves solo contenido estático y usas sendfile, todavía abres manejadores de archivos, pero pueden ser de corta duración. Si activas cache de archivos, pueden ser de larga duración.
Calcular un objetivo práctico
Ejemplo: esperas hasta 20,000 conexiones clientes concurrentes en una máquina y proxys a upstreams con keepalive. Asume de forma conservadora 2 FDs por cliente en pico (cliente + upstream): 40,000. Añade overhead: digamos 2,000. ¿Dividir entre workers? No exactamente—las conexiones se distribuyen, pero hay desigualdad. Si tienes 8 workers, en el peor caso uno podría contener más de la media temporalmente según comportamiento de accept y scheduling.
Por eso das margen. Un LimitNOFILE de 65535 por proceso es un punto común: lo bastante grande para evitar fallos tontos y lo bastante pequeño para que las fugas salten antes de causar catástrofe global.
Mide, no adivines
Después de fijar límites, sigue midiendo. Para un worker en vivo:
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
Significado: Conteos de FDs por worker bajo carga. Si ves estos números cerca de tu límite, necesitas bien límites mayores, o menos sockets de larga duración (ajustar keepalive/upstream) o más capacidad (más instancias).
Decisión: Si los conteos son estables y están cómodamente por debajo de los límites, deja de tocar cosas. Arregla y sigue con lo importante. La ingeniería no es un deporte donde ganas cambiando más perillas.
Tres microhistorias desde trincheras corporativas
Microhistoria 1: El incidente causado por una suposición equivocada
El equipo migró desde una versión antigua de Ubuntu a Ubuntu 24.04 como parte de una actualización de baseline de seguridad. La configuración de Nginx no cambió. El perfil de tráfico no cambió. Aun así, en días aparecieron fallos de conexión esporádicos durante picos previsibles.
El ingeniero de guardia hizo lo que todo linuxero ha hecho al menos una vez: SSHeó, ejecutó ulimit -n 65535, reinició Nginx a mano, vio que el problema desaparecía y volvió a dormir. Al día siguiente volvió. Así se convirtió en un ritual.
La suposición incorrecta fue sutil: creyeron que el ulimit de su shell se aplicaba al servicio tras reiniciarlo. No era así. Nginx era iniciado por systemd durante reinicios desatendidos y mantenimientos del host, regresando siempre a LimitNOFILE=1024. La solución manual solo funcionaba cuando Nginx se lanzaba desde una shell con el límite elevado.
La solución durable tomó diez minutos: un drop-in de systemd con LimitNOFILE, y luego verificar vía /proc/<pid>/limits. La ganancia real, sin embargo, fue social: escribieron el paso de verificación en la plantilla de incidentes. Las siguientes guardias dejaron de tratar «ulimit» como un encantamiento.
Microhistoria 2: La optimización que salió mal
Un ingeniero orientado al rendimiento quiso reducir latencia upstream. Activó ajustes agresivos de keepalive tanto del lado cliente como upstream. El cambio se vio brillante en pruebas sintéticas: menos handshakes, p95 más bajo, dashboards felices.
Luego producción hizo lo suyo. El servicio tenía muchos clientes de cola larga (redes móviles, proxies empresariales) que mantenían conexiones abiertas como si pagaran renta. Con keepalive al máximo, Nginx retuvo muchas más sockets concurrentes que antes. Los descriptores se mantuvieron asignados más tiempo y el sistema se acercó al límite por proceso durante las horas pico.
Peor aún: el equipo también habilitó una open_file_cache más grande para assets estáticos en los mismos nodos, añadiendo más manejadores de archivos de larga duración. El efecto combinado hizo aparecer el error en lugares extraños—a veces durante una deploy (cuando rotaban logs), a veces durante picos de tráfico.
La solución no fue «desactivar keepalive», porque eso habría devuelto el coste de latencia. La solución fue supervisión adulta: elevar LimitNOFILE correctamente, capear worker_connections a un número defendible y ajustar timeouts de keepalive para coincidir con el comportamiento real de clientes. Además separaron roles: nodos heavies en estáticos recibieron configuración de caching distinta que los nodos proxy. Las optimizaciones pueden funcionar; solo necesitan un presupuesto.
Microhistoria 3: La práctica aburrida pero correcta que salvó el día
Una organización distinta tenía un runbook simple: cada vez que cambiaban ajustes de concurrencia en Nginx, registraban tres números en el ticket de cambio: LimitNOFILE, worker_rlimit_nofile y el pico observado de /proc/<pid>/fd. No era glamuroso. No era «innovador». Efectivo.
Una tarde, una dependencia upstream empezó a hacer timeouts intermitentes. Las conexiones cliente se acumularon mientras Nginx esperaba respuestas upstream. La concurrencia subió. Eso es normal ante lentitud upstream: se forman colas en la capa proxy.
Pero el proxy no colapsó. ¿Por qué? Porque sus límites de FD estaban configurados con margen y tenían monitorización que alertaba al 70% del límite de FD por worker. La guardia vio la alerta, la reconoció como retención de sockets inducida por upstream y escaló al equipo de la dependencia mientras rate-limiteaban un endpoint ruidoso. Sin fallo en cascada. Sin «Nginx murió también».
El informe post-incident fue casi aburrido, que es el mayor cumplido que puedes dar a una práctica operativa.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: Configuraste ulimit a 65535 pero Nginx sigue registrando EMFILE
Causa raíz: Nginx está gestionado por systemd; los límites de shell no se aplican. systemd sigue con LimitNOFILE=1024.
Solución: Añade un drop-in override de systemd para nginx.service con LimitNOFILE=65535, reinicia y verifica vía /proc/<pid>/limits.
2) Síntoma: systemctl show informa un LimitNOFILE alto, pero /proc muestra uno bajo
Causa raíz: Cambiaste la unidad pero no reiniciaste el servicio; o inspeccionas el PID equivocado; o Nginx se inicia por otra unidad/wrapper.
Solución: systemctl daemon-reload, luego systemctl restart nginx. Revisa el PID maestro y lee los límites desde ese PID.
3) Síntoma: Aumentar LimitNOFILE ayudó un día, luego los errores volvieron
Causa raíz: Una fuga o aumento sostenido de concurrencia por lentitud de upstream o comportamiento de clientes. Trataste el síntoma, no la tendencia.
Solución: Rastrear uso de FDs en el tiempo por worker. Correlacionar con latencia upstream y conexiones activas. Corregir keepalive, timeouts, pooling de upstream o la dependencia upstream.
4) Síntoma: Errores solo durante rotación de logs o recargas
Causa raíz: Cuando estás cerca del límite de FDs, acciones benignas como reabrir logs requieren descriptores libres. No tienes margen.
Solución: Elevar límites y asegurar que el conteo normal de FDs esté por debajo del ~70–80% del límite en pico.
5) Síntoma: Nginx acepta conexiones pero fallan los proxys a upstream
Causa raíz: Puede que tengas suficientes FDs para clientes pero no para sockets a upstream, o los puertos efímeros están limitados.
Solución: Aumenta el límite de FDs y asegúrate de que keepalive upstream funcione. Revisa rango de puertos efímeros y churn de TIME_WAIT; aborda la reutilización de conexiones.
6) Síntoma: Solo un worker alcanza el límite y se quema
Causa raíz: Distribución desigual de conexiones, posiblemente por configuración de accept mutex, afinidad de CPU o patrones de tráfico.
Solución: Asegura worker_processes auto acorde a CPUs, revisa ajustes de accept y verifica distribución de carga. A menudo la solución real es más margen + evitar que el upstream obligue a retenciones largas.
7) Síntoma: Aumentaste worker_connections masivamente y todo empeoró
Causa raíz: Aumentaste la concurrencia teórica sin asegurar que memoria, capacidad upstream y límites de FDs acompañen. Invitaste a una estampida mayor.
Solución: Dimensiona worker_connections a lo que el sistema puede sostener. Usa rate limiting, encolamiento o escalado en lugar de concurrencia infinita.
Listas de verificación / plan paso a paso
Ruta paso a paso: camino seguro en producción
- Captura evidencia: tail del registro de errores de Nginx y journal para
EMFILE. - Encuentra PIDs: identifica maestro y workers.
- Lee límites en vivo:
/proc/<pid>/limitspara maestro y un worker. - Mide uso: cuenta
/proc/<pid>/fdpara workers; muestrea varias veces bajo carga. - Revisa límite de systemd:
systemctl show nginx -p LimitNOFILE. - Implementa override systemd:
systemctl edit nginx→LimitNOFILE=65535. - Reinicia (no recargues): aplica límites con restart.
- Verifica otra vez: systemd muestra nuevo límite, /proc muestra nuevo límite.
- Alinea configuración de Nginx: establece
worker_rlimit_nofile, consideraworker_connectionssegún concurrencia medida. - Recarga configuración de Nginx:
nginx -tluegosystemctl reload nginx. - Vigila regresiones: monitoriza FDs abiertos por worker; alerta al 70–80% del límite.
- Investiga causas raíz: si el uso de FDs sube sostenidamente, busca fugas o lentitud en upstream; no vuelvas a subir números sin entender por qué.
Checklist: qué se ve bien después del arreglo
systemctl show nginx -p LimitNOFILEdevuelve el valor objetivo./proc/<masterpid>/limitscoincide.- Conteos de
/proc/<pid>/fdde workers tienen margen en pico. - El registro de errores de Nginx ya no muestra
accept4() failed (24: Too many open files). - Despliegues/recargas y rotación de logs no disparan fallos de conexión.
Checklist: cuando aumentar límites es la solución equivocada
- El conteo de FDs sube monótonamente durante horas con tráfico constante (patrón de fuga).
- El upstream es lento y las conexiones se acumulan; necesitas timeouts, backpressure o escalado.
- Los fallos de conexión correlacionan con alto TIME_WAIT y rango de puertos pequeño (agotamiento de puertos).
- Hay presión de memoria ya; aumentar concurrencia empeorará latencia y fallos.
Preguntas frecuentes (preguntas reales a las 02:00)
1) ¿Por qué /etc/security/limits.conf no arregla Nginx en Ubuntu 24.04?
Porque Nginx se ejecuta como un servicio systemd. Los límites PAM se aplican a sesiones de login. systemd aplica sus propios límites de recursos desde archivos de unidad y valores por defecto.
2) ¿Necesito establecer ambos LimitNOFILE y worker_rlimit_nofile?
Sí, en la práctica. LimitNOFILE es el techo impuesto por el SO para el servicio. worker_rlimit_nofile hace que Nginx solicite/propague un límite coincidente para los workers. Alinea ambos para evitar ambigüedad de «debería estar bien».
3) ¿Cuál es un LimitNOFILE razonable para Nginx?
Común: 65535. Proxies de alto tráfico pueden usar 131072. Ve más alto solo con necesidad medida y monitorización, porque puede ocultar fugas y aumentar el radio de impacto.
4) Si aumento el límite, ¿Nginx manejará automáticamente más conexiones?
No automáticamente. También necesitas worker_connections adecuados, CPU, memoria, capacidad upstream y a veces ajustes del kernel. Más concurrencia sin capacidad es solo una cola más grande hacia el fallo.
5) Subí los límites pero todavía veo “Too many open files” ocasionalmente. ¿Por qué?
O no aplicaste el límite al proceso en ejecución (verifica /proc), o estás alcanzando el nuevo límite legítimamente bajo picos, o otro componente (upstream, resolvedor, logging) consume descriptores inesperadamente. Mide FDs por worker y correlaciona con tráfico y latencias.
6) ¿Puede “Too many open files” ser causado por un bug o fuga?
Sí. Si el uso de FDs crece sostenidamente sin aumento de tráfico, sospecha fugas o una mala configuración que mantiene descriptores (keepalive agresivo, caching, conexiones upstream estancadas).
7) ¿Es seguro cambiar LimitNOFILE sin downtime?
Cambiar el límite requiere reiniciar el servicio, lo que puede causar una breve interrupción a menos que tengas múltiples instancias detrás de un balanceador. Planifica reinicios rolling cuando sea posible.
8) ¿Cómo alerto de esto antes de que sea un corte?
Alerta por uso de FDs como porcentaje del límite por worker (por ejemplo, 70% aviso, 85% crítico). Puedes calcularlo desde /proc/<pid>/fd y /proc/<pid>/limits.
9) ¿Y si el file-max del sistema es el cuello de botella?
Entonces estás en terreno de «todo el host se queda sin descriptores». Identifica los mayores consumidores por proceso. Aumentar límites de Nginx no ayudará si el kernel no puede asignar más. Esto es raro con valores por defecto modernos de Ubuntu, pero posible en nodos multi-tenant densos.
10) ¿HTTP/2 reduce el uso de FDs por multiplexación?
A veces, pero no lo asumas. HTTP/2 puede reducir el número de conexiones TCP cliente, pero el comportamiento upstream, pools de keepalive y sidecars aún pueden dominar el uso de FDs.
Conclusión: próximos pasos que perduran
Cuando Nginx en Ubuntu 24.04 alcanza “Too many open files”, la solución correcta no es un one-liner heroico. Es una cadena verificada: systemd concede el límite, Nginx está configurado para usarlo de forma segura y el kernel tiene suficiente capacidad global.
Haz esto, en este orden:
- Verifica el límite en vivo vía
/proc/<pid>/limits. Si está en 1024, para todo y corrigeLimitNOFILEen systemd. - Fija un drop-in de systemd para
nginx.servicey reinicia. Verifica otra vez. - Alinea Nginx con
worker_rlimit_nofiley ajustaworker_connectionsbasado en concurrencia medida, no en sensaciones. - Mide conteos de FDs por worker en picos. Añade alertas sobre margen. Si el uso de FDs asciende con el tiempo, investiga fugas o lentitud en upstream.
Después de eso, tendrás el raro lujo de un servidor web que falla por razones interesantes, no porque se quedó sin manejadores numerados.