Warum ein sanftes Beenden nicht dasselbe ist wie ein harter Kill
Wer produktive Prozesse mit kill -9 beendet, riskiert korrupte PHP-FPM-Requests, verwaiste Locks und inkonsistente Daten. Dieser Artikel erklärt, wie Unix-Signale wirklich funktionieren, warum SIGTERM und SIGKILL grundverschieden sind, wie man Signale in eigenen Skripten sauber abfängt und wie kill, pkill, killall sowie systemd für kontrolliertes Prozessmanagement zusammenspielen.
Inhaltsverzeichnis
- 1. Warum Signale und Prozesskontrolle über Stabilität entscheiden
- 2. Wie Signale funktionieren: Kernel, Prozesstabelle, Standardaktionen
- 3. SIGTERM vs. SIGKILL: der kritische Unterschied
- 4. Warum kill -9 bei PHP-FPM In-Flight-Requests korrumpiert
- 5. Graceful Shutdown mit trap in eigenen Skripten
- 6. kill, pkill und killall im Vergleich
- 7. systemd und Container: TimeoutStopSec, KillSignal, stop_grace_period
- 8. Weitere wichtige Signale: SIGHUP, SIGINT, SIGUSR1/2, SIGSTOP
- 9. Zombie-Prozesse, Fallstricke und Signal-Patterns im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Signale und Prozesskontrolle über Stabilität entscheiden
Ein Unix-Signal ist eine asynchrone Benachrichtigung, die der Kernel an einen Prozess schickt, um ihn über ein Ereignis zu informieren oder zu einer bestimmten Reaktion zu zwingen. Signale sind keine Nachrichten mit Nutzdaten wie bei Message Queues, sondern einfache nummerierte Unterbrechungen: Ein Prozess erhält lediglich die Information, welches Signal eingetroffen ist, und reagiert dann mit einem registrierten Handler, der Standardaktion des Kernels oder Ignorieren. Für Administratoren und Entwickler entscheidet das Verständnis von Signalen darüber, ob ein Prozess kontrolliert herunterfährt oder mitten in einer Operation abgewürgt wird.
In produktiven Umgebungen zeigt sich der Unterschied schmerzhaft: Ein Datenbank-Dump, der per SIGKILL beendet wird, hinterlässt eine unvollständige Datei. Ein PHP-FPM-Worker, der mitten in einer Bestellung getötet wird, kann eine halb geschriebene Transaktion zurücklassen. Wer Prozesskontrolle ernst nimmt, unterscheidet bewusst zwischen sanften Signalen wie SIGTERM, die dem Prozess Zeit zum Aufräumen geben, und harten Signalen wie SIGKILL, die sofort und ohne Rücksicht wirken. Die folgenden Abschnitte zeigen, wie Signale technisch funktionieren und wie man sie in Deployment-Skripten, PHP-FPM-Konfigurationen und systemd-Units richtig einsetzt.
2. Wie Signale funktionieren: Kernel, Prozesstabelle, Standardaktionen
Der Linux-Kernel verwaltet für jeden Prozess eine Signalmaske und eine Tabelle mit bis zu 64 Standardsignalen, von denen die ersten 31 klassische POSIX-Signale mit festen Nummern sind. Jedes Signal hat eine Standardaktion: Beenden, Beenden mit Core-Dump, Ignorieren, Anhalten oder Fortsetzen. Ein Prozess kann für die meisten Signale einen eigenen Handler registrieren, der die Standardaktion überschreibt, oder das Signal explizit blockieren. Zwei Signale bilden dabei die Ausnahme: SIGKILL (Nummer 9) und SIGSTOP (Nummer 19) lassen sich weder abfangen noch ignorieren noch blockieren, weil der Kernel sie direkt am Scheduler durchsetzt, ohne den Zielprozess überhaupt zu benachrichtigen.
Der Befehl kill -l listet alle verfügbaren Signalnamen mit ihren Nummern auf und ist der schnellste Weg, sich die Zuordnung wieder in Erinnerung zu rufen. In eigenen Bash-Skripten zeigt trap -p, welche Handler für welches Signal aktuell registriert sind. Diese Grundlagen sind Voraussetzung, um im nächsten Schritt den entscheidenden Unterschied zwischen SIGTERM und SIGKILL richtig einzuordnen.
# List all standard POSIX signal names with their numbers
kill -l
# Show the signal number for a specific name
kill -l TERM # 15
kill -l KILL # 9
kill -l HUP # 1
# Show currently registered trap handlers in the current shell
trap -p
# Send a signal to a process by PID (default signal is SIGTERM)
kill 4821
# Explicit signal, three equivalent notations
kill -SIGTERM 4821
kill -TERM 4821
kill -15 4821
# Hard kill, enforced by the kernel scheduler, cannot be caught
kill -SIGKILL 4821
kill -9 4821
3. SIGTERM vs. SIGKILL: der kritische Unterschied
SIGTERM (Signal 15) ist die höfliche Bitte an einen Prozess, sich zu beenden. Der Prozess behält die Kontrolle: Er kann einen Handler registrieren, offene Datenbankverbindungen schließen, laufende Requests zu Ende bringen, temporäre Dateien löschen und sich danach selbst beenden. Genau das ist der Sinn eines Graceful Shutdown. Ignoriert der Prozess SIGTERM komplett oder reagiert er nicht innerhalb einer angemessenen Frist, bleibt er einfach am Leben und blockiert unter Umständen einen Neustart oder ein Deployment.
SIGKILL (Signal 9) dagegen kennt keine Kulanz. Der Kernel entfernt den Prozess sofort aus der Scheduler-Warteschlange, ohne ihm die Möglichkeit zu geben, Handler auszuführen, Dateien zu schließen oder Ressourcen freizugeben. Offene Dateideskriptoren räumt der Kernel selbst auf, aber alles, was der Prozess eigentlich hätte tun müssen, wie das Commit einer Transaktion oder das Schreiben eines konsistenten Zustands, entfällt ersatzlos. Die Faustregel in der Praxis: immer zuerst SIGTERM senden, eine angemessene Frist abwarten, und SIGKILL nur als letzten Ausweg für hängende Prozesse verwenden, die auf SIGTERM nicht reagieren.
4. Warum kill -9 bei PHP-FPM In-Flight-Requests korrumpiert
PHP-FPM verarbeitet HTTP-Requests in Workerprozessen, die vom Master-Prozess verwaltet werden. Wird ein Worker mitten in einem Request mit SIGKILL beendet, bricht die laufende PHP-Ausführung genau an der Stelle ab, an der sie sich gerade befand, ohne dass ein finally-Block, ein Destruktor oder ein registrierter Shutdown-Handler noch ausgeführt wird. Bei einem Checkout-Prozess in Magento kann das bedeuten, dass eine Bestellung im System angelegt, der Warenkorb aber nicht geleert wurde, oder dass eine Zahlungsbestätigung nicht mehr gespeichert wird, obwohl der Zahlungsanbieter die Transaktion bereits als erfolgreich meldet.
Der Master-Prozess von PHP-FPM reagiert auf SIGTERM und SIGQUIT unterschiedlich: SIGQUIT löst einen Graceful Stop aus, bei dem laufende Requests zu Ende gebracht werden, bevor die Worker beendet werden. Die Konfigurationsdirektive process_control_timeout legt fest, wie lange ein Worker nach dem Empfang eines Stop-Signals Zeit bekommt, bevor der Master-Prozess mit SIGKILL nachhilft. Wer diesen Wert zu niedrig setzt, riskiert genau die abgebrochenen Requests, die ein Graceful Shutdown eigentlich verhindern soll.
; /etc/php/8.4/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
; Grace period a worker gets to finish an in-flight request
; after receiving SIGTERM/SIGQUIT, before the master sends SIGKILL
process_control_timeout = 10s
; Let idle workers shut down gracefully instead of a hard restart
pm.process_idle_timeout = 30s
catch_workers_output = yes
decorate_workers_output = no
5. Graceful Shutdown mit trap in eigenen Skripten
Das trap-Builtin in Bash registriert eine Funktion oder einen Befehl, der beim Empfang eines bestimmten Signals ausgeführt wird. Für Wartungsskripte, Worker-Prozesse und Deployment-Automatisierung ist trap das zentrale Werkzeug, um auf SIGTERM und SIGINT kontrolliert zu reagieren, statt den Prozess unvorbereitet sterben zu lassen. Eine typische Handler-Funktion beendet die aktuelle Schleife, wartet mit wait auf laufende Hintergrundjobs und gibt erst danach den Exit-Code zurück.
Wichtig ist, dass der Handler selbst schnell und robust bleibt: Ein Signal-Handler, der seinerseits hängen bleibt, weil er auf eine nicht mehr erreichbare Ressource wartet, verwandelt einen sauberen Shutdown in einen unbegrenzten Hang. Deshalb sollte man innerhalb des Handlers eigene Timeouts setzen und im Zweifel nach einer definierten Frist selbst mit exit abbrechen. Da SIGKILL nicht abfangbar ist, bleibt trap ausschließlich für SIGTERM, SIGINT, SIGHUP und die benutzerdefinierten Signale SIGUSR1 und SIGUSR2 wirksam, niemals für SIGKILL oder SIGSTOP.
#!/usr/bin/env bash
# graceful-worker.sh: reacts to SIGTERM/SIGINT and finishes the current job first
set -euo pipefail
RUNNING=1
CURRENT_JOB_PID=""
terminate() {
echo "[INFO] SIGTERM received, waiting for current job to finish" >&2
RUNNING=0
if [[ -n "$CURRENT_JOB_PID" ]]; then
wait "$CURRENT_JOB_PID"
fi
echo "[INFO] Graceful shutdown complete" >&2
exit 0
}
trap terminate SIGTERM SIGINT
while [[ "$RUNNING" -eq 1 ]]; do
process_next_job &
CURRENT_JOB_PID=$!
wait "$CURRENT_JOB_PID"
CURRENT_JOB_PID=""
done
6. kill, pkill und killall im Vergleich
kill, pkill und killall lösen dieselbe Aufgabe, das Senden eines Signals, auf unterschiedliche Weise. kill adressiert einen Prozess ausschließlich über seine PID und ist damit das präziseste, aber auch das umständlichste Werkzeug, weil man die PID vorher ermitteln muss, etwa mit pgrep oder ps. pkill sucht Prozesse anhand eines Namensmusters oder anderer Kriterien wie Benutzer oder Arbeitsverzeichnis und sendet das Signal direkt an alle Treffer, ohne den Umweg über eine explizite PID.
killall ähnelt pkill, erwartet aber standardmäßig einen exakten Prozessnamen statt eines Musters und ist auf manchen Unix-Systemen abweichend implementiert, weshalb es auf Linux-Servern etwas weniger portabel ist als pkill. Eine häufige Falle bei pkill und killall: Ein zu unspezifisches Muster trifft mehr Prozesse als beabsichtigt, im schlimmsten Fall auch systemkritische Dienste mit ähnlichem Namen. Die Option -f bei pkill matcht gegen die komplette Kommandozeile statt nur gegen den Prozessnamen und sollte deshalb immer mit einem möglichst spezifischen Muster kombiniert werden.
# kill: precise, requires a known PID
pgrep -f "php-fpm: pool www" | head -1
kill -TERM 4821
# pkill: match by name pattern, send SIGTERM to every match
pkill -TERM -f "queue:consume magento"
# pkill: only match processes owned by a specific user
pkill -TERM -u www-data -f "bin/magento cron:run"
# killall: exact process name, all instances
killall -TERM php-fpm8.4
# Always send SIGTERM first, wait, then escalate to SIGKILL as a fallback
pkill -TERM -f "worker.php" && sleep 10 && pkill -KILL -f "worker.php"
# Dry-run first: list matches before sending any signal
pgrep -af "worker.php"
7. systemd und Container: TimeoutStopSec, KillSignal, stop_grace_period
systemd sendet beim Stoppen eines Dienstes standardmäßig SIGTERM und wartet die in TimeoutStopSec konfigurierte Zeit, bevor es mit SIGKILL nachhilft, falls der Prozess bis dahin nicht beendet ist. Die Direktive KillSignal erlaubt es, das initiale Signal zu ändern, was etwa bei PHP-FPM sinnvoll ist, weil dessen Master-Prozess auf SIGQUIT mit einem echten Graceful Stop reagiert, während SIGTERM einen sofortigen Stopp auslöst. Wer TimeoutStopSec zu kurz setzt, erzwingt faktisch immer ein SIGKILL, egal wie sauber die Anwendung selbst auf SIGTERM reagieren würde.
In Container-Umgebungen gilt dasselbe Prinzip mit anderen Namen: Docker sendet beim Stoppen eines Containers das über stop_signal konfigurierte Signal, standardmäßig SIGTERM, und wartet stop_grace_period, bevor SIGKILL folgt. Kubernetes verwendet analog terminationGracePeriodSeconds. Wird dieser Wert nicht an die tatsächliche Dauer laufender Requests angepasst, erzwingt die Orchestrierungsschicht regelmäßig harte Kills, obwohl die Anwendung selbst durchaus in der Lage wäre, sich sauber zu beenden, wenn man ihr nur genug Zeit ließe.
# docker-compose.yaml: give PHP-FPM enough time to finish in-flight requests
services:
php-fpm:
image: mironsoft/php-fpm:8.4
stop_signal: SIGQUIT # PHP-FPM's own graceful-stop signal
stop_grace_period: 15s # Docker sends SIGKILL after this if still running
healthcheck:
test: ["CMD", "php-fpm-healthcheck"]
interval: 10s
timeout: 3s
retries: 3
8. Weitere wichtige Signale: SIGHUP, SIGINT, SIGUSR1/2, SIGSTOP
Neben SIGTERM und SIGKILL gibt es eine Reihe weiterer Signale mit klar definierten Einsatzzwecken. SIGHUP (Signal 1) bedeutete ursprünglich den Verlust der Terminalverbindung, wird heute aber von vielen Daemonen als Aufforderung interpretiert, die Konfiguration neu zu laden, ohne den Prozess komplett neu zu starten, etwa bei nginx oder rsyslog. SIGINT (Signal 2) entspricht dem Drücken von Strg+C im Terminal und wird typischerweise wie SIGTERM als Aufforderung zum sauberen Beenden behandelt.
SIGUSR1 und SIGUSR2 sind für benutzerdefinierte Zwecke reserviert und werden häufig genutzt, um Anwendungen zur Laufzeit zu steuern, etwa um Logdateien nach einer Rotation neu zu öffnen oder Debug-Ausgaben umzuschalten. SIGSTOP und SIGCONT pausieren beziehungsweise setzen einen Prozess fort, ohne ihn zu beenden, und sind wie SIGKILL nicht abfangbar. Praktisch nützlich ist das beim Debugging eines hängenden Prozesses: Mit kill -STOP lässt sich ein Prozess einfrieren, um seinen Zustand zu untersuchen, ohne dass er währenddessen weiterläuft, und mit kill -CONT anschließend wieder fortsetzen.
{
"timestamp": "2026-07-12T08:14:32Z",
"level": "info",
"pid": 18422,
"signal": "SIGUSR1",
"event": "log_files_reopened",
"message": "Worker received SIGUSR1, reopened log handles after logrotate"
}
9. Zombie-Prozesse, Fallstricke und Signal-Patterns im Vergleich
Ein Zombie-Prozess entsteht, wenn ein Kindprozess beendet ist, aber sein Elternprozess dessen Exit-Status noch nicht mit wait() abgeholt hat. Der Zombie belegt keinen Speicher und keine CPU-Zeit mehr, blockiert aber einen Eintrag in der Prozesstabelle und lässt sich, anders als oft angenommen, nicht mit kill beenden, weil er technisch bereits tot ist. Die eigentliche Lösung liegt beim Elternprozess: Entweder verarbeitet er SIGCHLD korrekt und ruft wait() auf, oder er wird selbst beendet, wodurch init beziehungsweise systemd die verwaisten Zombies übernimmt und aufräumt.
Ein weiterer häufiger Fallstrick ist ein Signal-Handler, der selbst nicht async-signal-safe ist, etwa weil er Speicher allokiert oder in eine Race Condition mit dem Hauptthread läuft. Solche Handler können zu Deadlocks führen, die erst unter Last sichtbar werden. Die folgende Übersicht zeigt typische Signal-Patterns im direkten Vergleich zwischen einer riskanten und einer robusten Umsetzung.
| Aufgabe | Riskant / Falsch | Empfohlenes Pattern | Vorteil |
|---|---|---|---|
| PHP-FPM neu laden | kill -9 $(pgrep php-fpm) |
systemctl reload php8.4-fpm |
Laufende Requests werden nicht abgebrochen |
| Prozess beenden | kill -9 <pid> sofort |
kill -TERM, warten, kill -9 als Fallback |
Cleanup-Handler laufen vollständig durch |
| Mehrere Prozesse beenden | kill -9 $(ps aux | grep name) |
pkill -TERM -f name |
Kein versehentlicher Treffer auf grep selbst |
| Skript gegen Signale absichern | Kein Handler, direktes Beenden | trap cleanup SIGTERM SIGINT |
Ressourcen werden garantiert freigegeben |
| Stop-Timeout in systemd | Standardwert unangepasst übernommen | TimeoutStopSec passend zur Anwendung |
Kein hartes SIGKILL vor Abschluss der Anwendung |
Wer diese Patterns konsequent anwendet, reduziert abgebrochene Requests, Zombie-Prozesse und ungeklärte Hänger beim Deployment spürbar. Die Kombination aus SIGTERM zuerst, einer angemessenen Wartezeit und SIGKILL nur als Fallback ist dabei kein Zusatzaufwand, sondern die Grundvoraussetzung für stabile Produktionsumgebungen.
Mironsoft
Prozessmanagement, PHP-FPM-Tuning und systemd-Konfiguration für Magento-Server
Prozesse, die sauber herunterfahren statt Requests zu verlieren?
Wir analysieren eure PHP-FPM- und systemd-Konfiguration, härten Signal-Handling in Deployment-Skripten und sorgen dafür, dass Container und Worker-Prozesse kontrolliert stoppen, statt mitten im Request gekillt zu werden.
Signal-Audit
Analyse von PHP-FPM, systemd-Units und Deployment-Skripten auf riskante kill -9 Aufrufe
Graceful Shutdown
trap-basierte Cleanup-Handler und passende Timeouts für Worker und Cronjobs
Container & systemd
TimeoutStopSec, KillSignal und stop_grace_period korrekt auf eure Workloads abstimmen
10. Zusammenfassung
Signale und Prozesskontrolle entscheiden in der Produktion darüber, ob ein Prozess kontrolliert herunterfährt oder mitten in einer Operation abbricht. SIGTERM gibt einem Prozess die Chance, offene Ressourcen freizugeben, Requests zu Ende zu bringen und sich selbst zu beenden. SIGKILL kennt diese Kulanz nicht und sollte ausschließlich als letzter Ausweg für Prozesse dienen, die auf SIGTERM nicht reagieren. Bei PHP-FPM ist der Unterschied besonders greifbar: Ein per SIGKILL getöteter Worker kann Requests mitten in der Verarbeitung abbrechen und inkonsistente Daten hinterlassen, während SIGQUIT einen echten Graceful Stop auslöst.
trap macht Bash-Skripte signalfest, pkill und killall erleichtern die Prozessverwaltung über Namensmuster, und systemd sowie Container-Runtimes bieten mit TimeoutStopSec, KillSignal und stop_grace_period die Stellschrauben, um Stop-Signale an die tatsächliche Laufzeit der Anwendung anzupassen. Wer diese Werkzeuge bewusst kombiniert, ersetzt zufällig funktionierende Kill-Befehle durch nachvollziehbares, sicheres Prozessmanagement.
Signale und Prozesskontrolle: Das Wichtigste auf einen Blick
SIGTERM vs. SIGKILL
SIGTERM ist abfangbar und höflich, SIGKILL wirkt sofort und ohne Rücksicht. Immer zuerst SIGTERM senden.
PHP-FPM sauber stoppen
SIGQUIT löst einen Graceful Stop aus, process_control_timeout begrenzt die Wartezeit bis zum SIGKILL.
trap in Skripten
trap cleanup SIGTERM SIGINT fängt Signale ab und gibt Ressourcen zuverlässig frei, außer bei SIGKILL/SIGSTOP.
systemd & Container
TimeoutStopSec, KillSignal und stop_grace_period an die reale Shutdown-Dauer der Anwendung anpassen.