# Graceful shutdown with Tokio and Axum

> Handle Ctrl+C and SIGTERM, trigger Axum graceful shutdown, and understand what still needs explicit cancellation in background tasks.

HTML: https://rustprint.com/recipes/graceful-shutdown-tokio/

A production process should not treat termination as "the operating system will kill it eventually." The useful pattern is: detect shutdown, notify the server, stop accepting new work, let in-flight work finish where possible, and explicitly stop application-owned background tasks.

Tokio describes graceful shutdown as three separate problems: deciding when to shut down, telling parts of the program to stop, and waiting for them to finish.

## Handle the signals your process actually receives

For local development, Ctrl+C is the obvious signal. On Unix deployments and in containers, SIGTERM is also a normal termination path.

**Ctrl+C plus Unix SIGTERM shutdown signal**

```rust
async fn shutdown_signal() {
    let ctrl_c = async {
        if let Err(error) = signal::ctrl_c().await {
            tracing::error!(%error, "failed to install Ctrl+C handler");
            std::future::pending::<()>().await;
        }
    };

    #[cfg(unix)]
    let terminate = async {
        match signal::unix::signal(signal::unix::SignalKind::terminate()) {
            Ok(mut stream) => {
                stream.recv().await;
            }
            Err(error) => {
                tracing::error!(%error, "failed to install SIGTERM handler");
                std::future::pending::<()>().await;
            }
        }
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {}
        _ = terminate => {}
    }

    tracing::info!("shutdown signal received");
}
```

The Unix branch is conditionally compiled. On non-Unix targets the second future never completes, so Ctrl+C remains the portable shutdown path.

If installing a signal handler fails, the example logs the failure and keeps the failed branch pending instead of pretending that a shutdown signal was received.

## Attach the signal to the HTTP server

Axum's `Serve::with_graceful_shutdown` takes a future. When that future completes, the server begins its graceful shutdown path.

The [Axum REST API blueprint](/blueprints/axum-rest-api/index.md) wires the checked signal future directly into the server startup.

Keep this distinction clear:

- the signal future decides **when shutdown starts**
- Axum handles the HTTP server shutdown
- your application still owns any tasks it spawned independently

## Background tasks need their own cancellation path

A task started with `tokio::spawn` does not become magically owned by Axum because the HTTP server is shutting down.

Tokio's shutdown guidance recommends cancellation tokens for notifying multiple tasks and a task tracker when the process must wait for them to finish. Those utilities live in `tokio-util`.

A service with workers might therefore use this order:

1. receive Ctrl+C or SIGTERM
2. stop accepting new HTTP work
3. cancel background workers
4. close producers that can create more work
5. wait for tracked tasks with an explicit deadline policy
6. let the process exit

The exact order depends on your dependencies. A queue consumer, a database writer, and an HTTP-only service do not have the same shutdown semantics.

## Do not confuse graceful with infinite

Graceful shutdown needs a failure policy. If one request or worker can block forever, "wait for everything" can turn into "never terminate."

Decide which operations have deadlines before shutdown happens. For orchestrated deployments, also make sure the application's maximum shutdown time fits inside the platform's termination grace period.

## Signals are process infrastructure

Tokio notes that signal handling has platform-specific caveats. Registering handlers changes process-level behavior, so keep signal installation in your binary startup code rather than hiding it inside request handlers or reusable library functions.

For a small service, one shutdown future is enough. When the application gains independent workers, promote cancellation and task tracking to explicit application infrastructure.

## Sources

- [Tokio graceful shutdown](https://tokio.rs/tokio/topics/shutdown)
- [Tokio signal module](https://docs.rs/tokio/1.53.1/tokio/signal/index.html)
- [Axum with_graceful_shutdown](https://docs.rs/axum/0.8.9/axum/serve/struct.Serve.html)

## Related RustPrint guides

- [Production-oriented REST API with Axum](/blueprints/axum-rest-api/index.md): A checked Axum 0.8 service skeleton with typed state, JSON handlers, HTTP error responses, in-process tests, structured tracing, and graceful shutdown.
- [Structured tracing for a Rust service](/recipes/structured-tracing/index.md): Initialize tracing-subscriber with runtime filtering and newline-delimited JSON output, then keep logs, spans, and exported telemetry conceptually separate.
- [How RustPrint verifies code and technical claims](/methodology/index.md): What SOURCE-BACKED and CI-CHECKED mean, what RustPrint actually validates, and which claims the verification system does not make.
