Ordinary exceptions pass through Python’s normal error machinery and can be recorded with logging or traceback. A process can also fail inside native code, hit a segmentation fault, enter a deadlock, overflow the stack, or remain blocked indefinitely. The Python faulthandler module is designed to produce minimal stack information even in catastrophic states where conventional exception handling may no longer run.
This guide explains how to enable the module, request manual dumps, investigate timeouts, register Unix signals, and use the C stack support available in Python 3.14. It complements our articles about Python traceback, debugging with pdb, object introspection, and memory problems.
When faulthandler is useful
The module targets situations in which a normal exception may never be created. Examples include crashes in C extensions, stack overflows, aborts, blocked native calls, and thread deadlocks. Depending on the platform, it installs handlers for fatal signals such as SIGSEGV, SIGFPE, SIGABRT, SIGBUS, and SIGILL.
Because a handler can run while the process is unstable, the output is intentionally simple. It includes filenames, functions, and line numbers with limits on frames and threads. The reduced format sacrifices detail to improve the chance that some evidence is written before termination.
Enable it in application code
Call faulthandler.enable() near process startup.
import faulthandler
faulthandler.enable()Output goes to sys.stderr by default and includes all threads. Long-running services should enable it before starting worker threads and libraries that create native resources.
Enable it from the command line
When source code cannot be changed, start Python with -X faulthandler.
python -X faulthandler app.pyThe environment variable provides another option:
PYTHONFAULTHANDLER=1 python app.pyPython Development Mode enables the handler automatically. Command-line activation is especially useful while reproducing failures in third-party applications.
Check and change the state
is_enabled() reports whether fatal handlers are active.
import faulthandler
if not faulthandler.is_enabled():
faulthandler.enable()disable() removes handlers installed by enable(). Most server processes leave the feature active for their entire lifetime.
Request a manual dump
dump_traceback() prints the current stack of every thread without causing a crash.
faulthandler.dump_traceback()This helps investigate a process that appears slow or frozen. Pass all_threads=False to include only the current thread.
faulthandler.dump_traceback(all_threads=False)For deadlocks, the relationship among all thread stacks is usually the most valuable evidence.
Write dumps to a file
The destination must remain open while the handler uses it.
log_file = open("faults.log", "a", buffering=1)
faulthandler.enable(file=log_file, all_threads=True)The official faulthandler documentation warns that the module keeps the underlying file descriptor. If the file is closed and that descriptor number is reused, a later dump may be written to an unrelated destination.
Log rotation and descriptors
When log rotation replaces a file, call enable() again with the newly opened destination. The same rule applies to dump_traceback_later() and register().
def reopen_fault_log():
global log_file
log_file.close()
log_file = open("faults.log", "a", buffering=1)
faulthandler.enable(file=log_file, all_threads=True)Understand whether the service is restarted, receives a signal, or keeps the old inode after rotation.
Diagnose operations that time out
dump_traceback_later() schedules a dump after a timeout.
faulthandler.dump_traceback_later(
30,
repeat=False,
)Cancel it when the operation completes:
try:
run_long_operation()
finally:
faulthandler.cancel_dump_traceback_later()This pattern provides evidence for frozen tests, blocked native calls, and deadlocks that never raise an exception.
Repeated dumps
With repeat=True, the watchdog writes stacks periodically.
faulthandler.dump_traceback_later(
60,
repeat=True,
)Several snapshots reveal whether threads remain on exactly the same lines or are merely progressing slowly. Cancel the timer after diagnosis to avoid unnecessary log growth.
Exit after the timeout
The exit=True option calls _exit(1) after writing the dump.
faulthandler.dump_traceback_later(
120,
exit=True,
)_exit() terminates immediately. It does not run finally blocks, atexit handlers, or normal buffer flushing. Use it only when remaining stuck is worse than an abrupt stop and a supervisor can restart the service safely.
On-demand diagnosis with signals
On Unix systems, register() associates a user signal with a stack dump.
import signal
faulthandler.register(
signal.SIGUSR1,
all_threads=True,
)An operator can then request diagnostics without stopping the process:
kill -USR1 PROCESS_IDThe official signal documentation explains signal behavior and platform differences. User-signal registration is unavailable on Windows.
Preserve an existing signal handler
Set chain=True to invoke the previous handler after dumping stacks.
faulthandler.register(
signal.SIGUSR1,
all_threads=True,
chain=True,
)Use this carefully because the prior handler may terminate the process or perform an incompatible action. unregister() removes the registration installed by faulthandler.
C stacks in Python 3.14
Python 3.14 adds dump_c_stack() and the c_stack option to enable(). When supported by the build and operating system, the report includes native frames after the Python frames.
faulthandler.enable(c_stack=True)
faulthandler.dump_c_stack()This is valuable for C extensions, database drivers, scientific libraries, cryptographic bindings, and image-processing packages. Symbols may be incomplete, and stack generation can be slow depending on available DWARF debugging information.
C stack compatibility
Not every platform offers the necessary backtrace(), dladdr1(), compiler behavior, or symbol information. When unsupported, the module prints an explanatory error instead of a C stack. Treat that outcome as an environment limitation rather than an application failure.
Free-threaded builds
In Python 3.14, when the GIL is disabled, the fatal handler dumps only the current thread to reduce the risk of data races. Therefore, all_threads=True may produce different results on a free-threaded build.
Always record the interpreter version, build type, operating system, architecture, and container image with the dump.
Output limitations
The module relies on signal-safe operations and cannot use normal heap allocation. Output is ASCII with replacement escaping, strings are capped at 500 characters, and dumps are limited to 100 frames and 100 threads. Source lines are not included.
The ordering also differs from normal tracebacks: the most recent call appears first. Operational documentation should explain this to responders.
Automated test watchdogs
Use a timeout dump around tests that can freeze.
def test_batch_processing():
faulthandler.dump_traceback_later(10)
try:
result = process_batch()
assert result.ok
finally:
faulthandler.cancel_dump_traceback_later()Avoid extremely short timeouts in continuous integration, where overloaded runners can be temporarily slow. The purpose is to expose a real hang, not create noise.
Containers and service managers
In containers, keep stderr connected to the logging system or use a persistent volume. Confirm size limits, rotation, and retention. A report containing many threads can still be substantial despite module limits.
With Kubernetes, systemd, or another supervisor, combine exit=True with restart policies only after verifying that interrupted operations are idempotent and recoverable.
Operational security
Dumps usually do not include local variables, but they expose paths, function names, internal architecture, and current thread activity. Restrict access and never include them in public HTTP responses.
Do not intentionally trigger a segmentation fault in production merely to test the setup. Validate it in a separate process, test container, or staging environment.
faulthandler versus traceback
traceback provides rich formatting for ordinary exceptions, including chains and structured frame summaries. faulthandler is deliberately minimal and remains useful when the process is frozen or has crashed in native code. Mature systems use both.
faulthandler versus pdb
pdb pauses execution, inspects variables, and steps through code. It requires a process healthy enough for interaction. faulthandler creates a passive snapshot, which suits unattended servers and failures that terminate the interpreter.
Common mistakes
- Closing the file used by the handler.
- Rotating logs without reconfiguring the descriptor.
- Forgetting to cancel a scheduled dump.
- Using
exit=Truewithout supervision or recovery. - Expecting local variables and full source lines.
- Ignoring Unix and Windows differences.
- Treating unavailable C stacks as application errors.
- Publishing dumps without access controls.
Best practices
- Enable the module at process startup.
- Write to a persistent destination.
- Reconfigure after log rotation.
- Use watchdog timeouts for tests and critical operations.
- Register an on-demand signal on Unix.
- Record interpreter and platform details.
- Combine dumps with logging, metrics, and normal tracebacks.
- Protect and retain reports according to security policy.
Conclusion
The Python faulthandler module provides a last-resort observability layer for crashes, deadlocks, timeouts, stack overflows, and native-code failures. It can be enabled in source code, through an environment variable, or with -X, and it supports manual, scheduled, and signal-triggered dumps.
Its strength comes from simplicity. Even without local variables or rich formatting, a list of threads and frames may identify the lock, extension, or function where a process stopped. With disciplined file-descriptor handling, operational limits, and supervisor integration, faulthandler turns silent hangs into actionable diagnostics.







