Unix Signals and Child Processes: What Your Service Must Clean Up
Follow signals from a container stop through shell wrappers and child processes, and build shutdown behavior that preserves work.
On this page
Key takeaways
- A process should handle termination as a lifecycle event, not an exceptional surprise.
- Shell wrappers can interfere with signal delivery unless they replace themselves with the service process.
- Graceful shutdown needs a deadline and a clear policy for in-flight work.
- Unreaped children become zombies; orphaned work can outlive the request that started it.
A process receives a lifecycle request
On Unix-like systems, signals are asynchronous notifications sent to a process. SIGTERM is commonly used to request orderly shutdown; SIGKILL cannot be caught or deferred and stops a process immediately. A reliable service treats termination as a normal lifecycle path: stop accepting new work, allow safe in-flight work to finish within a deadline, close resources, then exit.
In orchestrated environments, the grace period is finite. If cleanup takes longer than the configured deadline, the runtime may send a forceful kill. The application should therefore stop intake promptly, set a bounded drain window, and report how many requests or jobs remain. A shutdown handler that waits forever is not graceful.
Shell wrappers can change signal behavior
This container command creates a shell parent and a child process:
#!/bin/sh
my-service --serve
Depending on how the process is launched, the termination signal may reach the wrapper but not be forwarded as expected. Replacing the shell with the service makes the service the container's main process:
#!/bin/sh
exec my-service --serve
The exec form also makes the service's exit code visible directly. When a wrapper intentionally supervises multiple children, it must forward signals, wait for children, and propagate failure status deliberately.
Child processes need ownership
When a parent exits, its children do not necessarily stop. A child can continue consuming CPU, hold file descriptors, or perform work after the user-facing request has failed. Parents should track spawned processes, send an appropriate termination signal during shutdown, wait for completion, and reap exit status. A terminated child that has not yet been waited on can remain as a zombie process entry until its parent reaps it.
Avoid spawning long-lived work from a request handler unless the application owns its lifecycle and durability. For durable background work, use a job system with acknowledgements, retries, and observability rather than an untracked child process.
Test the stop path
Run the service under the same entrypoint and supervisor used in production. Send SIGTERM during idle time and during active work. Verify that listeners close, in-flight operations follow the documented policy, child processes exit, and logs make the sequence visible. A healthy shutdown path is part of service reliability, not deployment polish.