Los procesos zombie son uno de esos fenómenos que todo administrador de sistemas GNU/Linux acaba encontrando tarde o temprano. Aunque en la mayoría de los casos son inofensivos, entender su origen y saber gestionarlos correctamente marca la diferencia entre un sistema bien administrado y uno que acumula entradas fantasma en la tabla de procesos. Esta guía cubre todo lo que necesitas saber en 2026.
¿Qué es un proceso zombie?
En Unix/Linux, cuando un proceso termina su ejecución, el kernel no lo elimina de inmediato de la tabla de procesos. En su lugar, el proceso entra en el estado Z (zombie) y permanece en la tabla hasta que su proceso padre recoge su código de salida mediante la llamada al sistema wait() o waitpid().
El ciclo de vida de un proceso sigue este flujo:
- fork() — El proceso padre crea un hijo.
- exec() — El hijo carga y ejecuta un nuevo programa.
- exit() — El hijo finaliza y pasa a estado zombie.
- wait() — El padre recoge el estado de salida; el zombie desaparece.
El kernel envía la señal SIGCHLD al proceso padre cuando un hijo termina. Si el padre la ignora o no tiene un manejador que llame a wait(), el proceso hijo permanece en estado zombie indefinidamente. El zombie no consume CPU ni memoria real, pero sí ocupa una entrada en la tabla de procesos (que tiene un límite finito) y un PID (también limitado por /proc/sys/kernel/pid_max).
¿Cómo se crean los procesos zombie?
La causa es siempre la misma: el proceso padre no recoge el estado de salida del hijo. El siguiente ejemplo en C crea deliberadamente un zombie:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
pid_t pid = fork();
if (pid == 0) {
/* Proceso hijo: termina inmediatamente */
printf("Hijo (PID %d): terminando.\n", getpid());
exit(0);
}
/* Proceso padre: duerme sin llamar a wait() */
printf("Padre (PID %d): hijo creado con PID %d.\n", getpid(), pid);
printf("Padre: durmiendo 60 segundos sin recoger al hijo...\n");
sleep(60);
return 0;
}Durante esos 60 segundos, el hijo aparecerá en la tabla de procesos con el estado Z (zombie / defunct).
Detección de procesos zombie
Existen varios métodos para identificar zombies en el sistema. A diferencia de otros problemas de recursos, los zombies no mantienen ficheros abiertos ni sockets, por lo que herramientas como lsof o ss no son útiles aquí.
Con ps
# Método rápido: buscar procesos con estado Z
ps aux | grep Z
# Método detallado: stat, PID, PPID y comando
ps -eo stat,pid,ppid,cmd | grep ^Z
# Ejemplo de salida:
# Z 4821 4820 [my_app] <defunct>Con top
Ejecuta top y observa la segunda línea de resumen. Verás algo como:
Tasks: 212 total, 1 running, 210 sleeping, 0 stopped, 1 zombieEl número al final de la línea indica los procesos zombie activos.
Con /proc
# Verificar el estado de un proceso concreto por su PID
cat /proc/4821/status | grep State
# Salida esperada si es zombie:
# State: Z (zombie)Eliminación de procesos zombie
No puedes matar un proceso zombie directamente. Ya está muerto; simplemente no ha sido recogido. Intentar kill -9 <pid_zombie> no tendrá efecto alguno. Las opciones son:
1. Enviar SIGCHLD al padre
La señal SIGCHLD le indica al padre que tiene hijos pendientes de recoger. Si el padre tiene un manejador correcto, debería llamar a wait() y limpiar el zombie:
# Obtener el PPID del zombie
ps -eo stat,pid,ppid,cmd | grep ^Z
# Z 4821 4820 [my_app] <defunct>
# Enviar SIGCHLD al padre (PPID = 4820)
kill -s SIGCHLD 48202. Matar al proceso padre
Si el padre no responde a SIGCHLD, la solución es terminar el proceso padre. Cuando el padre muere, el zombie queda huérfano y es adoptado por init / systemd (PID 1), que actúa como reaper global y llama a wait() de forma periódica, eliminando el zombie:
kill -9 4820One-liner: eliminar los padres de todos los zombies
ps -eo stat,ppid | awk '/^Z/{print $2}' | sort -u | xargs -r kill -9Úsalo con precaución: terminar el padre puede tener consecuencias en producción si el padre es un servicio crítico.
Prevención: buenas prácticas
Manejo correcto de SIGCHLD en C
La forma más sencilla es indicar al kernel que ignore la señal SIGCHLD, lo que le indica que limpie los hijos automáticamente sin necesitar que el padre llame a wait():
#include <signal.h>
#include <sys/wait.h>
/* Opción 1: ignorar SIGCHLD (el kernel limpia automáticamente) */
signal(SIGCHLD, SIG_IGN);
/* Opción 2: manejador explícito que llama a waitpid en bucle */
void sigchld_handler(int sig) {
int saved_errno = errno;
while (waitpid(-1, NULL, WNOHANG) > 0);
errno = saved_errno;
}
signal(SIGCHLD, sigchld_handler);Técnica del doble fork
El doble fork garantiza que el nieto quede huérfano de inmediato (adoptado por init), eliminando la responsabilidad del proceso original de hacer wait():
pid_t pid = fork();
if (pid == 0) {
/* Primer hijo: hace un segundo fork y termina */
if (fork() == 0) {
/* Nieto: hace el trabajo real; su padre (primer hijo) ya murió */
do_work();
exit(0);
}
exit(0); /* Primer hijo termina inmediatamente */
}
waitpid(pid, NULL, 0); /* Padre recoge solo al primer hijo */Prevención con systemd (unidades de servicio)
Para servicios gestionados por systemd, estas directivas ayudan a limpiar procesos huérfanos correctamente:
[Service]
# Mata todos los procesos del cgroup al parar el servicio
KillMode=control-group
# Considera exitosos los códigos de salida adicionales
SuccessExitStatus=143
# Tiempo de espera antes de SIGKILL
TimeoutStopSec=10Zombies en contenedores Docker
Los contenedores Docker presentan un escenario especialmente problemático. Por defecto, el proceso con PID 1 dentro del contenedor es la propia aplicación (no init ni systemd). Si esa aplicación hace fork sin gestionar SIGCHLD, los zombies se acumulan sin que nadie los recoja.
Solución: flag --init
# Docker inyecta tini como PID 1, que actúa como mini-init
docker run --init my-image
# O explícitamente con tini en el Dockerfile
ENTRYPOINT ["/sbin/tini", "--", "my-app"]Solución en Kubernetes
spec:
# Comparte el espacio de PIDs entre contenedores del pod;
# el proceso de pausa del pod actúa como reaper
shareProcessNamespace: true
containers:
- name: my-app
image: my-imageZombies y systemd: el concepto de subreaper
Desde Linux 3.4, un proceso puede declararse a sí mismo como subreaper de su subárbol de procesos usando la syscall prctl(PR_SET_CHILD_SUBREAPER, 1). systemd hace exactamente esto: se convierte en el reaper de todos los procesos del sistema que queden huérfanos, garantizando que ningún zombie persista indefinidamente.
Esto explica por qué en un sistema con systemd los zombies transitorios se limpian solos cuando el padre muere, incluso si el padre no era PID 1.
Monitorización de procesos zombie
En entornos de producción, es recomendable alertar cuando el número de zombies supera un umbral. Aquí dos enfoques:
Script de monitorización simple
#!/bin/bash
# check_zombies.sh — alerta si hay más de N zombies
THRESHOLD=5
ZOMBIE_COUNT=$(ps -eo stat | grep -c '^Z')
if [ "$ZOMBIE_COUNT" -gt "$THRESHOLD" ]; then
echo "ALERTA: $ZOMBIE_COUNT procesos zombie detectados en $(hostname)"
ps -eo stat,pid,ppid,cmd | grep ^Z
exit 1
fi
echo "OK: $ZOMBIE_COUNT zombies (umbral: $THRESHOLD)"
exit 0Con Prometheus node_exporter
Si usas Prometheus con node_exporter, la métrica de zombies está disponible de forma nativa. Puedes crear una alerta en tu fichero de reglas:
groups:
- name: process_alerts
rules:
- alert: ZombieProcessesHigh
expr: node_processes_state{state="zombie"} > 5
for: 10m
labels:
severity: warning
annotations:
summary: "Número elevado de procesos zombie en {{ $labels.instance }}"
description: "{{ $value }} procesos zombie llevan más de 10 minutos en {{ $labels.instance }}."La métrica que expone node_exporter es node_processes_state{state="zombie"} y se actualiza en cada scrape.
:wq!
Comentarios