Administración de Linux
Logs y resolución de problemas en Linux: journalctl, rotación, OOM, carga y strace
Avanzado12 min de lecturaActualizado
Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés
Bajo presión, la mayoría diagnostica a ciegas y reinicia. Esta guía sustituye esa costumbre por un método: averiguar qué ha cambiado, leer el log correcto y medir un recurso cada vez. Cubre journald y los ficheros de /var/log, la rotación y retención de logs, y los fallos recurrentes —discos llenos, el OOM killer, carga alta con las CPU ociosas, procesos colgados y DNS—, cada uno con el comando que lo identifica.
Lo que aprenderás
- Aplicar una secuencia de triaje repetible en lugar de reiniciar servicios y cruzar los dedos
- Consultar el journal por unidad, prioridad, arranque y franja horaria, y saber qué sigue escribiendo en /var/log
- Configurar la rotación de logs y la retención del journal para que el historial nunca llene el disco
- Distinguir entre saturación de CPU, espera de I/O, presión de memoria y OOM kills
- Inspeccionar un proceso en ejecución o colgado con lsof, strace, /proc y dmesg
Resolver problemas en Linux es una habilidad con estructura. Quienes son rápidos no se saben más comandos que el resto: siguen una secuencia, descartan una capa cada vez y leen el log antes de formular una teoría. Quienes son lentos reinician el servicio, ven que el síntoma vuelve, lo reinician otra vez y solo miran los logs al cabo de una hora.
Esta guía trata de esa secuencia. Explica adónde va realmente la salida de logs en un host con systemd, cómo consultarla y cómo evitar que llene el disco, y después repasa los fallos que se repiten en cualquier servidor: sin espacio, sin memoria, carga sin CPU, un proceso que ha dejado de responder y una dependencia que ha desaparecido. Da por hecho que conoces los procesos y systemd y el orden de diagnóstico de los fundamentos de redes.
Triaje antes que herramientas
Antes de teclear nada, responde a tres preguntas. ¿Cuál es el síntoma real —un error concreto, un timeout, un resultado incorrecto—, y no «la web se ha caído»? ¿Cuándo empezó, y coincide con un despliegue, un cambio de configuración, la caducidad de un certificado, una tarea programada o un aumento de tráfico? ¿Cuál es el alcance —un endpoint, un host, un cliente, todo?
Después recorre las capas en un orden fijo y detente en la primera que falle:
- ¿El servicio está en marcha y se ha reiniciado hace poco?
systemctl status,systemctl show -p NRestarts. - ¿Qué dice su log en torno a la hora de inicio?
journalctl -u … --since. - ¿Hay algún recurso agotado? Disco, inodos, memoria, descriptores de fichero, conexiones.
- ¿Sus dependencias están sanas? Base de datos, caché, API upstream, DNS.
- ¿Ha cambiado algo?
journalctl -u … --since, historial de paquetes, configuración en el control de versiones.
Anota sobre la marcha lo que compruebas y lo que encuentras. A los diez minutos de incidente nadie recuerda si ya se miró el disco, y una nota compartida marca la diferencia entre dos personas investigando y dos personas repitiendo el trabajo de la otra.
Adónde va la salida
En un host con systemd conviven dos mundos de logs, y necesitas los dos.
El journal recoge todo lo que una unidad escribe en su salida estándar y de error, además de los mensajes del kernel y todo lo que llega por la API de syslog. Es un almacén binario estructurado: cada entrada lleva campos como _SYSTEMD_UNIT, _PID, _UID, PRIORITY, _BOOT_ID y MESSAGE, y eso es lo que permite filtrar con precisión. Se guarda en /run/log/journal (volátil, se pierde al reiniciar) o en /var/log/journal (persistente) según el valor de Storage= en /etc/systemd/journald.conf. Si journalctl -b -1 no devuelve entradas, el almacenamiento persistente está desactivado; crea /var/log/journal y reinicia systemd-journald para activarlo.
Los ficheros de texto en /var/log los escriben los programas que gestionan su propio logging: /var/log/apache2/, /var/log/nginx/, /var/log/mysql/, /var/log/audit/audit.log. Las distribuciones que aún ejecutan rsyslog reciben también una copia del journal, que produce /var/log/syslog y /var/log/messages, además de /var/log/auth.log o /var/log/secure para los eventos de autenticación.
Saber qué va dónde ahorra tiempo. Una página PHP que falla está en el log de errores del pool de FPM y en el del virtual host de Apache, no en el journal; un servicio que no arranca está en el journal, no en ningún fichero que controle la aplicación.
Consultar el journal
journalctl -u nginx.service -n 100 --no-pager
journalctl -u orders-api -f # follow
journalctl -u orders-api --since "2026-09-07 14:00" --until "2026-09-07 14:30"
journalctl -p err -b # this boot, error and worse
journalctl -b -1 -p warning # previous boot
journalctl -k --since "30 min ago" # kernel ring buffer, time-filtered
journalctl _UID=33 --since today # everything by one user id
journalctl _PID=1842
journalctl -u orders-api -o json-pretty | head -40
journalctl --list-boots
Las prioridades van de emerg (0) a debug (7), y -p err significa «err y más grave». Las expresiones de tiempo admiten "10 min ago", yesterday, today y marcas absolutas. -o cat deja solo los mensajes, sin metadatos, que es justo lo que quieres antes de pasarlos a los pipelines de grep, sed y awk:
journalctl -u orders-api --since today -o cat \
| grep -oE 'status=[0-9]{3}' | sort | uniq -c | sort -rn
Durante un incidente conviene conocer dos opciones. journalctl -u a -u b --since … fusiona varias unidades en un único flujo ordenado en el tiempo, y así ves en la misma vista cómo una petición llega al proxy y falla en el backend. Y journalctl -f -u a -u b -p warning ofrece un flujo filtrado en directo durante un despliegue.
Los usuarios que no son root solo ven sus propias entradas, salvo que pertenezcan al grupo systemd-journal; merece la pena configurarlo para quien tenga que depurar sin sudo completo.
Rotación y retención
Los logs sin límite llenan los discos, y un disco lleno tumba de golpe todos los servicios de la máquina. Los dos mundos de logs necesitan un tope.
Para el journal, fíjalo en /etc/systemd/journald.conf:
[Journal]
Storage=persistent
SystemMaxUse=2G
SystemKeepFree=1G
MaxRetentionSec=30day
Compress=yes
sudo systemctl restart systemd-journald
journalctl --disk-usage
sudo journalctl --vacuum-size=1G # reclaim now
sudo journalctl --vacuum-time=14d
Para los ficheros, logrotate se ejecuta desde un timer de systemd y procesa las configuraciones que se dejan en /etc/logrotate.d/:
/var/log/myapp/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
create 0640 myapp adm
sharedscripts
postrotate
systemctl reload myapp.service > /dev/null 2>&1 || true
endscript
}
El mecanismo importa. logrotate renombra el fichero actual y crea uno nuevo, pero un proceso que ya tenía abierto el antiguo sigue escribiendo en él: el fichero renombrado crece mientras el nuevo sigue vacío. Hay dos soluciones: postrotate indica al servicio que reabra sus ficheros (lo que hace el reload de arriba), o bien usas copytruncate, que copia el contenido y trunca el original en su sitio. copytruncate pierde las líneas escritas durante la copia, así que prefiere la señal cuando la aplicación la admita.
Prueba una configuración antes de fiarte de ella:
sudo logrotate -d /etc/logrotate.d/myapp # debug: say what would happen
sudo logrotate -f /etc/logrotate.d/myapp # force a rotation now
cat /var/lib/logrotate/status | grep myapp
Los primeros sesenta segundos en una máquina enferma
Un repaso rápido que apenas cuesta nada y que a menudo cierra la investigación:
uptime # load average: 1, 5, 15 minutes
free -h # available memory, swap in use
df -h; df -i # space and inodes
systemctl --failed # units that gave up
journalctl -p err -b --since "1 hour ago" | tail -50
dmesg -T --level=err,warn | tail -30 # kernel complaints, human timestamps
sudo ss -s # socket counts by state
vmstat 1 5 # r, b, si/so, us/sy/id/wa over five seconds
Las columnas de vmstat son un diagnóstico en sí mismas: una r alta indica procesos en cola esperando CPU; una b alta, procesos bloqueados en I/O; si/so distintos de cero, swapping activo; y un wa alto en el grupo de CPU, procesadores ociosos porque están esperando al almacenamiento.
Disco e inodos agotados
df -h informa de bytes y df -i de inodos; un sistema de ficheros puede llenarse en cualquiera de las dos dimensiones, y una partición con el 100 % de los inodos ocupados y un 40 % de espacio libre desconcierta a todo el mundo, siempre. La causa habitual son millones de ficheros pequeños de sesión, de caché o de la cola de correo.
Cuando el problema son los bytes, busca lo que crece y no lo que ocupa: la mayor parte del disco de un sistema está ocupada legítimamente, y lo que te interesa es lo que ha cambiado:
sudo du -x --max-depth=2 /var 2>/dev/null | sort -n | tail -15
sudo find /var/log -type f -size +200M -printf '%s\t%p\n' | sort -rn | head
sudo find / -xdev -type f -mtime -1 -size +100M -printf '%TF %s %p\n' 2>/dev/null | sort -rn | head
Recuperar espacio con seguridad implica truncar en lugar de borrar cualquier fichero que un servicio mantenga abierto: sudo truncate -s 0 /var/log/myapp/huge.log devuelve los bloques al instante y deja el descriptor válido. Después elimina la causa: un nivel de log debug que se quedó activado tras un despliegue, una regla de rotación que nunca se escribió, un patrón de core dump que vuelca gigabytes en el directorio de trabajo o un journal sin SystemMaxUse.
Deja a propósito un pequeño porcentaje de margen. Las bases de datos, los gestores de paquetes y el journal se comportan mal con cero bytes libres, y recuperar una máquina que no tiene sitio ni para escribir un fichero temporal es mucho más difícil de lo que parece.
Presión de memoria y el OOM killer
En free -h solo importa una columna: available. Estima cuánta memoria podría obtener un proceso nuevo, contando la page cache que se puede recuperar. Linux usa deliberadamente toda la memoria sobrante como caché, así que una cifra de «free» baja con una de «available» holgada es un sistema funcionando bien, no un problema.
Cuando la memoria disponible se agota de verdad y el swap no puede absorberlo, el kernel invoca al OOM killer, que elige una víctima según una puntuación de «badness» que penaliza a los procesos grandes, arrancados recientemente y sin privilegios, y la mata con una señal que no se puede capturar. La prueba está en el log del kernel:
$ sudo dmesg -T | grep -i -A2 'out of memory'
[Sun Sep 7 03:14:22 2026] Out of memory: Killed process 4821 (java) total-vm:6291456kB,
anon-rss:3874112kB, file-rss:0kB, shmem-rss:0kB, UID:1001 pgtables:8192kB oom_score_adj:0
journalctl -k --since today | grep -i 'killed process'
systemctl status orders-api | grep -E 'Active|Main PID|status'
cat /sys/fs/cgroup/system.slice/orders-api.service/memory.peak
La firma es inconfundible: un servicio que desaparece sin ningún mensaje de parada en su propio log y una línea del kernel que lo nombra. systemctl status muestra la unidad como fallida con la señal KILL.
Las soluciones van ordenadas según lo bien que funcionan. Limita al culpable en la propia unidad —MemoryMax=2G y MemoryHigh=1600M en el fichero del servicio— para que el kernel frene y mate dentro de ese cgroup en lugar de elegir una víctima en todo el sistema. Corrige los límites de la propia aplicación, como un heap de la JVM mayor que la máquina o un pool de FPM con demasiados workers. Añade swap, que convierte una muerte en seco en una degradación lenta y da tiempo para reaccionar. Ajusta oom_score_adj para un proceso que tenga que sobrevivir, como la base de datos en un host de bases de datos. Añadir RAM sin entender cuál de estos casos se aplica suele limitarse a aplazar el fallo.
Carga, CPU e I/O
El load average cuenta los procesos ejecutables y los que están en sueño no interrumpible. Ese segundo grupo es la razón de que en Linux la carga no sea una métrica de CPU.
uptime
nproc # compare load against this
top -b -n1 | head -15
pidstat -u -p ALL 2 3 # per-process CPU over three samples
iostat -xz 2 3 # per-device utilisation, queue depth, await
ps -eo pid,stat,wchan:24,cmd | awk '$2 ~ /^D/' # what the blocked processes are waiting on
Interpreta la combinación:
- Carga alta,
usalto,wabajo: saturación real de CPU. Localiza el proceso conpidstat; haz profiling o escala horizontalmente. - Carga alta,
waalto, procesos enD: almacenamiento o sistema de ficheros en red.iostat -xmuestraawaity%utilpor dispositivo; un disco saturado o un montaje NFS bloqueado producen exactamente este cuadro. - Carga alta,
syalto: tiempo de kernel; a menudo una tormenta de interrupciones, un número desmesurado de cambios de contexto o un programa que llama al kernel con demasiada frecuencia.vmstat 1muestracsein. - Carga baja, aplicación lenta: el cuello de botella está en otra parte. Un lock, una consulta a la base de datos, un servicio upstream. Eso lo responden las métricas de la aplicación, no las del host, y ese es precisamente el argumento a favor de la instrumentación que se describe en actuator y observabilidad.
Mirar dentro de un proceso
Cuando un proceso concreto se porta mal, cuatro herramientas revelan qué está haciendo sin necesidad de un depurador.
sudo lsof -p 4821 | head -30 # open files, sockets, libraries
sudo lsof -i :8080 # who holds a port
sudo lsof /var/lib/mysql # who is using a path
ls -l /proc/4821/fd | wc -l # file descriptor count
cat /proc/4821/limits | grep 'open files' # against the limit
cat /proc/4821/status | grep -E 'State|Threads|VmRSS'
sudo cat /proc/4821/stack # kernel stack of a hung task
El agotamiento de descriptores es un fallo frecuente y desconcertante: la aplicación registra «too many open files», deja de aceptar conexiones nuevas y parece sana en todas las métricas del host. Compara el recuento con el límite y súbelo en la unidad con LimitNOFILE= en lugar de hacerlo en un perfil de shell, que un servicio nunca lee.
strace muestra las llamadas al sistema que hace un proceso. Engánchate a un proceso en ejecución y empieza por el resumen, porque la salida en bruto abruma:
sudo strace -c -p 4821 # counts and time per syscall; Ctrl-C to report
sudo strace -f -e trace=openat,connect -p 4821
sudo strace -f -T -o /tmp/trace.txt -p 4821
Un resumen dominado por futex apunta a contención de locks; uno dominado por openat que devuelven ENOENT suele indicar una ruta de búsqueda incorrecta; llamadas connect repetidas que devuelven ECONNREFUSED o ETIMEDOUT señalan la dependencia que ha desaparecido. Engancharse pausa brevemente el proceso y lo ralentiza de forma notable, así que ten cuidado con un proceso de producción muy cargado y prefiere una captura corta. ltrace hace lo mismo con las llamadas a bibliotecas; perf top muestrea en qué se gasta realmente el tiempo de CPU.
Mensajes del kernel
dmesg lee el ring buffer del kernel, donde aterrizan los eventos del hardware y de los drivers antes de que ningún demonio de logs los vea. dmesg -T añade marcas de tiempo legibles y dmesg -w lo sigue en directo.
Mensajes que conviene reconocer: EXT4-fs error e I/O error, dev sda indican un almacenamiento que falla; revisa smartctl -a /dev/sda de inmediato. nf_conntrack: table full, dropping packet significa que la tabla de seguimiento de conexiones está llena, lo que se manifiesta como fallos de red aleatorios bajo carga. TCP: request_sock_TCP: Possible SYN flooding significa que se desbordó la cola de accept. segfault at … ip … registra la caída de un binario. Under memory pressure y oom-kill son el problema de memoria descrito más arriba.
Como el buffer tiene un tamaño fijo, los mensajes antiguos se sobrescriben. journalctl -k -b -1 obtiene los mensajes del kernel del arranque anterior cuando el ring buffer ya no los tiene, algo imprescindible tras un reinicio inexplicable.
Comprobar un servicio de punta a punta
Una pasada estructurada sobre un servicio, de fuera hacia dentro:
systemctl status orders-api # running? restarts? since when?
systemctl show orders-api -p NRestarts -p ExecMainStartTimestamp
journalctl -u orders-api -p warning --since "1 hour ago"
sudo ss -ltnp | grep 8080 # listening, and on what address
curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' http://127.0.0.1:8080/healthz
getent hosts db.internal && nc -vz db.internal 5432 # dependency reachable
sudo lsof -p "$(systemctl show -p MainPID --value orders-api)" | wc -l
journalctl -u orders-api --since "1 hour ago" -o cat | grep -c 'ERROR'
Cada línea descarta algo. Un NRestarts que sube indica que la unidad está en un bucle de caídas y que el log interesante está justo antes de cada reinicio. Un curl local que responde bien mientras fallan las peticiones externas lleva la investigación al proxy o al firewall. Una dependencia que no resuelve o que no acepta la conexión pone fin a la búsqueda de inmediato.
Después del incidente
Hay dos hábitos que se amortizan solos. Primero, captura las pruebas antes de reiniciar: un thread dump, los últimos cientos de líneas del journal, la salida de ps, free, df. Un reinicio suele hacer desaparecer el síntoma y destruir la explicación, y la siguiente vez el fallo es igual de misterioso.
Segundo, escribe una nota breve y sin buscar culpables: cronología, síntoma, causa raíz, qué dificultó el diagnóstico y un cambio concreto. Lo más valioso de un incidente a menudo no es la corrección, sino la alerta que faltaba, la línea de log que debería haber existido o el ajuste de retención por el que las pruebas de ayer ya habían desaparecido.
Errores comunes
- Reiniciar primero. Pierdes las pruebas y no aprendes nada; el fallo vuelve a las 03:00.
- Leer solo la última línea del log. La entrada interesante suele ser el primer error de la secuencia, antes de la cascada de consecuencias.
- Borrar un fichero de log grande que sigue abierto. No se libera espacio. Trunca y después arregla la rotación.
- Tomar el load average por uso de CPU. Mira
way los procesos en estadoDantes de concluir que necesitas más núcleos. - Asustarse porque la memoria «free» es baja. Lee la columna
available; la caché está haciendo su trabajo. - Dejar el logging en debug tras una investigación. Llenará el disco sin hacer ruido una semana después.
- No activar el almacenamiento persistente del journal. Todo lo relativo al fallo que provocó el reinicio se ha perdido.
- Ejecutar
strace -fdurante minutos sobre un proceso de producción cargado. La ralentización se convierte en el nuevo incidente.
Puntos clave
- Sigue un orden fijo —síntoma, momento, alcance; después servicio, log, recurso, dependencia, cambio— y anota lo que has descartado.
- El journal guarda la salida de las unidades y del kernel; las aplicaciones que gestionan sus propios ficheros siguen escribiendo en
/var/log. Revisa ambos. journalctl -u … -p err --since, combinando varias unidades y con-o catpara encadenar comandos, responde a la mayoría de las preguntas sobre logs.- Pon límite a los dos almacenes de logs:
SystemMaxUsepara el journal y una regla delogrotatecon un reload enpostrotatepara los ficheros. - Distingue entre bytes agotados e inodos agotados, y trunca los ficheros abiertos en lugar de borrarlos.
- Un OOM kill se ve en
dmesg; contenlo con límites de cgroup en la unidad en lugar de añadir RAM a ciegas. - Carga alta con las CPU ociosas y procesos en estado
Des un problema de almacenamiento o de red:iostat -xywchanlo señalarán. lsof,/proc/<pid>/,strace -cydmesg -Texplican qué está haciendo un proceso; captura las pruebas antes de reiniciar nada.
Fuentes y lecturas recomendadas
Preguntas frecuentes
- ¿Dónde están realmente los logs en un sistema moderno: en el journal o en /var/log?
- En los dos. Todo lo que arranca systemd envía su salida estándar y de error al journal, un almacén binario e indexado que se consulta con journalctl. Las aplicaciones que abren sus propios ficheros de log siguen escribiendo texto plano en /var/log; suelen ser servidores web, bases de datos y sistemas de correo. Muchas distribuciones reenvían además el journal a rsyslog, así que el mismo mensaje aparece en /var/log/syslog. Revisa ambos sitios antes de concluir que un mensaje no existe.
- ¿Por qué el disco se vuelve a llenar al día siguiente de borrar un fichero de log enorme?
- Borrar un fichero que un proceso mantiene abierto no libera espacio hasta que el proceso lo cierra, y el servicio sigue escribiendo en el mismo descriptor. Lo correcto es truncar el fichero en su sitio y después atajar la causa con rotación de logs y un límite de retención. Si lo que crece es el journal, define SystemMaxUse en journald.conf y recupera espacio al momento con journalctl --vacuum-size.
- El load average está en 20, pero top muestra las CPU casi ociosas. ¿Qué significa?
- En Linux el load average cuenta tanto los procesos ejecutables como los que están en sueño no interrumpible, de modo que la I/O de disco o de red bloqueada lo dispara sin consumir CPU. Mira la columna wa en top o en iostat, busca procesos en estado D y mide la latencia de los dispositivos con iostat -x. Una carga de 20 con un 90 % de CPU ociosa es un problema de almacenamiento o de sistema de ficheros en red, no de falta de potencia de cálculo.
- ¿Cómo sé si el kernel mató mi proceso o si terminó por su cuenta?
- Busca una línea Out of memory en dmesg o en journalctl -k en torno al momento en que desapareció; el kernel imprime la víctima elegida, su puntuación y una tabla de los procesos que más memoria consumían. systemctl status también informa del motivo de la salida y muestra la señal 9 cuando el OOM killer ha matado el proceso principal de una unidad. Un código de salida distinto de cero, limpio y sin mensaje del kernel significa que la aplicación decidió parar, así que la respuesta está en su propio log.