kill <pid> stops a process in Linux, and kill -9 <pid> stops one that will not stop. The difference is the signal: plain kill sends SIGTERM, which asks the process to shut down and gives it the chance to flush its buffers, close its connections, and finish what it was doing. kill -9 sends SIGKILL, which the process cannot catch, cannot handle, and never learns about - the kernel simply stops it. Reaching for -9 first is a habit worth breaking, because half the corrupt-state mysteries in production start with someone killing a process that was mid-write and never got to finish.
Everyone learns kill -9 first because it always works. That is precisely the problem: it always works, including the times when it should not have.
Table of contents
- Finding the process
- The signals that matter
- What kill -9 actually costs you
- When SIGTERM does nothing
- Use systemctl for services
- This is really about graceful shutdown
- How this fits the rest of the stack
- FAQ
Finding the process
pgrep -af nginx # PIDs plus full command line. The good one.
ps aux | grep -v grep | grep nginx
pidof nginx # just the PIDs
ss -tulpn | grep :8080 # what is holding a port?
lsof -i :8080 # same question, different tool
lsof /var/log/app.log # what has this file open?
pgrep -af beats ps aux | grep because it does not match its own grep process, which is the thing that has confused every person who has ever run the second one.
The port question comes up constantly - address already in use, and you need to know what is squatting on 8080. ss -tulpn | grep :8080 gives you the PID and the command in one line.
The signals that matter
There are dozens. You need four.
kill <pid> # SIGTERM (15) - please stop. The default.
kill -9 <pid> # SIGKILL (9) - stop now. Cannot be caught.
kill -HUP <pid> # SIGHUP (1) - reload config, usually
kill -INT <pid> # SIGINT (2) - what Ctrl+C sends
- SIGTERM (15) - the polite request. The process can catch it, finish the current request, flush to disk, close its database connections, and exit cleanly. This is the default for a reason.
- SIGKILL (9) - the kernel stops the process. It cannot be caught, blocked, or ignored. No cleanup, no flush, no goodbye.
- SIGHUP (1) - historically hang up. By convention many daemons reload their config on it without restarting, which is how you apply an nginx change with no downtime.
- SIGINT (2) - Ctrl+C. Like SIGTERM, catchable.
The correct sequence is TERM, wait, then KILL:
kill 1234
sleep 5
kill -0 1234 2>/dev/null && kill -9 1234 # still alive? Then force it.
kill -0 sends no signal at all - it just checks whether the process exists and you have permission to signal it. It is the standard way to test liveness in a script.
What kill -9 actually costs you
SIGKILL is not just faster. It skips everything the process would have done on the way out:
- Buffered writes are lost. Data your application believed it had written, sitting in a buffer, never reaching disk.
- Temp files stay. Cleanup handlers never run. Lock files remain, and the next start may refuse because of a stale lock.
- Connections are not closed. The database sees an abrupt disconnect and holds the session until its own timeout.
- Child processes are orphaned. They do not receive the signal and keep running, now reparented to init.
- Transactions are abandoned mid-flight. Whatever consistency guarantees you had, you no longer have.
- Shared memory and semaphores can leak, which is why some daemons refuse to restart after a -9.
This is the origin of a whole genre of it worked yesterday. Someone -9’d a process last week, a lock file survived, and the service now fails to start with an error that has nothing to do with the actual cause.
SIGKILL is the right tool for a process that is genuinely stuck - an infinite loop, an ignored SIGTERM, a wedged state. It is the wrong tool for I will just restart this quickly.
When SIGTERM does nothing
Sometimes you send TERM and the process sits there. The reasons, in order of likelihood:
- It is handling the signal badly. It caught SIGTERM, started a graceful shutdown, and got stuck waiting for something - a connection that will not drain, a request that will not finish.
- It is ignoring SIGTERM. Legal, and occasionally deliberate.
- It is in uninterruptible sleep. State
Dinps. Blocked in a kernel call, usually on I/O - a hung NFS mount is the classic. SIGKILL will not work either. Nothing will, until the I/O completes or fails. - It is already a zombie. State
Z. It has exited; the parent has not reaped it. You cannot kill it - kill the parent, or wait for init to adopt it.
ps -o pid,stat,comm -p 1234
# S sleeping - normal
# R running - normal
# D uninterruptible - kill -9 will NOT help. It is I/O.
# Z zombie - already dead. Kill the parent.
That D state catches people out badly: they escalate to kill -9, nothing happens, and they conclude the system is broken. The process is blocked in the kernel and no signal can touch it. Fix the I/O - the stuck mount, the failing disk - and it will resolve.
Use systemctl for services
If the process is a managed service, do not go hunting for PIDs at all.
systemctl stop nginx # sends TERM, waits, escalates to KILL
systemctl restart nginx
systemctl reload nginx # SIGHUP - config reload, no downtime
systemctl status nginx
systemd already implements the correct sequence: TERM, wait TimeoutStopSec, then KILL. It also tracks the whole cgroup, so child processes go too rather than being orphaned - which manual kill on the parent PID does not do.
Killing a service’s PID by hand is worse than systemctl stop in two ways: systemd may restart it immediately because Restart=always is set, making your kill look like it did nothing, and the children survive. Use the service manager for anything the service manager owns.
This is really about graceful shutdown
The signal story matters most in containers, where SIGTERM is not an edge case - it is the normal path. Every deploy, every scale-down, every rescheduling sends SIGTERM and then waits a grace period before SIGKILL.
So a process that ignores SIGTERM gets killed on every single deploy, dropping in-flight requests each time. Users see occasional 502s during releases and nobody connects it to signal handling.
What a well-behaved service does on SIGTERM:
- Stop accepting new work - fail the readiness check so traffic drains away.
- Finish in-flight requests, within a bounded time.
- Flush buffers, commit or roll back cleanly, close connections.
- Exit with status 0.
import signal, sys
def on_term(signum, frame):
server.stop_accepting()
server.drain(timeout=25) # inside the grace period
sys.exit(0)
signal.signal(signal.SIGTERM, on_term)
One container-specific trap: if your image uses shell form (CMD python app.py), your process runs as a child of /bin/sh, which is PID 1 and does not forward signals. Your app never receives SIGTERM at all and is always SIGKILLed after the grace period. Use exec form - CMD ["python", "app.py"] - so your process is PID 1 and gets the signal. This one line explains a lot of mysterious deploy-time errors.
How this fits the rest of the stack
Graceful shutdown is the difference between a deploy nobody notices and a deploy that drops requests every time. It costs one signal handler and one line in a Dockerfile, and it is invisible until you look for it. The surrounding question - what the platform does with your process during a release - is worth understanding before you pick one. The RunxBuild hosting calculator shows the service, database, storage, and bandwidth as separate line items, and the RunxBuild dashboard is where the team watches deploys and restarts as they happen.
Useful related references:
- How to Kill a Process in Linux: kill, pkill, killall, and Signals
- Linux Kill Process: kill, pkill, killall, and the Right Defaults
- How to Rename a File in Linux: mv, rename, and git mv
- Services on RunxBuild
FAQ
What is the difference between kill and kill -9 in Linux?
kill sends SIGTERM, a request the process can catch so it can flush buffers, close connections, and exit cleanly. kill -9 sends SIGKILL, which cannot be caught or ignored - the kernel stops the process immediately with no cleanup. Always try kill first and give it a few seconds; -9 risks lost writes, stale lock files, and orphaned children.
How do I find the PID of a process in Linux?
pgrep -af name gives PIDs with the full command line and, unlike ps aux | grep name, does not match its own grep process. For a port, ss -tulpn | grep :8080 or lsof -i :8080 shows what is listening and which PID owns it - the fastest way to resolve an address-already-in-use error.
Why won’t kill -9 kill my process?
Most likely it is in uninterruptible sleep - state D in ps - blocked in a kernel call, usually on I/O such as a hung NFS mount. No signal, including SIGKILL, can interrupt that; the process resolves only when the I/O completes or fails. The other case is a zombie, state Z, which has already exited and just has not been reaped - kill the parent instead.
What is SIGTERM and why does it matter in containers?
SIGTERM is the polite stop signal a process can catch to shut down cleanly. It matters in containers because it is the normal path: every deploy and scale-down sends SIGTERM, waits a grace period, then sends SIGKILL. A service that ignores SIGTERM gets force-killed on every single deploy, dropping in-flight requests - which surfaces as intermittent 502s during releases.
Should I use kill or systemctl stop for a service?
systemctl stop for anything systemd manages. It already does the correct sequence - SIGTERM, wait for the configured timeout, then SIGKILL - and it signals the whole cgroup so child processes stop too rather than being orphaned. Killing the PID by hand can also appear to do nothing, because a unit with Restart=always will simply start it again.