Zombie processes are one of those phenomena that every GNU/Linux system administrator ends up encountering sooner or later. Although in most cases they are harmless, understanding their origin and knowing how to manage them correctly makes the difference between a well-managed system and one that accumulates ghost entries in the process table. This guide covers everything you need to know in 2026.
What is a zombie process?
On Unix/Linux, when a process terminates execution, the kernel does not immediately remove it from the process table. Instead, the process enters the state Z (zombie) and remains in the table until its parent process collects its exit code via system call wait() either waitpid().
The life cycle of a process follows this flow:
- fork() — The parent process creates a child.
- exec() — The child loads and runs a new program.
- exit() —The son ends and goes into zombie state.
- wait() — The parent collects the exit status; the zombie disappears.
The kernel sends the signal SIGCHLD to the parent process when a child terminates. If the parent ignores it or does not have a handler to call wait(), the child process remains in the zombie state indefinitely. The zombie does not consume CPU or real memory, but it does occupy an entry in the process table (which has a finite limit) and a PID (also limited by /proc/sys/kernel/pid_max).
How are zombie processes created?
The cause is always the same: the parent process does not collect the exit status of the child. The following C example deliberately creates a 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;
}During those 60 seconds, the child will appear in the process table with the status Z (zombie/defunct).
Zombie process detection
There are several methods to identify zombies in the system. Unlike other resource problems, zombies they do not keep open files or sockets, so tools like lsof either ss They are not useful here.
with 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>With top
Run top and look at the second summary line. You will see something like:
Tasks: 212 total, 1 running, 210 sleeping, 0 stopped, 1 zombieThe number at the end of the line indicates the active zombie processes.
With /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)Killing zombie processes
You can't kill a zombie process directly. He is already dead; It just hasn't been picked up. Attempt kill -9 <pid_zombie> It will have no effect. The options are:
1. Send SIGCHLD to the parent
The signal SIGCHLD tells the father that he has children pending to pick up. If the parent has a correct handler, it should call wait() and clean the 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. Kill the parent process
If the parent does not respond to SIGCHLD, the solution is to terminate the parent process. When the father dies, the zombie is orphaned and adopted by init/systemd (PID 1), which acts as a global reaper and calls wait() periodically, eliminating the zombie:
kill -9 4820One-liner: eliminate the parents of all the zombies
ps -eo stat,ppid | awk '/^Z/{print $2}' | sort -u | xargs -r kill -9Use with caution: Terminating the parent may have production consequences if the parent is a critical service.
Prevention: good practices
Correct handling of SIGCHLD in C
The simplest way is to tell the kernel to ignore the SIGCHLD signal, which tells it to clean up children automatically without needing the parent to call 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);Double fork technique
The double fork ensures that the grandchild is immediately orphaned (adopted by init), removing the responsibility of the original process of making 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 */Prevention with systemd (service units)
For services managed by systemd, these directives help clean up orphaned processes correctly:
[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 in Docker containers
Docker containers present a particularly problematic scenario. By default, the process with PID 1 inside the container is the application itself (not init or systemd). If that application forks without managing SIGCHLD, the zombies accumulate without anyone picking them up.
Solution: 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"]Solution in 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 and systemd: the concept of subreaper
Since Linux 3.4, a process can declare itself as subreaper from your process hive using the syscall prctl(PR_SET_CHILD_SUBREAPER, 1). systemd does exactly this: it becomes the reaper for all orphaned system processes, ensuring that no zombie persists indefinitely.
This explains why on a system with systemd the transient zombies clean themselves up when the parent dies, even if the parent was not PID 1.
Zombie process monitoring
In production environments, it is advisable to alert when the number of zombies exceeds a threshold. Here two approaches:
Simple monitoring script
#!/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 0With Prometheus node_exporter
If you use Prometheus with node_exporter, the zombie metric is available natively. You can create an alert in your rules file:
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 }}."The metric that exposes node_exporter is node_processes_state{state="zombie"} and is updated on each scrape.
:wq!
Comments