Skip to content
Go back

Why kill -9 Is the Wrong Default

By SumGuy 5 min read
Why kill -9 Is the Wrong Default

You’ve typed it a thousand times: kill -9 1234. And it works. The process dies. Problem solved. But here’s the thing: using -9 by default is like setting your house on fire to get rid of a spider. It works, but the collateral damage is usually not worth it.

Signals are how Unix processes communicate with each other. kill sends signals. Understanding which signal to send and why is the difference between graceful shutdowns and corrupted data.

How Signals Work

When you run kill [pid], you’re sending a signal to that process. The process catches the signal and decides what to do. By default, most signals make the process exit, but the process can handle them first — clean up resources, close database connections, flush logs, whatever.

SIGKILL (signal 9) is different. It can’t be caught. The kernel kills the process immediately, no questions asked.

The Signal Reference

Here are the ones you actually need to know:

SIGTERM (15) — The Default

Terminal window
$ kill 1234
# Same as:
$ kill -15 1234

“Please terminate.” The process receives this signal and gets to clean up. Close files, finish transactions, log that it’s shutting down. A well-written app will exit gracefully within a few seconds.

When to use: 95% of the time. Always try this first.

SIGKILL (9) — The Nuclear Option

Terminal window
$ kill -9 1234

“Die immediately. No cleanup, no questions.” The kernel forcefully terminates the process. It can’t be caught or ignored. The process doesn’t get to run any shutdown code.

When to use: Only when SIGTERM doesn’t work after a reasonable timeout (usually 10-30 seconds). Deadlocked processes, kernel bugs, things that won’t respond to anything else.

SIGHUP (1) — Reload Configuration

Terminal window
$ kill -1 1234

Originally meant “the terminal hung up,” but modern daemons use it to mean “reload your config file.” Nginx, Apache, many daemons support this.

When to use: When you want a process to reload configuration without restarting.

SIGSTOP (19) / SIGCONT (18) — Pause and Resume

Terminal window
$ kill -19 1234 # Pause (suspend)
$ kill -18 1234 # Resume

Freeze a process without killing it. Useful for debugging or temporarily pausing a resource hog.

The Graceful Shutdown Pattern

This is how you should terminate processes:

Terminal window
# Step 1: Ask nicely
$ kill -15 1234
# Step 2: Wait a bit (give it time to clean up)
$ sleep 5
# Step 3: Check if it's gone
$ kill -0 1234 && echo "Still running" || echo "Stopped"
# Step 4: If it's still there, force it
$ kill -9 1234

Or as a one-liner:

Terminal window
pkill -15 myapp || (sleep 5 && pkill -9 myapp)

This sends SIGTERM, waits 5 seconds, then sends SIGKILL only if it’s still running.

How Apps Handle Signals

A well-written app catches SIGTERM and shuts down gracefully:

Terminal window
#!/bin/bash
cleanup() {
echo "Received SIGTERM, cleaning up..."
# Close connections, flush data, etc.
exit 0
}
trap cleanup SIGTERM
while true; do
echo "Running..."
sleep 1
done

When you send SIGTERM, the trap catches it, prints “Received SIGTERM”, and exits cleanly.

If you use kill -9, the trap never runs. The process is gone. Instant. If it had an open database transaction, that gets rolled back (usually). If it was writing to a file, the write is incomplete. You lose data.

Using pkill and killall

These are friendlier than kill:

Terminal window
# Kill by process name
$ pkill -15 nginx
# Kill and wait for graceful shutdown
$ pkill -15 -x myapp
# Kill all processes owned by a user
$ pkill -15 -u someuser
# Kill with a grep pattern
$ pkill -15 -f "python.*server.py"
# See what would be killed without actually killing
$ pkill -15 --dry-run python

killall is similar but matches the exact command name:

Terminal window
$ killall -15 nginx

Systemd Does This Right

When you run systemctl stop myapp, systemd sends SIGTERM, waits (by default, 90 seconds), then sends SIGKILL if needed. You can configure the timeout:

/etc/systemd/system/myapp.service
[Service]
Type=simple
ExecStart=/usr/bin/myapp
TimeoutStopSec=30
KillMode=mixed
KillSignal=SIGTERM

TimeoutStopSec=30 gives the app 30 seconds to shut down gracefully. KillSignal=SIGTERM specifies the initial signal. KillMode=mixed sends SIGTERM to the main process and SIGKILL to child processes if they don’t stop.

Why -9 Causes Problems

Real example: You have a Python app with an open database connection. You kill -9 it.

  1. The connection never gets closed cleanly.
  2. The database sees an abrupt disconnect.
  3. The transaction gets rolled back.
  4. Any changes made but not committed are lost.
  5. The database might hold locks briefly, slowing other connections.

If you’d sent SIGTERM first, the app could have:

  1. Received the signal
  2. Finished the current transaction
  3. Closed the connection cleanly
  4. Exited gracefully

Same end state (app is dead), but the data is safe.

The Checklist

When terminating a process:

  1. kill -15 [pid] — Ask nicely
  2. Wait 5-10 seconds
  3. Check: ps aux | grep [pid] — Is it gone?
  4. If yes, done. If no, kill -9 [pid] — Force it.

For services in systemd:

  1. systemctl stop myapp — Respects shutdown timeouts automatically
  2. Done. No need to kill -9.

For your own apps:

  1. Catch SIGTERM in your code
  2. Implement graceful shutdown
  3. Close connections, flush state, exit cleanly

That’s it. You’ll avoid data corruption, database locks, and 3 AM pages about mysterious errors.

Using kill -9 by default is like never using the brakes on your car, just driving into a wall. It works, but the damage adds up.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it may appear here.


Previous Post
Why the `latest` Docker Tag Is Lying to You
Next Post
Multi-Platform Docker Builds with buildx

Related Posts