os.kill

Signals, not killing: os.kill(pid, signal.SIGTERM) asks a process to stop; the process decides what to do unless the signal is SIGKILL. os.kill works on Unix and Windows (not WASI or iOS) but means different things there, killpg is Unix only, and abort ends the calling process without cleanup.

os functionPython 3 (all versions); os.kill on Windows 3.2+
Common call
os.kill(pid, signal.SIGTERM)
Returns
None - then wait for the process
Replaces
Popen.terminate() / Popen.kill() for processes you started
Watch out
Windows: every signal except CTRL_C/CTRL_BREAK events terminates
os.kill(pidpid — kill: the target process id. On Unix 0 and negative values address process groups (see kill(2)).type: int · required, sigsig — A signal number, best taken from the signal module (signal.SIGTERM, signal.SIGKILL on Unix). On Unix 0 sends nothing and only checks that the process exists.type: int | signal.Signals · required, /) / os.killpg(pgid, sigsig — A signal number, best taken from the signal module (signal.SIGTERM, signal.SIGKILL on Unix). On Unix 0 sends nothing and only checks that the process exists.type: int | signal.Signals · required, /) / os.abort()
→ None

Parameters

NameTypeRequiredDescription
pidintyeskill: the target process id. On Unix 0 and negative values address process groups (see kill(2)).
pgidintyeskillpg: the process group id (Unix only).
sigint | signal.SignalsyesA signal number, best taken from the signal module (signal.SIGTERM, signal.SIGKILL on Unix). On Unix 0 sends nothing and only checks that the process exists.

Return value

None — kill and killpg return None once the signal is sent (they do not wait for the process to end). abort never returns.

Common patterns

Stop politely, then force (Unix)
SIGTERM lets the process clean up; SIGKILL cannot be caught.
import os, signal, time
os.kill(pid, signal.SIGTERM)
time.sleep(5)
try:
    os.kill(pid, signal.SIGKILL)
except ProcessLookupError:
    pass  # already gone
Does this pid exist? (Unix only)
Signal 0 checks without sending anything. Do not do this on Windows - see the FAQ.
import os
def pid_exists(pid):
    try:
        os.kill(pid, 0)
    except ProcessLookupError:
        return False
    except PermissionError:
        return True  # exists, owned by someone else
    return True
Stop a whole process tree (Unix)
Start the child in its own session, then signal its group.
import os, signal, subprocess
p = subprocess.Popen(['./server.sh'], start_new_session=True)
os.killpg(os.getpgid(p.pid), signal.SIGTERM)
p.wait()
Processes you started: use Popen
terminate()/kill() are portable and do nothing if the process already ended.
import subprocess
p = subprocess.Popen(['long_job'])
p.terminate()
try:
    p.wait(timeout=5)
except subprocess.TimeoutExpired:
    p.kill()
    p.wait()

Examples

1. Send SIGTERM to a child you started
import os, signal, subprocess, sys p = subprocess.Popen([sys.executable, '-c', 'import sys; sys.stdin.read()'], stdin=subprocess.PIPE) os.kill(p.pid, signal.SIGTERM) p.wait() p.stdin.close() (p.returncode != 0, abs(p.returncode) == signal.SIGTERM)
Returns
(True, True)
2. Signals are an IntEnum
import signal (int(signal.SIGTERM), signal.SIGTERM.name, signal.Signals(15))
Returns
(15, 'SIGTERM', <Signals.SIGTERM: 15>)
3. SIGINT is 2 on Windows and Linux
import signal int(signal.SIGINT)
Returns
2
4. The portable way: Popen.terminate
import subprocess, sys p = subprocess.Popen([sys.executable, '-c', 'import sys; sys.stdin.read()'], stdin=subprocess.PIPE) p.terminate() rc = p.wait() p.stdin.close() rc != 0
Returns
True
5. terminate() after exit does nothing
import subprocess, sys p = subprocess.Popen([sys.executable, '-c', 'pass']) p.wait() p.terminate() p.returncode
Returns
0
6. os.kill after exit raises OSError
import os, signal, subprocess, sys p = subprocess.Popen([sys.executable, '-c', 'pass']) p.wait() try: os.kill(p.pid, signal.SIGTERM) except OSError as e: result = isinstance(e, OSError) result
Returns
True
7. os.abort in a child: no cleanup, no flush
import subprocess, sys r = subprocess.run([sys.executable, '-c', 'import os; print("lost"); os.abort()'], capture_output=True, text=True) (r.returncode != 0, r.stdout)
Returns
(True, '')

Pitfalls

1. Reading returncode right after the signal
os.kill only sends the signal. Until you wait (or poll), Popen.returncode stays None - and on Unix the dead child stays a zombie.
no wait
import os, signal, subprocess, sys
p = subprocess.Popen([sys.executable, '-c', 'import sys; sys.stdin.read()'], stdin=subprocess.PIPE)
os.kill(p.pid, signal.SIGTERM)
rc = p.returncode
p.wait()
p.stdin.close()
rc is None
True
wait()
import os, signal, subprocess, sys
p = subprocess.Popen([sys.executable, '-c', 'import sys; sys.stdin.read()'], stdin=subprocess.PIPE)
os.kill(p.pid, signal.SIGTERM)
rc = p.wait()
p.stdin.close()
rc != 0
True
2. Passing the Popen object instead of its pid
os.kill needs the integer pid. For a Popen object, use p.pid - or simply p.terminate() / p.send_signal(sig).
os.kill(p, ...)
import os, signal, subprocess, sys
p = subprocess.Popen([sys.executable, '-c', 'pass'])
p.wait()
try:
    os.kill(p, signal.SIGTERM)
except TypeError as e:
    result = str(e)
result
"'Popen' object cannot be interpreted as an integer"
p.terminate()
import subprocess, sys
p = subprocess.Popen([sys.executable, '-c', 'pass'])
p.wait()
p.terminate()
p.returncode
0

When to use

Use it
  • Signalling a process you only know by pid (from a pid file, ps, psutil)
  • killpg: stopping a child together with everything it started (Unix)
  • abort: crash on purpose to get a core dump for debugging (Unix)
Reach for something else
  • Processes you started with subprocess → Popen.terminate() / kill() / send_signal()
  • Checking whether a pid exists on Windows - os.kill(pid, 0) is not a test there
  • Ending your own program normally → sys.exit()

Notes

CPython impl
os.kill calls kill(2) on Unix; on Windows it calls GenerateConsoleCtrlEvent for CTRL_C_EVENT / CTRL_BREAK_EVENT and TerminateProcess (exit code = sig) for any other value. os.abort calls the C abort() and does not run Python signal handlers for SIGABRT.
Availability
kill: Unix, Windows (not WASI or iOS). killpg: Unix only (not WASI or iOS). abort: everywhere.
Exit codes
A child stopped by os.kill(pid, signal.SIGTERM) had returncode -15 on Linux and 15 on Windows in our test; Popen.kill() gave -9 on Linux and 1 on Windows. Portable code checks returncode != 0.
Signal numbers
signal.SIGTERM is 15 and SIGINT 2 on both, but SIGABRT is 6 on Linux and 22 on Windows, and SIGKILL does not exist on Windows. Always use the names.
abort on Windows
The docs say the process returns exit code 3; in our test with Windows CPython 3.13.3 a child calling os.abort() ended with 3221226505 instead. On Linux the child was killed by SIGABRT (returncode -6).

FAQ

Killing processes cannot run inside the page, and the semantics differ: os.kill exists on Unix and Windows (not WASI or iOS), killpg is Unix only. On Windows only signal.CTRL_C_EVENT and CTRL_BREAK_EVENT are delivered as signals (to console processes sharing a console); any other value terminates the process with that number as exit code. The examples only signal a Python child they started and print results that are the same on both systems.