A Practical Guide to chan os.Signal in Go
Lukas Schneider
DevOps Engineer · Leapcell

chan os.Signal
is a channel used to receive signals sent by the operating system. This is very useful when you need to gracefully handle system signals (such as the interrupt signal SIGINT
or the termination signal SIGTERM
), for example, safely shutting down the program, releasing resources, or performing cleanup operations when receiving these signals.
Main Concepts
What is os.Signal
?
os.Signal
is an interface defined in Go’s standard library to represent operating system signals. It inherits from syscall.Signal
and provides a cross-platform way to handle signals across different operating systems.
type Signal interface { String() string Signal() // inherits from syscall.Signal }
What is chan os.Signal
?
chan os.Signal
is a channel specifically designed to receive os.Signal
type data. By listening to this channel, a program can respond to signals from the operating system and perform the corresponding handling logic.
Common Signals
Here are some common operating system signals and their uses:
- SIGINT: Interrupt signal, usually triggered when the user presses
Ctrl+C
. - SIGTERM: Termination signal, used to request the program to exit normally.
- SIGKILL: Forced termination signal, cannot be caught or ignored (and also cannot be handled in Go).
- SIGHUP: Hangup signal, usually used to notify the program to reload configuration files.
kill
is the most commonly used command-line tool for sending signals. Its basic syntax is as follows:
kill [options]<signal>
Here, <signal>
can be either the signal name (such as SIGTERM
) or the signal number (such as 15
), and <PID>
is the process ID of the target process.
Example
Send a SIGTERM
signal (request the process to exit normally):
kill -15 <PID> # or kill -s SIGTERM <PID> # or kill <PID> # sends SIGTERM by default
Send a SIGKILL
signal (force terminate a process):
kill -9 <PID> # or kill -s SIGKILL <PID>
Send a SIGINT
signal (interrupt signal):
kill -2 <PID> kill -s SIGINT <PID>
Using chan os.Signal
to Listen for Signals
Go provides the signal.Notify
function to register the signals to listen for and send those signals to a specified channel. The following is a typical usage example:
package main import ( "fmt" "os" "os/signal" "syscall" ) func main() { // Create a channel to receive signals sigs := make(chan os.Signal, 1) // Register signals to be listened for // Here we listen for SIGINT and SIGTERM signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) fmt.Println("The program is running. Press Ctrl+C or send a SIGTERM signal to exit.") // Block the main goroutine until a signal is received sig := <-sigs fmt.Printf("Received signal: %v", sig) // Perform cleanup operations or other processing before exiting fmt.Println("Exiting program gracefully...") }
Example Output
The program is running. Press Ctrl+C or send a SIGTERM signal to exit. ^C Received signal: interrupt Exiting program gracefully...
In the above example:
- A
chan os.Signal
channelsigs
with a buffer size of 1 was created. - The signals
SIGINT
andSIGTERM
were registered usingsignal.Notify
, and these signals are sent to thesigs
channel. - The main goroutine blocks on the
<-sigs
operation, waiting to receive a signal. - When the user presses
Ctrl+C
or the program receives aSIGTERM
signal, thesigs
channel receives the signal, the program prints the received signal, and performs exit processing.
Gracefully Handling Multiple Signals
You can listen for multiple types of signals simultaneously and perform different handling logic based on the specific signal.
Example Code
package main import ( "fmt" "os" "os/signal" "syscall" ) func main() { sigs := make(chan os.Signal, 1) // Register SIGINT, SIGTERM, and SIGHUP signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) fmt.Println("The program is running. Press Ctrl+C to send SIGINT, or send other signals to test.") for { sig := <-sigs switch sig { case syscall.SIGINT: fmt.Println("Received SIGINT signal, preparing to exit.") // Perform cleanup operations return case syscall.SIGTERM: fmt.Println("Received SIGTERM signal, preparing to exit gracefully.") // Perform cleanup operations return case syscall.SIGHUP: fmt.Println("Received SIGHUP signal, reloading configuration.") // Reload configuration logic default: fmt.Printf("Received unhandled signal: %v", sig) } } }
Using signal.Stop
to Stop Listening for Signals
In some cases, you may want to stop listening for specific signals while the program is running. You can achieve this with the signal.Stop
function.
Example Code
package main import ( "fmt" "os" "os/signal" "syscall" "time" ) func main() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) fmt.Println("The program is running. Press Ctrl+C to send SIGINT to exit.") // Start a goroutine to handle signals go func() { sig := <-sigs fmt.Printf("Received signal: %v", sig) }() // Simulate program running for some time before stopping signal listening time.Sleep(10 * time.Second) signal.Stop(sigs) fmt.Println("Stopped listening for signals.") // Continue running other parts of the program select {} }
In the above example, the program stops listening for signals after running for 10 seconds and continues to execute other logic.
Notes
- Buffered channel: It is generally recommended to set a buffer for the signal channel to prevent signals from being lost before they are processed. For example:
make(chan os.Signal, 1)
. - Default behavior: If you don’t call
signal.Notify
, the Go program will handle signals according to the operating system’s default behavior. For example,SIGINT
usually causes the program to terminate. - Multiple signals: Some signals may be sent multiple times, especially in long-running programs. Ensure that your signal handling logic can correctly deal with receiving the same signal more than once.
- Resource cleanup: After receiving a termination signal, make sure to perform necessary resource cleanup operations, such as closing files, releasing locks, and disconnecting network connections, to avoid resource leaks.
- Blocking issue:
signal.Notify
sends signals to the specified channel but does not block the send operation. Therefore, make sure there is a goroutine listening on that channel; otherwise, signals may be lost. - Uncatchable signals: Some signals (such as
SIGKILL
) cannot be caught or ignored. When a program receives such signals, it will terminate immediately.
Practical Applications
In real-world development, listening for system signals is often used in the following scenarios:
- Gracefully shutting down servers: After receiving a termination signal, the server can stop accepting new connections and wait for existing connections to finish before exiting.
- Hot reloading configuration: After receiving a specific signal (such as
SIGHUP
), the program can reload the configuration file without restarting. - Resource release: Ensure that resources are properly released before the program exits, such as closing database connections or releasing locks.
Example: Gracefully Shutting Down an HTTP Server
package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" ) func main() { // Create an HTTP server http.HandleFunc("/", handler) server := &http.Server{Addr: ":8080"} // Start the server go func() { if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("ListenAndServe error: %v", err) } }() // Create a signal channel sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) // Wait for a signal sig := <-sigs fmt.Printf("Received signal: %v, shutting down server...", sig) // Create a context with timeout for shutting down the server ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Gracefully shut down the server if err := server.Shutdown(ctx); err != nil { log.Fatalf("Server shutdown error: %v", err) } fmt.Println("Server has been successfully shut down.") } func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Response completed") fmt.Println("Printed 'response completed' in the background") }
In the above example, the HTTP server waits up to 5 seconds to finish processing current requests after receiving SIGINT
or SIGTERM
, and then shuts down gracefully.
Summary
chan os.Signal
is an important mechanism in Go for handling operating system signals. By combining signal.Notify
with a signal channel, programs can listen for and respond to various system signals, thereby enabling graceful resource management and program control. Understanding and correctly using chan os.Signal
is especially important for writing robust and reliable concurrent programs.
We are Leapcell, your top choice for hosting Go projects.
Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:
Multi-Language Support
- Develop with Node.js, Python, Go, or Rust.
Deploy unlimited projects for free
- pay only for usage — no requests, no charges.
Unbeatable Cost Efficiency
- Pay-as-you-go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real-time metrics and logging for actionable insights.
Effortless Scalability and High Performance
- Auto-scaling to handle high concurrency with ease.
- Zero operational overhead — just focus on building.
Explore more in the Documentation!
Follow us on X: @LeapcellHQ