# How to Gracefully Shut Down an iroh Endpoint and Router

> Learn how to gracefully shut down your iroh Endpoint and Router by closing the QUIC connection and releasing resources. This guide ensures a clean shutdown for reliable applications.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-06-21

---

**Call `endpoint.close().await` to initiate QUIC shutdown and notify the internal router, then optionally await `endpoint.closed().await` to block until the underlying QUIC stack finishes, and finally drop the `Endpoint` to release UDP sockets.**

The iroh networking stack (n0-computer/iroh) centers on the **Endpoint**, a high-level object that manages a QUIC endpoint, UDP sockets, and background actors including the router and relay managers. When you are done with an endpoint, you must shut it down cleanly to prevent data loss and ensure all connections close properly. This guide explains how to gracefully shut down an iroh Endpoint and its associated Router using the public API and internal mechanics found in the source code.

## Understanding the iroh Endpoint Architecture

### The Endpoint and Its Internal Router

The `Endpoint` in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) is not merely a wrapper around a QUIC connection. According to the iroh source code, it owns a QUIC endpoint, UDP sockets, and a set of background actors. The **router**, created in `socket::EndpointInner::bind` (see [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) line 84), is a background task that multiplexes protocol frames. When you initiate a shutdown, the router must stop processing new packets and exit cleanly.

### Connection Lifecycle and Resource Management

When you call `Endpoint::close` (line 61 of [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)), the implementation in `EndpointInner::close` sends a QUIC `ConnectionClose` with error-code 0, drains pending streams, and waits for the QUIC stack to finish retransmissions. The router receives a shutdown command via an internal channel when `EndpointInner::close` runs, ensuring no packet is lost during shutdown.

## Graceful Shutdown Steps

### Step 1: Initiate Shutdown with `close()`

Begin by calling `endpoint.close().await`. This method, defined in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs), triggers `EndpointInner::close`, which sends a QUIC close frame and notifies the router via a `Shutdown` message on the router's control channel (`socket::router::ControlMessage::Shutdown`). This starts the graceful-close handshake.

### Step 2: Wait for Completion with `closed()`

To block until the QUIC stack reports that the close is complete, await `endpoint.closed()`. This returns an `EndpointClosed` future (line 170 of [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)) that resolves only after the underlying QUIC endpoint signals cancellation. This ensures all connections are fully terminated before proceeding.

### Step 3: Drop the Endpoint to Clean Up Sockets

Once the close is complete, drop the `Endpoint` or let it go out of scope. The UDP sockets stay alive while any clone of `Endpoint` exists, guarded by `EndpointInner::is_closed` (line 236 of [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs)). When the last clone is dropped, the sockets are finally closed, and watchers (such as address-watchers created from `EndpointInner`) are automatically detached (line 124 of [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)).

## Code Examples

### Basic Graceful Shutdown Pattern

The following example demonstrates the standard shutdown sequence using the public API:

```rust
use iroh::{Endpoint, endpoint::presets};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Build and bind a minimal endpoint.
    let ep = Endpoint::builder(presets::Minimal)
        .alpns(vec![b"demo-alpn".to_vec()])
        .bind()
        .await?;

    // … use the endpoint for connections …

    // Start a graceful shutdown.
    ep.close().await;               // send QUIC close and shutdown router
    ep.closed().await;              // wait for the QUIC stack to finish

    // All clones are dropped here, UDP sockets are finally closed.
    Ok(())
}

```

### Coordinating Background Tasks with Shutdown

If you spawn tasks that should stop when the endpoint shuts down, use the `EndpointClosed` future with `run_until` (line 224 of [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)):

```rust
use iroh::{Endpoint, endpoint::presets};
use n0_future::FutureExt; // for `.run_until`

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ep = Endpoint::builder(presets::Minimal).bind().await?;
    let endpoint_closed = ep.closed();

    // Spawn a background task that does work until the endpoint closes.
    tokio::spawn(endpoint_closed.run_until(async {
        loop {
            // ... periodic work ...
            tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        }
    }));

    // When you are done with the endpoint:
    ep.close().await;   // triggers the above task to stop
    Ok(())
}

```

### Advanced: Explicitly Waiting for the Router

For library authors who need to ensure the router task has exited, you can use the internal control channel directly (advanced usage):

```rust
use iroh::socket::router::ControlMessage;
use futures::channel::mpsc::UnboundedSender;

fn shutdown_router(ctrl_tx: UnboundedSender<ControlMessage>) {
    // Send a shutdown request; the router will exit after processing any
    // pending packets.
    let _ = ctrl_tx.unbounded_send(ControlMessage::Shutdown);
}

```

## Key Source Files and Implementation Details

The graceful shutdown behavior is implemented across several files in the n0-computer/iroh repository:

- **[`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)**: Contains the public API for the `Endpoint`, including `close()` at line 61, `closed()` at line 170, and the `EndpointClosed` future with `run_until()` at line 224.
- **[`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs)**: Sets up the low-level QUIC endpoint and creates the router task at line 84. Also contains `EndpointInner::is_closed` at line 236 to guard UDP socket closure.
- **[`iroh/src/socket/mod.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/mod.rs)**: Re-exported as `socket`, holds the `EndpointInner` implementation including the close logic and socket management.
- **[`iroh/src/socket/router.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/router.rs)**: Implements the router that demultiplexes protocol frames; reacts to `ControlMessage::Shutdown`.

## Summary

- Call **`endpoint.close().await`** to initiate QUIC shutdown and notify the router via an internal channel.
- Await **`endpoint.closed().await`** to block until the QUIC stack finishes all retransmissions and connection cleanup.
- Drop the **`Endpoint`** (or let it go out of scope) to close UDP sockets once the last clone is dropped.
- Watchers and background tasks are automatically cleaned up when the last `Endpoint` clone is dropped, with no extra API calls required.
- The router lifecycle is managed internally; it receives the shutdown command automatically when you call `close()`.

## Frequently Asked Questions

### What happens if I don't call `close()` before dropping the Endpoint?

If you drop the `Endpoint` without calling `close()`, the QUIC endpoint will still close, but you may not wait for pending data to be transmitted or for connections to close gracefully. The UDP sockets will remain open until the last `Endpoint` clone is dropped due to `EndpointInner::is_closed` guards in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs), but active connections might receive an abrupt termination rather than a graceful `ConnectionClose` frame.

### How do I know when all connections are fully terminated?

Await the **`EndpointClosed`** future returned by `endpoint.closed()` (line 170 of [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)). This future resolves only after the underlying QUIC endpoint signals that it has finished retransmissions and all connections are closed. This ensures no data is lost during shutdown.

### Can I force an immediate shutdown without waiting for connections?

While the public API emphasizes graceful shutdown via `close()`, dropping the `Endpoint` immediately will eventually close the sockets. However, this is not recommended for production code as it bypasses the graceful QUIC handshake. The source code in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) shows that `close()` sends a proper `ConnectionClose` frame with error-code 0, allowing peers to detect a clean shutdown rather than a timeout.

### Is the router shutdown automatic or do I need to stop it manually?

The router shutdown is **automatic**. When you call `endpoint.close().await`, the implementation in `EndpointInner::close` sends a `ControlMessage::Shutdown` to the router's control channel. The router task, created in `socket::EndpointInner::bind` (line 84 of [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs)), processes this message and exits after handling any remaining inbound packets. No manual intervention is required.