How to terminate a python subprocess launched with shellTrue
Data Structures & Algorithms practice on Codemia
Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.
Introduction
When you launch a subprocess with shell=True, Python starts a shell process (/bin/sh on Unix, cmd.exe on Windows), which then spawns your actual command as a child. Calling process.terminate() or process.kill() only kills the shell, not the child command. To kill both, you need to kill the entire process group using os.killpg(), or avoid shell=True altogether and pass the command as a list.
The Problem
proc.pid is the PID of the shell, not the sleep command. Terminating the shell leaves the child as an orphan process.
Fix 1: Kill the Process Group (Recommended)
Start the subprocess as a process group leader and kill the entire group:
os.setsid makes the shell process the leader of a new session/process group. os.killpg() sends the signal to every process in that group, including the child command.
For SIGKILL (force kill):
Fix 2: Avoid shell=True
The simplest and safest approach — pass the command as a list:
Without shell=True, proc.pid is the PID of the actual command, and terminate() works as expected.
When you need shell features (pipes, wildcards, variable expansion), use Python equivalents:
Fix 3: Using subprocess.run with timeout
subprocess.run() with timeout handles cleanup automatically:
However, subprocess.run with shell=True and timeout may still leave orphan children. Combine with preexec_fn=os.setsid and a custom handler for reliable cleanup.
Fix 4: Using psutil for Cross-Platform Process Tree Kill
The psutil library can kill an entire process tree:
This works on both Unix and Windows.
Fix 5: Using start_new_session (Python 3.2+)
Instead of preexec_fn=os.setsid, use the start_new_session parameter:
start_new_session=True is the recommended replacement for preexec_fn=os.setsid — it is safer because preexec_fn is not compatible with threads.
Windows Considerations
On Windows, process groups work differently:
On Windows, os.killpg and os.setsid are not available. Use CREATE_NEW_PROCESS_GROUP and CTRL_BREAK_EVENT instead.
Common Pitfalls
- Calling
terminate()on a shell=True process: This only kills the shell (/bin/sh), leaving the actual command running as an orphan. Always useos.killpg()withstart_new_session=True, or avoidshell=True. - Using
preexec_fn=os.setsidin threaded code:preexec_fnruns betweenfork()andexec(), which is not safe in multi-threaded programs. Usestart_new_session=Trueinstead (Python 3.2+). - Forgetting to call
proc.wait()after kill: Withoutwait(), the process becomes a zombie on Unix. Always callproc.wait()(orproc.communicate()) after sending a kill signal. - Security risks of shell=True: Passing user input in a
shell=Truecommand enables shell injection attacks.Popen(f"grep {user_input} file.txt", shell=True)allows arbitrary command execution. Use list form:Popen(["grep", user_input, "file.txt"]). - SIGTERM vs SIGKILL:
SIGTERMallows the process to clean up (close files, release resources).SIGKILLterminates immediately with no cleanup. TrySIGTERMfirst, thenSIGKILLafter a timeout if the process does not exit.
Summary
shell=Truecreates a shell parent process —terminate()kills the shell but not its child- Use
start_new_session=Trueandos.killpg()to kill the entire process group - Best approach: avoid
shell=Trueand pass commands as a list (["cmd", "arg1", "arg2"]) - Use
psutilfor cross-platform process tree management - On Windows, use
CREATE_NEW_PROCESS_GROUPandCTRL_BREAK_EVENT - Always call
proc.wait()after killing a process to prevent zombies
Related reading
- How to test if a dictionary contains a specific key?
- How to test if a string contains one of the substrings in a list, in pandas?
- How to test NoneType in python?
- How to test single file under pytest
- How to train an SVM classifier on a satellite image using Python
- How to transform items using sklearn Pipeline?
- How to trigger message send of Fastapi websocket outside of Fastapi app
- How to truncate the time on a datetime object?
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseNo course covers this one yet
Basis Lab writes one for you from a sentence about what you want to be able to do, then teaches it and checks you understood. Your first course is free.
Build my courseData Structures & Algorithms practice on Codemia
Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.