vom Prozessbaum bis zum haengenden PHP-FPM-Worker
Jeder Linux-Prozess besitzt eine eindeutige PID und verweist über seine PPID auf seinen Elternprozess, wodurch ein nachvollziehbarer Baum aus Eltern- und Kind-Prozessen entsteht. Dieser Artikel zeigt, wie man diesen Baum mit ps und pstree liest, warum verwaiste Prozesse zu PID 1 wandern und wie man in der Praxis einen hängenden PHP-FPM-Worker gezielt aufspürt und beendet.
Inhaltsverzeichnis
- 1. Das Prozessmodell unter Linux: PID und PPID
- 2. Der Prozessbaum: Eltern-Kind-Beziehungen im Detail
- 3. Prozesse inspizieren mit ps
- 4. Prozessbaum visualisieren mit pstree, top und htop
- 5. Prozesszustände verstehen: R, S, D, Z, T
- 6. Verwaiste Prozesse und Reparenting zu PID 1
- 7. Zombie-Prozesse: der Unterschied zu Waisen
- 8. Praxis: einen hängenden PHP-FPM-Worker debuggen
- 9. Prozesssteuerung: Signale, kill und der Methodenvergleich
- 10. Zusammenfassung
- 11. FAQ
1. Das Prozessmodell unter Linux: PID und PPID
Jeder laufende Prozess unter Linux entsteht durch den Systemaufruf fork(), der einen bestehenden Prozess dupliziert und dem Kind eine neue, eindeutige PID (Process ID) zuweist. Der Kernel vergibt PIDs fortlaufend aus einem begrenzten Nummernraum, dessen Obergrenze in /proc/sys/kernel/pid_max steht. Intern verwaltet der Kernel jeden Prozess über eine task_struct-Datenstruktur, die neben der PID auch Zustand, offene Dateideskriptoren, Speicherzuordnungen und Ressourcenlimits enthält.
Die PPID (Parent Process ID) verweist auf genau den Prozess, der den aktuellen per fork() erzeugt hat. Diese Eltern-Kind-Beziehung ist strikt hierarchisch: Jeder Prozess außer dem allerersten hat exakt einen Elternprozess, niemals mehrere. Der erste Prozess nach dem Boot bekommt die PID 1, meist systemd oder ein anderes Init-System, und bildet die Wurzel des gesamten Prozessbaums. Ohne diesen Wurzelknoten gäbe es keinen definierten Ausgangspunkt für die gesamte Prozesshierarchie.
Zur Laufzeit lässt sich jeder Prozess über sein virtuelles Verzeichnis unter /proc/<pid>/ inspizieren. Dateien wie status, cmdline und der Symlink exe liefern PID, PPID, Kommandozeile und den Pfad zur ausführbaren Datei, ganz ohne zusätzliche Werkzeuge. Genau diese Dateien liest ps im Hintergrund aus, wenn man Prozesslisten abfragt.
2. Der Prozessbaum: Eltern-Kind-Beziehungen im Detail
Beim fork() kopiert der Kernel zunächst nicht den kompletten Speicher des Elternprozesses, sondern nutzt Copy-on-Write: Eltern und Kind teilen sich dieselben Speicherseiten, bis eine Seite tatsächlich verändert wird. Erst dann wird sie dupliziert. Direkt nach dem fork() ist das Kind fast eine exakte Kopie des Elternprozesses, mit eigener PID, aber identischem Programmcode. Ruft das Kind anschließend execve() auf, wird sein komplettes Speicherabbild durch ein neues Programm ersetzt, die PID bleibt jedoch erhalten. Dieses Fork-Exec-Muster nutzt jede Shell, wenn sie einen Befehl startet.
Jeder Prozess gehört zusätzlich zu einer Prozessgruppe (PGID) und einer Session (SID), was für Job-Control und die Signalzustellung an ganze Gruppen relevant ist. Der Prozessbaum lässt sich in dieser Struktur direkt sichtbar machen, entweder mit dem Standardwerkzeug ps --forest oder mit pstree, das die Hierarchie kompakter und übersichtlicher darstellt.
# Show the process tree in indented form, including PID and PPID
ps -e -o pid,ppid,pgid,sid,cmd --forest | head -n 20
# Example output (excerpt)
# PID PPID PGID SID CMD
# 1 0 1 1 /sbin/init
# 842 1 842 842 /lib/systemd/systemd-journald
# 1210 1 1210 1210 /usr/sbin/nginx -g daemon off;
# 1215 1210 1210 1210 \_ nginx: worker process
# 1340 1 1340 1340 php-fpm: master process (/etc/php/8.4/fpm/php-fpm.conf)
# 1341 1340 1340 1340 \_ php-fpm: pool www
# 1342 1340 1340 1340 \_ php-fpm: pool www
3. Prozesse inspizieren mit ps
ps unterstützt zwei historisch gewachsene Syntaxvarianten: die UNIX-Form ps -ef und die BSD-Form ps aux. Beide liefern ähnliche Informationen, unterscheiden sich aber in Spaltennamen und Optionen. ps -ef zeigt UID, PID, PPID, Startzeit und Kommandozeile, während ps aux zusätzlich CPU- und Speicherauslastung in Prozent direkt in der Standardausgabe liefert. Für Debugging-Zwecke ist die frei konfigurierbare Ausgabe mit -o meist präziser, weil man genau die Spalten wählt, die man tatsächlich braucht.
Die STAT-Spalte zeigt den Prozesszustand als Buchstabencode, ergänzt um Modifikatoren wie < für hohe Priorität oder + für Vordergrundprozesse einer Terminal-Session. Kombiniert mit Sortierung nach CPU- oder Speicherverbrauch über --sort lassen sich Ausreißer in Sekunden identifizieren, ganz ohne interaktives Tool. Genau dieses Muster ist der Einstieg in jede Prozessanalyse, bevor man überhaupt zu spezialisierten Werkzeugen wie strace greift.
# Custom columns: pid, ppid, state, cpu%, mem%, elapsed time, command
ps -eo pid,ppid,stat,pcpu,pmem,etime,cmd --sort=-pcpu | head -n 10
# Find every child process of a specific parent PID
ps -eo pid,ppid,cmd --ppid 1340
# Show only processes in uninterruptible sleep (D) or zombie (Z) state
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /D|Z/'
4. Prozessbaum visualisieren mit pstree, top und htop
pstree fasst identische Geschwisterprozesse standardmäßig zusammen und zeigt sie als Anzahl, etwa php-fpm: pool www---7*[php-fpm: pool www]. Mit dem Schalter -p werden zusätzlich die PIDs eingeblendet, mit -a die vollständigen Kommandozeilen inklusive Argumente. Das macht pstree -p <pid> zum schnellsten Weg, um zu sehen, wie viele Kindprozesse ein bestimmter Dienst gerade offen hält, ohne die komplette Systemliste durchsuchen zu müssen.
Für die laufende Beobachtung eignen sich top und htop besser, weil sie sich periodisch aktualisieren. In htop schaltet die Taste t zwischen flacher Liste und Baumansicht um, sodass sich Eltern-Kind-Beziehungen in Echtzeit verfolgen lassen, während sich CPU- oder Speicherverbrauch ändern. Beide Werkzeuge markieren Prozesse im Zustand D farblich hervorgehoben, was bei der Suche nach blockierten I/O-Operationen sofort ins Auge fällt, noch bevor man überhaupt eine einzelne Zeile genauer liest.
5. Prozesszustände verstehen: R, S, D, Z, T
Die STAT-Spalte von ps kodiert den Zustand jedes Prozesses in einem einzelnen Buchstaben. R (Running) bedeutet, dass der Prozess aktiv auf der CPU läuft oder lauffähig in der Warteschlange steht. S (Interruptible Sleep) ist der häufigste Zustand im Leerlauf, etwa wenn ein Prozess auf Netzwerk-I/O oder ein Timer-Event wartet und dabei jederzeit durch ein Signal geweckt werden kann. T (Stopped) tritt auf, wenn ein Prozess per SIGSTOP angehalten oder von einem Debugger via ptrace pausiert wurde.
Kritischer ist D (Uninterruptible Sleep): Der Prozess wartet auf eine Kernel-Operation, meist Festplatten- oder NFS-I/O, und kann in diesem Zustand durch kein Signal, auch nicht SIGKILL, unterbrochen werden. Viele D-Prozesse gleichzeitig deuten fast immer auf einen I/O-Engpass am Storage-Backend hin, nicht auf ein Problem in der Anwendung selbst. Z (Zombie) schließlich bezeichnet einen bereits beendeten Prozess, dessen Exit-Status noch nicht vom Elternprozess abgeholt wurde, dazu mehr im nächsten Abschnitt.
6. Verwaiste Prozesse und Reparenting zu PID 1
Ein Prozess gilt als verwaist (orphan), sobald sein ursprünglicher Elternprozess terminiert, während das Kind selbst weiterläuft. Der Kernel lässt keine PPID-Lücke zu: Jeder verwaiste Prozess wird automatisch reparentet, also einem neuen Elternprozess zugeordnet. Historisch war das immer PID 1. Seit Linux 3.4 kann jeder Prozess sich mit PR_SET_CHILD_SUBREAPER selbst als Subreaper markieren, sodass etwa ein Container-Init-Prozess oder ein Prozess-Supervisor verwaiste Enkelprozesse abfängt, bevor sie bis zu PID 1 durchgereicht werden. Genau dieses Verhalten nutzen Docker-Init-Systeme wie tini.
In der Praxis begegnet man Reparenting häufig bewusst: Ein mit nohup befehl & gestarteter Prozess überlebt das Schließen der Terminal-Session, weil er von SIGHUP entkoppelt ist. Beendet man anschließend die Shell, zeigt ps -o pid,ppid für diesen Prozess sofort PPID 1, sobald das Reparenting abgeschlossen ist. Das ist kein Fehler, sondern exakt das gewünschte Verhalten für langlebige Hintergrunddienste.
# Start a long-running process detached from the terminal session
nohup sleep 600 &
child_pid=$!
ps -o pid,ppid,cmd -p "$child_pid"
# PID PPID CMD
# 4821 4711 sleep 600 <- PPID is still the current shell
# Close the terminal / kill the parent shell, then check again
kill -HUP 4711
sleep 1
ps -o pid,ppid,cmd -p 4821
# PID PPID CMD
# 4821 1 sleep 600 <- reparented to PID 1 (init/systemd)
7. Zombie-Prozesse: der Unterschied zu Waisen
Ein Zombie-Prozess ist keinesfalls dasselbe wie ein verwaister Prozess, auch wenn beide Begriffe oft verwechselt werden. Ein Zombie hat bereits vollständig terminiert und keinerlei Code mehr ausgeführt, keinen Arbeitsspeicher mehr belegt und keine offenen Dateideskriptoren mehr. Übrig bleibt lediglich ein Eintrag in der Prozesstabelle des Kernels mit Exit-Code und Ressourcenverbrauchsstatistiken, solange der Elternprozess diesen Status nicht per wait() oder waitpid() abgeholt hat. In ps erscheint ein Zombie mit dem Zustand Z und dem Zusatz <defunct> hinter dem Kommandonamen.
Weil ein Zombie bereits tot ist, lässt er sich mit keinem Signal beenden, auch kill -9 läuft ins Leere. Die einzige Lösung ist, den Elternprozess dazu zu bringen, den Exit-Status abzuholen, entweder durch einen Bugfix im Code, der waitpid() korrekt aufruft, oder pragmatisch durch Neustart des Elternprozesses. Terminiert der Elternprozess, reparentet der Kernel den Zombie zu PID 1, und systemd ruft für diesen verwaisten Zombie automatisch wait() auf, wodurch der Eintrag verschwindet.
8. Praxis: einen hängenden PHP-FPM-Worker debuggen
Ein typisches Symptom im Live-Betrieb: Die Website antwortet mit 504 Gateway Timeout, obwohl nginx und die Datenbank grundsätzlich erreichbar sind. Häufigste Ursache ist ein PHP-FPM-Pool, dessen pm.max_children erreicht ist, weil einzelne Worker in einer blockierenden Operation feststecken, etwa einer langsamen externen API oder einem gesperrten Datenbank-Lock. Der erste Schritt ist, den Pool-Status abzufragen und die Worker-PIDs mit ungewöhnlich hoher Laufzeit zu identifizieren.
Mit ps -eo pid,ppid,etime,pcpu,cmd | grep php-fpm lassen sich Worker mit auffällig langer Laufzeit (etime) sofort erkennen, während neue Requests normalerweise in Millisekunden abgearbeitet werden. Für die eigentliche Ursachenanalyse hängt man sich mit strace -p <pid> an den hängenden Worker und sieht sofort, auf welchen Systemaufruf er wartet, etwa recvfrom() bei einer hängenden Netzwerkverbindung oder flock() bei einem Dateisperrkonflikt.
Reicht strace nicht aus, liefert gdb -p <pid> -batch -ex "bt" einen PHP-internen Stacktrace, sofern die Debug-Symbole vorhanden sind. Damit lässt sich meist exakt die Codezeile identifizieren, in der der Worker feststeckt. Präventiv gehört request_slowlog_timeout in jede Produktionskonfiguration, damit FPM verdächtig lange Requests automatisch protokolliert, bevor sie zum manuellen Debugging-Fall werden.
; /etc/php/8.4/fpm/pool.d/www.conf
; Pool tuning to detect and contain runaway workers early
[www]
pm = dynamic
pm.max_children = 12
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
; Log any request that runs longer than 5 seconds, with a full backtrace
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/www-slow.log
; Hard kill a worker that exceeds the configured wall-clock limit
request_terminate_timeout = 30s
; Emit the FPM status page for automated monitoring
pm.status_path = /fpm-status
9. Prozesssteuerung: Signale, kill und der Methodenvergleich
Der Befehl kill sendet trotz seines Namens nicht zwingend ein Terminierungssignal, sondern beliebige POSIX-Signale. SIGTERM (Standard bei kill ohne Option) bittet den Prozess um einen geordneten Shutdown, den er selbst abfangen und sauber beenden kann, etwa offene Dateien schließen und Verbindungen abbauen. SIGKILL (kill -9) hingegen wird direkt vom Kernel verarbeitet, ohne den Prozess zu informieren, und kann daher zu inkonsistenten Zuständen führen, etwa halb geschriebenen Dateien oder verwaisten Locks.
Für einen automatisierten Watchdog, der hängende PHP-FPM-Worker erkennt und kontrolliert beendet, lohnt sich ein strukturiertes Audit-Log, damit jede Eingriffsentscheidung im Nachhinein nachvollziehbar bleibt, statt nur eine Zeile in syslog zu hinterlassen.
{
"timestamp": "2026-07-12T09:41:18Z",
"watchdog": "fpm-worker-guard",
"event": "runaway_worker_terminated",
"pid": 18422,
"ppid": 1340,
"pool": "www",
"state_before": "R",
"etime_seconds": 47,
"signal_sequence": ["SIGTERM", "SIGKILL"],
"reason": "exceeded request_terminate_timeout",
"stack_hint": "blocked in mysqli_query() on locked row"
}
Soll nicht nur ein einzelner Prozess, sondern eine ganze Prozessgruppe beendet werden, etwa ein Shell-Skript mitsamt aller gestarteten Kindprozesse, zielt kill mit negativer PID auf die gesamte Gruppe: kill -- -PGID. Die folgende Tabelle stellt gängige, aber riskante Vorgehensweisen den empfohlenen Alternativen gegenüber.
| Aufgabe | Riskant / ungenau | Empfohlenes Vorgehen | Vorteil |
|---|---|---|---|
| Prozess beenden | kill -9 sofort |
kill -TERM, dann -9 nach Timeout |
Geordnetes Cleanup, keine korrupten Dateien |
| PHP-FPM neu starten | systemctl restart php-fpm |
pm.max_requests + reload |
Kein kompletter Ausfall aller Pools |
| Zombie-Prozess "entfernen" | kill -9 <zombie-pid> |
Elternprozess reparieren oder neu starten | Zombies sind bereits tot, Signale wirkungslos |
| Ganzen Prozessbaum stoppen | Nur die Haupt-PID killen | kill -- -PGID oder pkill -P |
Keine verwaisten Kindprozesse zurück |
| Hängenden Worker finden | Nur CPU-Auslastung prüfen | ps -eo stat,etime,ppid nach D/Z filtern |
Erfasst auch blockierte I/O-Prozesse |
Mironsoft
Server-Administration, Debugging und Deployment-Infrastruktur für Magento-Shops
PHP-FPM-Worker hängen, der Server läuft heiß?
Wir analysieren Prozessbäume, PHP-FPM-Pools und Datenbank-Locks in eurer Produktionsumgebung, finden die Ursache hängender Worker und richten Monitoring ein, das solche Vorfälle künftig automatisch erkennt und dokumentiert.
Server-Diagnose
Prozessbaum-Analyse mit ps, pstree und strace bei akuten Performance-Problemen
PHP-FPM Tuning
Pool-Konfiguration, Slowlogs und Timeouts an eure Last anpassen
Monitoring-Setup
Automatisierte Erkennung hängender Worker und strukturierte Audit-Logs
10. Zusammenfassung
Jeder Linux-Prozess trägt eine eindeutige PID und eine PPID, die ihn seinem Elternprozess zuordnet, wodurch sich der komplette Prozessbaum ausgehend von PID 1 nachvollziehen lässt. Werkzeuge wie ps --forest und pstree -p machen diese Hierarchie sichtbar, während top und htop sie in Echtzeit beobachtbar machen. Stirbt ein Elternprozess vor seinem Kind, reparentet der Kernel das Kind automatisch zu PID 1 oder einem registrierten Subreaper, statt eine Lücke im Baum entstehen zu lassen.
Der Zustand D signalisiert blockierte I/O und lässt sich durch kein Signal unterbrechen, während ein Zombie bereits vollständig terminiert ist und nur auf das wait() seines Elternprozesses wartet. Für die Praxis auf produktiven Servern ist die Kombination aus ps -eo stat,etime,ppid, strace -p und einem sauber konfigurierten request_slowlog_timeout der zuverlässigste Weg, hängende PHP-FPM-Worker zu finden, bevor sie zu einem kompletten Ausfall führen.
Linux-Prozesse: PID, PPID, Prozessbaum: Das Wichtigste auf einen Blick
PID & PPID
Jeder Prozess hat genau eine PID und genau eine PPID. PID 1 (systemd oder init) ist die Wurzel des gesamten Prozessbaums.
Prozessbaum lesen
pstree -p oder ps --forest zeigen Eltern-Kind-Beziehungen sofort visuell, ohne manuelles PID-Mapping.
Waisen & Zombies
Waisen werden zu PID 1 reparentet und laufen weiter. Zombies sind bereits tot und warten nur auf wait() durch den Elternprozess.
PHP-FPM Debugging
ps -eo pid,ppid,etime,stat,cmd, dann strace -p oder gdb -p für den Stacktrace des hängenden Workers.