Every backend developer must have faced one issue once while trying to start their local server : Port 8080 (for example) is already in use. Then many devs immediately google the issue and run the command mostly lsof -i :8080, then grab the PID and without hesitation , run kill -9 <PID>. It works perfectly fine and the port is free, but in production using kill -9 by default is like pulling the power cord from the wall every time you want to turn off your pc.
To understand why this can be critical in production we first need to understand what kill command is doing under the hood.
What actually is a Linux process?
Before talking about killing or signals, lets take a step back: What is a process?
A program (like spring boot jar file or python script) is just passive bytes sitting on your disk.
A process is that program brought to life. When you run it, the Linux kernel loads it into RAM, gives it CPU time, allocates memory and gives it a unique number tag: the PID (Process ID).
What is Linux Signal ?
One of the biggest misconception in Linux is that kill command is used solely to terminate a process but in reality it is different. In Linux, a Signal is a standardized asynchronous notification sent by the OS kernel to inform a target process that a specific event has occurred . Think of it as a way of communication in which kernel taps a running program saying that "Hey something has happened, react to it".
If you run this command in your terminal :
kill -l
You won't see kill commands , Linux will send 64 different signals such as SIGHUP(terminal closed) and SIGSEGV (segmentation fault/memory crash).
Out of these 64, we mostly deal with three signals almost every single day.
SIGINT(Signal 2): Triggered when you pressCtrl + Cin the terminal to interrupt a running process.SIGTERM(Signal 15): This is the signal when you runkill <PID>without any flags. It politely request the process to shut down.SIGKILL(Signal 9): Sent when you runkill -9 <PID>.the kernel rips the process out of memory instantly. The app gets no chance to clean up. Cannot be caught, blocked, or ignored , the guaranteed way to kill a hung process
"Wait, If Ctrl + C Works, Why Do Ports Get Stuck in the First Place?"
When you run your Spring Boot app in the terminal and press Ctrl + C, you are actually sending a SIGINT (Signal 2). The JVM catches this signal, Tomcat stops listening on port 8080, connections close, and the port is released cleanly.
So why do we still find ourselves dealing with stuck ports and kill commands?
- Closing the terminal directly: Closing your terminal window or VS Code tab without pressing
Ctrl + Cfirst leaves the child process running detached in the background. - IDE / Build tool disconnects: Tools like Maven (
./mvnw spring-boot:run) or Gradle spawn a child Java process. Stopping the build tool in an IDE sometimes fails to terminate the underlying Java process. - No keyboard in Production: In production environments, background daemons, or CI pipelines, there is no interactive terminal. You cannot press
Ctrl + C. Managing processes has to happen via scripts sending signals (kill).
SIGTERM vs. SIGKILL: Graceful Shutdown vs. The Sledgehammer
- SIGTERM (Signal 15): The Graceful Path
The critical property of SIGTERM is that it can be caught and handled by your application code.
Modern backend frameworks (Spring Boot, Express, Go, Django) register shutdown hooks that listen for SIGTERM. When received, the application enters Graceful Shutdown:
Stops accepting new traffic: Tells the load balancer it is going down.
Finishes active HTTP requests: Any user currently in the middle of a request gets their response instead of a random drop.
Closes database pools: Connections to MySQL/Postgres are cleanly closed rather than severed.
Flushes logs and caches: Cached memory is written to disk and locks are released.
Once cleanup is complete, the process exits peacefully with code 0.
2. SIGKILL (Signal 9): The Bypass
SIGKILL is completely different: your application code never even sees it.
The Linux kernel intercepts SIGKILL directly. It immediately removes the process from the CPU, frees its memory, and deletes its PID. The application gets zero milliseconds to execute any cleanup logic.
What Actually Breaks in Production with kill -9?
Running kill -9 on your laptop feels harmless because you are the only user. In production, doing it triggers silent chaos:
Ghost Database Connections: The app drops instantly without closing the connection pool. The database keeps those connections open until an idle timeout, causing connection pool exhaustion for other instances.
In-Flight Data Loss: If a user was uploading a file, submitting an order, or running a payment, the connection breaks mid-stream, leaving half-written data or corrupted states.
Orphaned Locks: Distributed locks (in Redis or Zookeeper) or file locks don't get released, blocking other background jobs until an automatic TTL kicks in.
The Golden Rule: The "Polite-First" Pattern
Next time you need to stop a running backend process, ditch the default -9 and follow this 3-step discipline:
# Step 1: Send the polite request (SIGTERM)
kill <PID>
# Step 2: Give the app a few seconds to flush buffers and close connections
sleep 5
# Step 3: Use kill -9 ONLY if the process is completely hung or frozen
kill -9 <PID>
Reserve kill -9 for its intended purpose: an emergency override for completely unresponsive processes, not your everyday off switch.
