Zombie- und verwaiste Prozesse verstehen und beheben
$
/etc
Linux · Prozessverwaltung · Debugging · DevOps
Zombie- und verwaiste Prozesse verstehen und beheben
Warum kill -9 nicht hilft und wie man den Elternprozess findet

Ein Zombie-Prozess hat sich bereits beendet, doch sein Elternprozess hat den Exit-Status nie abgeholt und blockiert damit einen Eintrag in der Prozesstabelle. Dieser Leitfaden erklärt den Unterschied zwischen Zombies und verwaisten Prozessen, zeigt, warum kill -9 wirkungslos bleibt, und liefert konkrete Befehle, um den verantwortlichen Elternprozess zu finden und das Problem im Code dauerhaft zu beheben.

17 Min. Lesezeit Zombie-Prozesse · wait() · SIGCHLD Linux · Prozessverwaltung · Debugging

1. Was ein Zombie-Prozess wirklich ist

Ein Zombie-Prozess ist kein hängender oder blockierter Prozess, sondern ein bereits vollständig beendeter Prozess. Sobald ein Prozess exit() aufruft oder durch ein Signal terminiert wird, gibt der Kernel seinen Speicher, seine Dateideskriptoren und alle anderen Ressourcen sofort frei. Es läuft kein Code mehr, es wird keine CPU-Zeit mehr verbraucht. Übrig bleibt lediglich ein minimaler Eintrag in der Prozesstabelle, der PID, Exit-Status und Ressourcenverbrauch (rusage) für den Elternprozess bereithält. Genau dieser Eintrag wird in ps als Z (zombie) oder <defunct> angezeigt.

Der Grund für diese Zwischenphase liegt im POSIX-Prozessmodell: Ein Elternprozess soll die Möglichkeit haben, den Exit-Code seines Kindes abzufragen, etwa um zu erkennen, ob ein Sub-Task erfolgreich war. Diese Abfrage geschieht über wait() oder waitpid(). Erst wenn der Elternprozess diesen Aufruf tätigt, entfernt der Kernel den Tabelleneintrag endgültig, man spricht vom „Reaping“ (Einsammeln) des Kindes. Bleibt dieser Aufruf aus, bleibt der Zombie bestehen, theoretisch unbegrenzt lange.

2. Der Prozess-Lebenszyklus: fork, exit und wait()

Um Zombies zu verstehen, muss man den vollständigen Lebenszyklus eines Kindprozesses kennen. Ein Prozess erzeugt mit fork() eine Kopie von sich selbst; das Kind erhält eine eigene PID und läuft unabhängig weiter, oft gefolgt von exec(), das das Programmimage durch ein neues Programm ersetzt. Beendet sich das Kind, egal ob normal über exit() oder durch ein nicht abgefangenes Signal, sendet der Kernel dem Elternprozess ein SIGCHLD-Signal und versetzt das Kind in den Zustand Z.

Der Elternprozess kann auf zwei Arten reagieren: entweder synchron mit einem blockierenden wait()-Aufruf direkt nach dem Start des Kindes, oder asynchron über einen SIGCHLD-Handler, der bei Erhalt des Signals waitpid(-1, &status, WNOHANG) in einer Schleife aufruft, um alle wartenden Kinder auf einmal einzusammeln. Fehlt beides, bleibt der Zombie bis zum Ende des Elternprozesses bestehen. Stirbt der Elternprozess selbst, werden auch seine eigenen Zombies vom Init-System übernommen und dort eingesammelt.

3. Zombies erkennen: ps, STAT-Spalte und /proc

Die STAT-Spalte von ps zeigt Zombies zuverlässig mit dem Buchstaben Z an, oft kombiniert mit weiteren Flags wie Zs für einen Session-Leader. Der Befehlsname erscheint dabei typischerweise als <defunct>, weil das ursprüngliche Programmimage längst aus dem Speicher entfernt wurde und der Kernel nur noch den Namen aus dem Tabelleneintrag kennt. Für einen schnellen Systemüberblick eignet sich ein Filter über awk, der alle Prozesse mit STAT Z extrahiert und nach Elternprozess gruppiert.

Alternativ liefert /proc/<pid>/status dieselbe Information direkt aus dem Kernel: Das Feld State enthält bei Zombies exakt Z (zombie). Dieser Weg ist besonders in Skripten nützlich, weil er ohne externes ps-Binary auskommt und sich direkt aus /proc parsen lässt. Wichtig für die Einordnung: Eine einzelne Momentaufnahme sagt wenig aus, da kurzlebige Zombies durchaus normal sind, solange die Zahl über die Zeit nicht kontinuierlich steigt.


#!/usr/bin/env bash
# List all zombie processes on the system
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /Z/ { print }'

# Count zombies grouped by parent PID, descending
ps -eo ppid,stat | awk '$2 ~ /Z/ { print $1 }' | sort | uniq -c | sort -rn

# Inspect a single process directly via /proc
cat /proc/4821/status | grep -E 'Name|State|PPid'
# Name:   sh
# State:  Z (zombie)
# PPid:   4711

4. Warum sich Zombies nicht direkt killen lassen

Der intuitive Reflex bei einem auffälligen Prozess ist kill -9, doch bei einem Zombie läuft dieser Befehl ins Leere. Signale werden an einen Thread zugestellt, der sie verarbeiten oder daran sterben kann. Ein Zombie besitzt keinen laufenden Thread mehr, keinen Adressraum, keine Register, nichts, was ein Signal empfangen oder darauf reagieren könnte. Der Kernel liefert das Signal zwar formal aus, verwirft es aber sofort wieder, da es keinen Empfänger im eigentlichen Sinn gibt. Der Prozess bleibt exakt so im Zustand Z, wie er vorher war.

Die einzige Möglichkeit, einen Zombie aus der Prozesstabelle zu entfernen, ist ein wait()-Aufruf durch seinen Elternprozess, oder das Beenden des Elternprozesses selbst, wodurch das Init-System die Verantwortung übernimmt und aufräumt. Wer also einen einzelnen Zombie „loswerden“ will, muss nicht den Zombie selbst adressieren, sondern den Elternprozess. Ein Neustart des Systems entfernt Zombies ebenfalls zuverlässig, ist aber für ein wiederkehrendes Problem keine Lösung, sondern nur ein Symptom-Reset.

5. Den verantwortlichen Elternprozess diagnostizieren

Der erste Schritt ist immer, die PPID des Zombies zu ermitteln, etwa mit ps -o ppid= -p <pid>. Danach lohnt sich ein Blick auf den Elternprozess selbst: Läuft er noch, welches Programm ist es, und reagiert er überhaupt auf SIGCHLD? Die Felder SigCgt (Caught) und SigIgn (Ignored) in /proc/<ppid>/status zeigen als Bitmaske, ob der Prozess einen eigenen Handler registriert hat oder das Signal explizit ignoriert. Fehlt beides und der Prozess ruft auch nie synchron wait() auf, ist das die eigentliche Fehlerursache.

Für eine tiefere Analyse liefert strace -p <ppid> -e trace=wait4,waitid -f in Echtzeit, ob und wie oft der Elternprozess tatsächlich einsammelt. Bleibt die Ausgabe über mehrere Minuten leer, obwohl neue Zombies entstehen, ist der Beweis erbracht: Der Elternprozess reaped seine Kinder nicht. Bei komplexeren Anwendungen hilft zusätzlich gdb -p <ppid> mit einem Stacktrace, um zu sehen, in welcher Funktion der Prozess gerade hängt und ob eine Fork-Schleife ohne begleitendes Reaping vorliegt.


#!/usr/bin/env bash
# Find the parent responsible for a known zombie PID
ZOMBIE_PID=4821
PARENT_PID=$(ps -o ppid= -p "$ZOMBIE_PID" | tr -d ' ')
echo "Zombie $ZOMBIE_PID is a child of PID $PARENT_PID"

# Inspect the parent: command line and signal disposition
ps -fp "$PARENT_PID"
grep -E 'SigCgt|SigIgn' /proc/"$PARENT_PID"/status

# Attach strace to see whether the parent ever calls wait()/waitpid()
strace -p "$PARENT_PID" -e trace=wait4,waitid -f -tt
# no output after several minutes => parent never reaps its children

6. Verwaiste Prozesse: Unterschied und Re-Parenting

Ein verwaister Prozess (orphan) ist etwas grundlegend anderes als ein Zombie: Er läuft noch ganz normal weiter, nur ist sein ursprünglicher Elternprozess bereits beendet, bevor das Kind selbst fertig war. Der Kernel löst dieses Problem, indem er dem Waisenkind sofort einen neuen Elternprozess zuweist, das sogenannte Re-Parenting. Standardmäßig übernimmt PID 1 (init oder systemd) diese Rolle, seit Linux 3.4 kann jeder Prozess sich per prctl(PR_SET_CHILD_SUBREAPER) selbst als Subreaper für seine eigenen Nachkommen eintragen.

Ein verwaister Prozess ist für sich genommen unproblematisch, da systemd oder init routinemäßig wait() für alle adoptierten Kinder aufrufen und sie sauber einsammeln, sobald sie sich beenden. Kritisch wird es erst, wenn der neue Elternprozess selbst kein zuverlässiger Reaper ist, etwa ein minimaler Container-Entrypoint ohne Init-Funktion. In diesem Fall wird aus einem harmlosen Waisenkind später ein dauerhafter Zombie, weil PID 1 im Container die Reaping-Pflicht nicht übernimmt. Das ist als „PID-1-Problem“ in der Docker-Welt bekannt.

7. Typische Ursachen im eigenen Code

Die häufigste Ursache in selbst geschriebenen Daemons und Worker-Prozessen ist ein fork() ohne begleitendes Reaping: Ein Server-Prozess startet für jede Anfrage einen Kindprozess, kümmert sich aber nie um dessen Beendigung, weil er keinen SIGCHLD-Handler registriert und auch nie waitpid() aufruft. Unter dauerhafter Last summieren sich diese nicht eingesammelten Kinder zu hunderten Zombies, bis die Prozesstabelle an ihre Grenze stößt und fork() mit EAGAIN fehlschlägt.

In Container-Umgebungen tritt eine Variante desselben Problems auf: Läuft die Anwendung als PID 1 im Container, etwa ein PHP-Skript, das per shell_exec() weitere Prozesse startet, übernimmt niemand die Reaper-Rolle, die außerhalb von Containern das Init-System erledigt. Auch Shell-Skripte, die Hintergrundjobs mit & starten und nie mit wait abwarten, produzieren dasselbe Muster, meist unbemerkt, weil das Skript selbst kurz danach beendet wird und die Zombies dem übergeordneten Prozess zufallen.

8. Zombies und verwaiste Prozesse dauerhaft beheben

Im eigenen C- oder C++-Code ist die robusteste Lösung ein SIGCHLD-Handler, der in einer Schleife waitpid(-1, &status, WNOHANG) aufruft, bis keine beendeten Kinder mehr übrig sind. Wer den Exit-Status gar nicht auswerten muss, kann die Disposition von SIGCHLD explizit auf SIG_IGN setzen, seit POSIX.1-2001 reapen moderne Linux-Kernel Kinder dann automatisch, ohne dass sie jemals in den Zombie-Zustand wechseln. Beim klassischen Daemonizing verhindert der Double-Fork-Trick das Problem strukturell: Der unmittelbare Kindprozess beendet sich sofort wieder, der Enkelprozess wird automatisch an das Init-System re-parented, das ihn zuverlässig einsammelt.

In Container-Umgebungen löst ein echter Init-Prozess als PID 1 das Problem am saubersten. docker run --init beziehungsweise init: true in Docker Compose injiziert automatisch tini als PID 1, das genau diese Reaping-Aufgabe übernimmt. Unter systemd sollte man außerdem auf manuelles Double-Forking verzichten und stattdessen Type=simple oder Type=exec nutzen, da systemd als PID 1 selbst zuverlässig reaped und der Double-Fork-Trick dort unnötig wird.


[Unit]
Description=Custom worker daemon without manual double-fork
After=network.target

[Service]
; Type=simple keeps the process in the foreground - systemd (PID 1)
; becomes the reaper for any children it spawns, no double-fork needed
Type=simple
ExecStart=/usr/local/bin/worker --foreground
Restart=on-failure
KillMode=control-group

[Install]
WantedBy=multi-user.target

services:
  worker:
    image: mironsoft/php-worker:8.4
    # init: true injects tini as PID 1 inside the container,
    # which reaps orphaned and zombie grandchild processes
    init: true
    command: ["php", "bin/worker.php"]
    restart: unless-stopped

9. Zombie vs. verwaist im Vergleich

In der Praxis ist die wichtigste Frage selten „gibt es Zombies“, sondern „ist das ein normaler, kurzlebiger Zustand oder ein echter Bug“. Ein einzelner Zombie, der nach Sekunden wieder verschwindet, ist Teil des normalen Prozess-Lebenszyklus. Eine kontinuierlich wachsende Zahl mit demselben Elternprozess ist dagegen ein klares Warnsignal für fehlerhaftes Reaping im Code.

Merkmal Harmloser, kurzlebiger Zombie Problematischer, dauerhafter Zombie Handlungsempfehlung
Lebensdauer Unter 1 Sekunde bis wenige Sekunden Minuten, Stunden oder unbegrenzt Wert über die Zeit beobachten, nicht nur einmalig prüfen
Anzahl im System 0 bis 2, schwankend Konstant steigende Zahl Trend per Monitoring erfassen, z. B. node_exporter
Elternprozess-Verhalten Ruft wait()/waitpid() zeitnah auf SIGCHLD ignoriert, wait() fehlt komplett strace -e trace=wait4 gegen die PPID prüfen
Typische Quelle Kurzlebige Shell-Subprozesse, Cronjobs Eigener Server- oder Worker-Prozess mit Fork-Bug Code-Review des verantwortlichen Elternprozesses
Auswirkung Keine, minimaler Tabellen-Eintrag Erschöpfung der PID-Tabelle, fork() schlägt fehl pid_max mit aktueller Prozesszahl vergleichen
Maßnahme Keine, verschwindet von selbst Elternprozess patchen oder kontrolliert neu starten Nie den Zombie selbst adressieren, immer den Elternprozess

Für dauerhafte Sicherheit lohnt sich ein automatisierter Check, der die Zombie-Zahl je Elternprozess regelmäßig erfasst und bei Überschreiten eines Schwellwerts alarmiert, statt Zombies nur bei einem manuellen ps-Aufruf zufällig zu entdecken.


{
  "check": "zombie_process_count",
  "host": "shop-prod-02",
  "timestamp": "2026-07-12T09:15:00Z",
  "zombie_count": 3,
  "threshold_warning": 5,
  "threshold_critical": 20,
  "top_parents": [
    { "ppid": 4711, "cmd": "php-fpm: pool magento", "zombies": 3 }
  ],
  "status": "ok"
}

Mironsoft

Linux-Systemadministration, Monitoring und DevOps für Magento-Infrastruktur

Zombie-Prozesse und Systeminstabilitäten professionell beheben?

Wir analysieren eure Linux-Server und Docker-Container, identifizieren Elternprozesse mit fehlerhaftem Reaping und beheben die Ursache im Code statt nur Symptome zu kaschieren.

Prozess-Audit

Systematische Analyse aller Zombie- und verwaisten Prozesse auf euren Produktionsservern

Code-Review

Fork- und Signal-Handling in eigenen Daemons und Workern auf fehlendes wait() prüfen

Container-Härtung

Docker- und Kubernetes-Setups mit korrektem PID-1-Reaping und Init-Prozessen absichern

10. Zusammenfassung

Ein Zombie-Prozess ist ein bereits vollständig beendeter Prozess, dessen Elternprozess den Exit-Status noch nicht per wait() oder waitpid() abgeholt hat. Da kein laufender Code mehr existiert, läuft kill -9 gegen einen Zombie ins Leere, denn es gibt keinen Empfänger für das Signal. Die Lösung liegt immer beim Elternprozess: entweder direkt reparieren, indem ein SIGCHLD-Handler ergänzt wird, oder den Prozess kontrolliert neu starten, damit das Init-System die verwaisten Kinder übernimmt.

Verwaiste Prozesse sind davon klar zu unterscheiden: Sie laufen noch aktiv, wurden aber nach dem Tod ihres ursprünglichen Elternprozesses automatisch an PID 1 re-parented. Kritisch werden sie erst, wenn dieser neue Elternprozess selbst kein zuverlässiger Reaper ist, etwa ein minimaler Container-Entrypoint. Die wichtigste Praxisregel: Ein einzelner, kurzlebiger Zombie ist normal, eine kontinuierlich wachsende Zahl mit demselben Elternprozess ist ein Bug, der im Code des Elternprozesses behoben werden muss, nicht durch wiederholtes Neustarten des Servers.

Zombie- und verwaiste Prozesse: Das Wichtigste auf einen Blick

Was ein Zombie ist

Bereits mit exit() beendet, aber der Elternprozess hat wait() noch nicht aufgerufen. Reiner Eintrag in der Prozesstabelle, kein laufender Code.

Warum kill nicht hilft

Kein Thread mehr vorhanden, der ein Signal empfangen könnte. Nur der Elternprozess kann den Eintrag durch Reaping freigeben.

Verwaist vs. Zombie

Verwaiste Prozesse laufen weiter und werden vom Init-System neu adoptiert. Zombies sind bereits beendet und warten nur auf das Reaping.

Prävention

SIGCHLD-Handler mit waitpid(WNOHANG), Double-Fork beim Daemonizing, docker run --init bzw. tini in Containern.

11. FAQ: Zombie- und verwaiste Prozesse

1Was ist ein Zombie-Prozess genau?
Bereits vollständig beendet, alle Ressourcen freigegeben. Übrig bleibt nur ein Eintrag in der Prozesstabelle mit dem Exit-Status, bis der Elternprozess ihn per wait() abholt.
2Warum hilft kill -9 nicht bei Zombies?
Kein laufender Thread mehr vorhanden, der ein Signal empfangen könnte. Der Kernel verwirft das Signal sofort. Nur der Elternprozess kann per wait() aufräumen.
3Wie erkenne ich Zombies unter Linux?
Über ps -eo pid,ppid,stat,cmd mit Filter auf STAT Z, oder /proc//status, wo State bei Zombies Z (zombie) zeigt. Befehlsname meist .
4Unterschied Zombie vs. verwaister Prozess?
Zombie ist beendet und wartet auf Reaping. Verwaister Prozess läuft noch aktiv, wurde nur automatisch an PID 1 re-parented, nachdem sein ursprünglicher Elternprozess starb.
5Wie finde ich den verantwortlichen Elternprozess?
ps -o ppid= -p für die PPID, dann SigCgt/SigIgn in /proc//status prüfen. strace -e trace=wait4 zeigt, ob überhaupt gereaped wird.
6Sind Zombie-Prozesse gefährlich?
Einzelne, kurzlebige Zombies sind harmlos. Gefährlich wird es erst bei kontinuierlich steigender Zahl, weil die PID-Tabelle erschöpft und fork() fehlschlägt.
7Wie behebe ich einen dauerhaften Zombie?
Kurzfristig: Elternprozess kontrolliert neu starten. Dauerhaft: SIGCHLD-Handler mit waitpid(WNOHANG) im Code ergänzen oder SIGCHLD auf SIG_IGN setzen.
8Warum besonders viele Zombies in Docker?
Läuft die App selbst als PID 1, fehlt die Reaper-Rolle des Init-Systems. docker run --init aktiviert tini als echten PID-1-Prozess und löst das Problem.
9Hilft manuelles SIGCHLD an den Elternprozess?
Nein. Ohne registrierten Handler oder wait()-Aufruf im Code löst ein manuell gesendetes SIGCHLD keinerlei Reaping aus. Das muss aktiv implementiert sein.
10Wie verhindere ich Zombies im eigenen Code?
SIGCHLD-Handler mit waitpid(WNOHANG) in Schleife, oder SIGCHLD auf SIG_IGN setzen. Beim Daemonizing verhindert der Double-Fork-Trick das Problem strukturell.