Las claves en 30 segundos
- CPU saturada: si la columna
r de vmstat se queda por encima del número de núcleos, hay procesos esperando turno. En una máquina virtual, mira también st: es el tiempo que el hipervisor no te deja usar.
- Presión de memoria: lo que importa no es cuánta RAM aparece libre, sino si el sistema está moviendo páginas a swap (
si/so) y si las tareas se quedan paradas esperando memoria (/proc/pressure/memory).
- Disco:
iostat -xz 1 da la latencia de cada operación y la cola. La columna await del borrador típico ya no existe: las versiones actuales de sysstat la parten en r_await y w_await. Y %util al 100 % no significa lo mismo en un SSD que en un disco mecánico.
- Red: retransmisiones, paquetes descartados y colas de conexiones llenas.
ss -lnt lo enseña si sabes qué significan sus dos primeras columnas en un socket que escucha.
- Límites: descriptores de fichero, cuotas de CPU y memoria de contenedores y cgroups. El host puede estar sobrado y el servicio, ahogado.
- Latencia de la aplicación: si todo lo anterior está bien y las peticiones siguen siendo lentas, el problema está en una consulta, en el DNS o en una API externa. Ahí mandan los percentiles, no la media.
El aviso suele llegar de la misma forma: «la API va lenta». Lo abre alguien de producto, lo recoge el equipo de desarrollo y, si no hay una causa evidente en el último despliegue, acaba en una pregunta incómoda para quien lleva la infraestructura: ¿subimos de máquina?
Muchas veces la respuesta correcta es que no. Más núcleos no arreglan un bucle que se ha quedado girando, más RAM no arregla una fuga de memoria y un disco más rápido no compensa una consulta sin índice que lee media tabla en cada petición. Y al revés: también hay casos en los que el código está bien y la máquina se ha quedado corta, o la tiene limitada un ajuste que nadie recuerda haber puesto.
Para quien desarrolla, saber leer estas señales ahorra dos discusiones. La de «la infraestructura va mal» cuando el problema está en la aplicación, y la de «tu código es lento» cuando lo que pasa es que el contenedor tiene media CPU asignada. Este artículo parte de una pieza de systemadministration.net sobre las seis señales que delatan un cuello de botella en Linux. Hemos comprobado los comandos en Debian 12 y 13 y Ubuntu 24.04, corregido lo que ha cambiado y añadido lo que más se echa en falta cuando el servidor es una máquina virtual o el servicio corre en un contenedor.
Antes de empezar: dos trampas de los propios comandos
La primera línea de vmstat e iostat miente un poco. Ambos imprimen primero la media desde que arrancó el sistema, y luego, una muestra por intervalo. En un servidor que lleva tres meses encendido, esa primera fila diluye cualquier pico. Fíjate en las siguientes. Con iostat se puede quitar directamente con -y.
Sin una referencia, ningún número dice nada. Un servidor de base de datos con la CPU al 70 % puede estar perfectamente, y una cola de disco de 2 puede ser mucho para un volumen y nada para otro. Lo útil es medir mientras el problema ocurre y comparar con un periodo en el que todo iba bien. Si tienes monitorización con histórico, empieza por ahí. Si no, deja vmstat 5 y iostat -xyz 5 escribiendo en un fichero durante una hora tranquila y otra mala.
1. CPU saturada: cuando los procesos hacen cola
El porcentaje de uso de CPU es lo primero que mira todo el mundo, y lo que menos dice por sí solo. Un servidor puede ir al 90 % y responder bien si la carga es estable y las colas no crecen.
vmstat 1
La columna que importa es r: procesos que están ejecutándose o esperando para hacerlo. Si se mantiene por encima del número de núcleos (nproc te lo dice), hay trabajo esperando turno. Es una pista, no un diagnóstico: una ráfaga de unos segundos es normal; una cola que no baja en minutos, no.
En el mismo vmstat hay otras tres columnas que conviene saber leer:
wa es tiempo de CPU ocioso esperando a disco. No significa que la CPU esté ocupada; significa que no tiene nada que hacer porque espera E/S. Si wa es alto, el problema probablemente está en el apartado 3.
st (steal) es el tiempo que la máquina virtual quería CPU y el hipervisor se la dio a otra. Si ves valores sostenidos por encima de unos pocos puntos, el problema no está dentro de tu servidor: el host físico está sobrecargado o el plan reparte más vCPU de las que hay. Ningún ajuste en el sistema operativo lo arregla.
gu, que aparece en vmstat desde procps 4 (Debian 13 la muestra, Debian 12 no), es el tiempo dedicado a ejecutar máquinas virtuales invitadas. Solo tiene sentido en un hipervisor.
Para saber quién consume la CPU:
top
pidstat -u 1
mpstat -P ALL 1
mpstat -P ALL merece más atención de la que recibe, sobre todo en equipos de desarrollo. Un núcleo al 100 % y el resto ociosos es la firma de un proceso que no puede repartir su trabajo: un servidor de Node.js con un único proceso, Redis, un intérprete de Python con el GIL o un trabajo por lotes secuencial. Añadir vCPU a esa máquina no cambia nada; lo que cambia algo es arrancar más procesos o repartir el trabajo.
Antes de ampliar la CPU, localiza el proceso, la tarea programada o el tipo de petición responsable del consumo. Más capacidad ayuda cuando la carga la necesita de verdad, no cuando hay un bucle que no debería estar ahí.
2. Presión de memoria: cuando la RAM no alcanza
Los problemas de memoria cuestan más de ver que los de CPU. El servidor sigue funcionando, pero cada vez más despacio, porque el kernel tiene que recuperar memoria o mover páginas entre la RAM y el swap.
free -m
Mira la columna available, no free. Linux usa la RAM que nadie está usando como caché de disco, y la libera cuando una aplicación la pide. Un servidor con 1 GB «libre» y 4 GB «disponibles» está bien.
Lo que de verdad indica presión es la actividad de swap, no su ocupación. En vmstat 1, las columnas si y so son la memoria que entra y sale de swap cada segundo. Tener 500 MB en swap no es un problema si son páginas inactivas que llevan días allí. Tener so moviéndose de forma continua mientras sube la latencia, sí.
Para afinar más, el kernel ofrece PSI (Pressure Stall Information) desde la versión 4.20:
cat /proc/pressure/memory
some avg10=0.00 avg60=0.00 avg300=0.00 total=5212590
full avg10=0.00 avg60=0.00 avg300=0.00 total=5210970
some es el porcentaje de tiempo en el que al menos una tarea estaba parada esperando memoria; full, el tiempo en que todas las tareas no ociosas lo estaban, que es tiempo de trabajo perdido para todo el sistema. Las tres medias cubren los últimos 10, 60 y 300 segundos. Es la medida que mejor separa un servidor con poca RAM libre que va bien de uno al que la memoria está frenando. Existen también /proc/pressure/cpu y /proc/pressure/io, que se leen igual.
Queda comprobar si el kernel ha matado algún proceso por falta de memoria:
journalctl -k | grep -i -E 'out of memory|killed process'
El borrador de partida usaba dmesg -T, que sirve igual, aunque en Debian suele necesitar sudo. journalctl -k lee los mismos mensajes del kernel desde el journal y conserva los de arranques anteriores si el journal es persistente. Ninguno de los dos funciona dentro de un contenedor sin privilegios: ahí, el rastro está en los contadores del cgroup que vemos en el apartado 5.
Ojo con los contenedores. Un servicio puede quedarse sin memoria con el host sobrado, porque su límite es el del cgroup y no el de la máquina. Lo vemos en el apartado 5 con una prueba. Para quien programa en lenguajes con recolector de basura, el ajuste equivalente está en el propio runtime: el tamaño máximo del heap en Java o GOMEMLIMIT en Go, del que hablamos en el artículo sobre Go y la infraestructura cloud.
Si se confirma la presión, el siguiente paso es saber qué procesos consumen la RAM y si ese consumo es el esperado. Ampliar memoria puede ser lo correcto, pero también puede haber una fuga, una caché sin tope o un límite de contenedor mal puesto.
3. Disco: cuando la aplicación espera a la E/S
Un servidor puede tener CPU y RAM de sobra y ser lento porque cada escritura tarda demasiado. Pasa a menudo con bases de datos, máquinas virtuales, sistemas de registros y cargas que leen o escriben muchos ficheros pequeños.
iostat viene en el paquete sysstat, que no se instala por defecto en Debian ni en Ubuntu:
sudo apt install sysstat
iostat -xyz 1
Aquí está la corrección más útil al borrador. Muchas guías hablan de la columna await, pero las versiones actuales de sysstat ya no la muestran en iostat -x. En lo que traen Debian 12, Debian 13 y Ubuntu 24.04 (sysstat 12.6 y 12.7), la cabecera es esta:
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s ... f_await aqu-sz %util
Las que importan:
r_await y w_await: milisegundos que tarda de media una lectura o una escritura, contando el tiempo en cola. Separarlas ayuda: una base de datos que espera a sus fsync lo enseña en w_await aunque las lecturas vayan bien. d_await es para operaciones de descarte (TRIM) y f_await, para vaciados de caché.
aqu-sz: longitud media de la cola de peticiones pendientes.
%util: porcentaje del tiempo en que el dispositivo tenía al menos una petición en curso.
Con %util hay que tener cuidado, y lo dice el propio manual de iostat: en dispositivos que atienden peticiones en paralelo, como los SSD NVMe o los volúmenes RAID, un 100 % no significa que el dispositivo esté al límite. Solo dice que siempre había algo en marcha. En un NVMe que atiende decenas de peticiones a la vez puede quedar mucho margen. La señal fiable es la combinación: latencia que sube a la vez que crece la cola, de forma sostenida.
Para saber qué proceso genera la E/S:
pidstat -d 1
Y cat /proc/pressure/io dice cuánto tiempo pasan las tareas paradas esperando a disco, que es justo lo que siente el usuario.
En una máquina virtual, el disco que ves suele ser un volumen en un almacenamiento compartido. Si la latencia es alta y tu carga no ha cambiado, el problema puede estar en otra máquina que comparte cabina contigo. Comprar un disco más rápido sin saber de dónde sale la E/S puede subir la capacidad y dejar intacto el problema de fondo.
4. Red: retransmisiones, descartes y colas de conexión
Los problemas de red pocas veces tumban el servicio. Lo normal es que siga respondiendo, pero que las conexiones tarden más en establecerse, que haya que retransmitir o que se pierdan paquetes cuando sube la carga.
ip -s link
Busca contadores de errores (errors) y descartes (dropped) que crezcan. Un valor distinto de cero no dice nada por sí solo; lo que dice algo es que aumente justo cuando los usuarios notan la lentitud.
Para las conexiones TCP:
ss -ti
nstat
ss -ti enseña por cada conexión el tiempo de ida y vuelta (rtt), la ventana de congestión y las retransmisiones (retrans). nstat muestra contadores de todo el sistema, y por defecto enseña lo que ha cambiado desde la última vez que se ejecutó, así que la segunda ejecución es la interesante.
Hay una señal que se pasa por alto con facilidad: la cola de conexiones pendientes de un servicio que escucha. Cuando una aplicación no acepta conexiones al ritmo al que llegan, el kernel las va acumulando y, cuando la cola se llena, empieza a descartarlas. El cliente lo vive como una conexión que tarda segundos en abrirse, porque el SYN se reintenta.
ss -lnt
nstat -az TcpExtListenOverflows TcpExtListenDrops
En un socket en estado LISTEN, las dos primeras columnas de ss no significan lo mismo que en una conexión normal: Recv-Q es cuántas conexiones esperan a que la aplicación las acepte y Send-Q es el tamaño máximo de esa cola. Lo hemos reproducido con un servicio de prueba que escucha con una cola de 4 y no acepta nada, y 20 clientes intentando conectar:
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 5 4 127.0.0.1:8080 0.0.0.0:*
TcpExtListenOverflows 45 0.0
TcpExtListenDrops 45 0.0
Recv-Q igual o mayor que Send-Q y ListenOverflows creciendo es la firma de una aplicación que no da abasto o tiene la cola demasiado corta. Para quien desarrolla, la pista suele estar en el parámetro backlog del servidor web o del framework, o en un bucle de eventos bloqueado que no vuelve a aceptar conexiones.
En servidores que hacen de router, pasarela o cortafuegos, otra tabla que se llena es la de seguimiento de conexiones (conntrack). El kernel lo deja escrito en el registro como nf_conntrack: table full, dropping packet, y se puede comparar /proc/sys/net/netfilter/nf_conntrack_count con nf_conntrack_max.
El diagnóstico de red debe seguir los datos. Más ancho de banda no arregla la pérdida de paquetes, una tabla de conexiones llena ni una dependencia lenta al otro lado.
5. Límites de procesos y contenedores: cuando el sistema dice que no
A veces el host tiene CPU, memoria y disco de sobra, pero una aplicación concreta no puede usarlos. Los límites de Linux, la configuración de systemd y las cuotas de los contenedores actúan por debajo del nivel de la máquina.
Descriptores de fichero
Linux usa descriptores de fichero para ficheros abiertos, sockets y otros recursos. Un servicio que llega a su límite deja de aceptar conexiones o de abrir ficheros, y en los registros de la aplicación aparece Too many open files (el error EMFILE).
El error más común al investigarlo es mirar el límite equivocado:
ulimit -n # el de tu shell, no el del servicio
cat /proc/PID/limits # el del proceso que importa
ls /proc/PID/fd | wc -l # cuántos tiene abiertos ahora
systemctl show nginx -p LimitNOFILE
Un servicio que arranca systemd no hereda el ulimit de tu sesión. Usa el LimitNOFILE de su unidad o, si no lo define, el valor por defecto de systemd, que desde la versión 240 es 1024 como límite blando y 524288 como límite duro. Lo hemos comprobado en el system.conf de Debian 12. Ese 1024 se queda corto para un proxy o una base de datos con muchas conexiones. Pero antes de subirlo, mira si el número de descriptores abiertos crece sin parar: entonces lo que hay es una fuga de conexiones, y un límite más alto solo retrasa el fallo.
Cuotas de CPU y memoria en cgroups
Con cgroup v2, que es lo que usan Debian 12 y 13 y Ubuntu 22.04 y posteriores, cada servicio y cada contenedor tienen su propio grupo con sus contadores. Lo primero es localizarlo:
cat /proc/PID/cgroup
# 0::/system.slice/nginx.service
Los ficheros de ese grupo están en /sys/fs/cgroup/ seguido de esa ruta. El borrador de partida leía /sys/fs/cgroup/cpu.stat directamente, y eso en el host describe la raíz, no el servicio. Dentro de un contenedor sí suele funcionar, porque el contenedor ve su propio grupo como raíz.
Para ver la diferencia entre un límite y un problema real, hemos limitado un contenedor a media CPU (--cpus=0.5) y lo hemos puesto a girar en un bucle durante cinco segundos:
cpu.max 50000 100000
nr_periods 51
nr_throttled 50
throttled_usec 2497837
cpu.pressure some avg10=15.84
cpu.max dice que el grupo puede usar 50 ms de cada 100 ms. nr_throttled en 50 de 51 periodos dice que se ha topado con ese techo casi siempre: 2,5 segundos de los 5 pasó congelado. Desde dentro, la aplicación solo nota que todo tarda el doble, y la CPU del host puede estar casi ociosa. Si un servicio en contenedor va lento y nr_throttled crece, el problema es la cuota, no el código ni la máquina.
Con la memoria pasa algo parecido. Un contenedor limitado a 64 MB que intenta reservar más:
memory.max 67108864
max 38
oom 1
oom_kill 1
El proceso termina con código 137 (matado con SIGKILL), memory.events cuenta un oom_kill y el host no se ha quedado sin memoria en ningún momento. El contador max (38 en la prueba) es el número de veces que el grupo rozó el límite. Si sube a menudo sin que llegue a haber OOM, el servicio vive con el kernel recuperando memoria a su costa, que es otra forma de ir lento.
systemd-cgtop enseña en directo el consumo de cada grupo, y es la forma más rápida de ver qué servicio se lleva la máquina.
6. Latencia de la aplicación: cuando el servidor está bien y la petición no
La última señal es la que más se escapa a una monitorización centrada en la infraestructura. CPU normal, memoria holgada, discos tranquilos, y los usuarios siguen esperando.
El motivo puede estar dentro de la aplicación o en algo de lo que depende: una consulta lenta a la base de datos, una resolución DNS que tarda, un servicio de autenticación saturado o una API externa que responde despacio.
El libro de SRE de Google propone vigilar cuatro señales: latencia, tráfico, errores y saturación. La latencia, el tiempo que tarda una petición en completarse, es el complemento natural de todas las métricas del sistema operativo que hemos visto hasta aquí.
Para una primera comprobación de un servicio HTTP:
curl -o /dev/null -s -w \
'DNS: %{time_namelookup}s\nConexión: %{time_connect}s\nTLS: %{time_appconnect}s\nPrimer byte: %{time_starttransfer}s\nTotal: %{time_total}s\n' \
https://ejemplo.com
Hay un detalle que el borrador no explicaba y que lleva a malas lecturas: los tiempos son acumulados, todos medidos desde el inicio de la petición. Si DNS marca 1,08 s y Conexión 1,13 s, la conexión TCP no tardó 1,13 segundos; tardó 0,05, y el resto fue resolver el nombre. Para saber cuánto tarda cada fase, resta la anterior. La diferencia entre TLS y Primer byte es lo que tardó tu aplicación en empezar a responder, que suele ser el número que más interesa a quien desarrolla.
Una sola petición no es un patrón. En producción hay que mirar la distribución de la latencia, no la media. El percentil 95 (P95) es el tiempo por debajo del cual terminan el 95 % de las peticiones; el P99 enseña ese 1 % de peticiones lentas que la media esconde. Una API con una media de 80 ms y un P99 de 4 segundos tiene un problema que el gráfico de la media nunca va a enseñar.
Después, compara esa latencia con el tráfico y los errores. Si sube a la vez que el volumen de peticiones, el servicio se está acercando a su capacidad. Si solo se ralentizan las peticiones que tocan una base de datos o una API concreta, esa dependencia es la sospechosa. Los registros de consultas lentas de la base de datos y las trazas distribuidas (OpenTelemetry) son las herramientas para seguirla.
Cómo usar las seis señales juntas
Ninguna métrica explica sola todas las lentitudes. Brendan Gregg, que lleva años escribiendo sobre rendimiento en Linux, propone un orden sencillo con su método USE: para cada recurso (CPU, memoria, disco, red), mirar su uso, su saturación (la cola) y sus errores. Las cuatro señales de Google hacen lo mismo desde el lado del servicio. Combinarlas cubre las dos mitades: si la máquina no da más de sí y si la aplicación está esperando a algo.
| Señal | Primer comando | Qué mirar |
|---|
| CPU | vmstat 1, mpstat -P ALL 1 | Cola r frente a núcleos, st en máquinas virtuales, un núcleo saturado |
| Memoria | free -m, /proc/pressure/memory | available, actividad si/so, PSI, OOM en journalctl -k |
| Disco | iostat -xyz 1, pidstat -d 1 | r_await/w_await con aqu-sz; %util solo en discos sin paralelismo |
| Red | ip -s link, ss -ti, ss -lnt | Descartes que crecen, retransmisiones, Recv-Q en sockets que escuchan |
| Límites | /proc/PID/limits, ficheros del cgroup | LimitNOFILE, nr_throttled, oom_kill y max del grupo correcto |
| Aplicación | curl -w, métricas de la aplicación | P95/P99, fases restadas de curl, consultas y dependencias lentas |
El orden importa menos que reunir pruebas antes de cambiar nada. Si la CPU está saturada, busca la carga. Si hay presión de memoria, encuentra los procesos responsables. Si la latencia de disco sube, averigua qué operaciones la generan. Si la máquina parece sana y las peticiones siguen siendo lentas, sigue la petición por la aplicación y sus dependencias. Así es mucho más fácil comprobar después si el cambio ha servido.
Para la base del servidor antes de que haya un problema, está la guía de hardening para Debian y Ubuntu, y si el servidor lo has heredado y no sabes qué corre en él, cómo auditar un servidor Linux heredado. Si la columna st de tus máquinas sube a menudo, o la latencia del disco cambia sin que cambie tu carga, la conversación es con tu proveedor, no con tu código. En Cloud Privado podemos ayudarte a averiguar si tu problema se arregla con más máquina, con otra configuración o con otra infraestructura.
Preguntas frecuentes
¿Cómo sé si un servidor Linux tiene un cuello de botella de CPU?
Con vmstat 1: si la columna r (procesos ejecutándose o esperando CPU) se mantiene por encima del número de núcleos durante minutos, hay contención. pidstat -u 1 dice qué procesos consumen, y mpstat -P ALL 1 si la carga se concentra en un solo núcleo. En máquinas virtuales, un st alto indica que el problema está en el hipervisor.
¿Usar swap significa que el servidor necesita más RAM?
No necesariamente. Puede haber páginas inactivas en swap durante días sin ningún efecto. Lo preocupante es la actividad continua (si y so en vmstat) junto con presión de memoria en /proc/pressure/memory y aplicaciones más lentas.
¿Por qué iostat no muestra la columna await?
Porque las versiones actuales de sysstat la dividen en r_await (lecturas), w_await (escrituras), d_await (descartes) y f_await (vaciados de caché). Es lo que muestran Debian 12 y 13 y Ubuntu 24.04. Separadas son más útiles: dicen si lo lento es leer o escribir.
¿Un %util del 100 % en iostat significa que el disco está saturado?
En un disco mecánico, prácticamente sí. En un SSD NVMe o un RAID, no: solo indica que siempre había alguna petición en curso, y estos dispositivos atienden muchas a la vez. La señal fiable es una latencia que sube a la vez que la cola (aqu-sz).
¿Por qué mi aplicación va lenta si la CPU y la memoria del servidor están bien?
Puede estar limitada por la cuota de su contenedor (mira nr_throttled en el cpu.stat de su cgroup), haber llegado a su límite de descriptores de fichero, tener llena la cola de conexiones o estar esperando a una base de datos, al DNS o a una API externa. Las métricas de latencia por petición y por dependencia son las que lo localizan.
Fuentes
- systemadministration.net, Slow Linux Server? 6 Warning Signs to Find the Bottleneck: el artículo que originó este.
- Google, Site Reliability Engineering: Monitoring Distributed Systems: las cuatro señales de oro.
- Brendan Gregg, The USE Method: uso, saturación y errores por recurso.
- Documentación del kernel Linux, PSI - Pressure Stall Information: significado de
some, full y las medias.
- Documentación del kernel Linux, Control Group v2:
cpu.max, cpu.stat, memory.max y memory.events.
- Documentación del kernel Linux, Netfilter Conntrack Sysfs variables:
nf_conntrack_max y nf_conntrack_count.
- Manuales de vmstat(8), iostat(1), pidstat(1) y ss(8).
- Manual de systemd.exec(5):
LimitNOFILE y los límites de los servicios.
- curl, manual de la línea de órdenes: variables de
--write-out y cómo se miden los tiempos.
Las columnas de vmstat e iostat, el valor por defecto de LimitNOFILE y las pruebas de cuota de CPU, OOM en un cgroup y desbordamiento de la cola de escucha salen de pruebas propias en contenedores con las imágenes oficiales de Debian 12, Debian 13 y Ubuntu 24.04, hechas el 14 de octubre de 2026.