# How to Handle Connection Timeouts and Retry Logic in iroh Applications

> Learn how to handle connection timeouts and retry logic in iroh applications. Discover automated management with QUIC transport controls, heartbeats, and exponential back-off. Configure via ClientBuilder.

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

---

**iroh applications automatically manage connection timeouts and retries through layered QUIC transport controls, heartbeat intervals, and exponential back-off for NAT traversal, configurable via the `ClientBuilder` API.**

iroh is a peer-to-peer networking library that uses QUIC for reliable connections across unreliable networks. Understanding how to handle connection timeouts and retry logic is essential for building resilient iroh applications that gracefully manage flaky networks, NAT traversal, and DNS resolution failures.

## Transport-Level Timeout Architecture

### Relay Actor Timeouts

In [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs), the relay actor guards every outgoing operation with `tokio::time::timeout`. The `CONNECT_TIMEOUT` constant (approximately 10 seconds) protects the initial QUIC handshake, while `PING_INTERVAL` regulates keep-alive traffic. An `RELAY_INACTIVE_CLEANUP_TIME` automatically tears down idle connections to prevent resource leaks.

### Heartbeat and Connection Lifecycle

The socket implementation in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) defines a `HEARTBEAT_INTERVAL` of 5 seconds. The library implements a 15-second overall timeout strategy that allows three heartbeats and multiple retry attempts before marking a connection as failed. This heartbeat loop ensures that stalled connections are detected quickly while avoiding premature termination during temporary congestion.

### QUIC Address Validation

According to [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) and [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs), iroh uses QUIC's built-in RETRY packet mechanism for address validation. When the server receives a packet from an unvalidated address, it issues a retry token via `Incoming::retry()`. The client must resend the packet with this token, creating an implicit retry cycle that validates the source address before establishing the connection.

## Retry Mechanisms for NAT Traversal and DNS

### Hole-Punching Retries

When direct connectivity requires NAT traversal, the remote state machine in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs) schedules hole-punch attempts with calculated back-off. The code calculates `next_hp - now` to determine the retry delay, logging each attempt with `trace!` and aborting only on fatal errors. This allows the connection to survive multiple NAT traversal failures before ultimately timing out.

### DNS Lookup Resilience

The DNS resolver in [`iroh/src/address_lookup/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup/pkarr.rs) implements per-attempt timeouts with progressive back-off. On resolution failure, the library schedules a new lookup after a delay proportional to the number of failed attempts using `retry_after = Duration::from_secs(failed_attempts)`. This exponential back-off prevents overwhelming the DNS resolver while ensuring eventual connectivity when the network recovers.

## Configuring Application-Level Timeouts

While iroh manages internal retries automatically, applications can customize timeout behavior through the builder API exposed in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs).

```rust
use iroh::client::ClientBuilder;
use std::time::Duration;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Configure custom timeout values
    let client = ClientBuilder::default()
        .connect_timeout(Duration::from_secs(8))   // QUIC handshake timeout
        .ping_interval(Duration::from_secs(4))     // Keep-alive interval
        .build()
        .await?;
    
    Ok(())
}

```

The `connect_timeout` and `ping_interval` methods map directly to the internal constants used in the relay actor, allowing you to tune the library's behavior to your specific network environment.

## Handling Timeouts in Application Code

For additional control, wrap connection attempts with `tokio::time::timeout` to apply application-specific deadlines beyond iroh's internal mechanisms.

```rust
use tokio::time::{timeout, Duration};

// Apply an explicit 10-second deadline
let connect_fut = client.connect("iroh://example.org");
match timeout(Duration::from_secs(10), connect_fut).await {
    Ok(Ok(conn)) => {
        println!("Connected successfully");
    }
    Ok(Err(e)) => {
        eprintln!("Connection failed: {e}");
        // Implement custom retry logic here
    }
    Err(_) => {
        eprintln!("Connection timed out");
        // Handle timeout-specific recovery
    }
}

```

This pattern mirrors the internal timeout guards found in [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs), giving you consistent error handling across your application stack.

## Summary

- **Transport timeouts**: The relay actor uses `CONNECT_TIMEOUT` (~10s) and `PING_INTERVAL` to guard handshakes and keep-alives, defined in [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs).
- **Heartbeat detection**: A 5-second `HEARTBEAT_INTERVAL` with a 15-second overall timeout provides multiple retry opportunities before failure, implemented in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs).
- **NAT traversal**: Hole-punching retries use calculated back-off in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs), automatically attempting direct paths until success or fatal error.
- **Address validation**: QUIC RETRY packets validate client addresses via `Incoming::retry()` in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) before accepting connections.
- **DNS resilience**: The resolver in [`iroh/src/address_lookup/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup/pkarr.rs) applies progressive back-off proportional to failure count.
- **Configuration**: Use `ClientBuilder` methods like `connect_timeout` and `ping_interval` to customize default values.

## Frequently Asked Questions

### What is the default connection timeout in iroh?

The default `CONNECT_TIMEOUT` is approximately 10 seconds, guarding the initial QUIC handshake in the relay actor. Additional keep-alive traffic is governed by a 5-second `HEARTBEAT_INTERVAL`, with connections considered failed after roughly 15 seconds of inactivity.

### How does iroh handle NAT traversal failures?

iroh automatically retries hole-punching attempts using calculated back-off delays in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs). The library schedules new attempts after `next_hp - now` intervals, logging each retry and only aborting when encountering fatal errors or the overall timeout expires.

### Can I customize retry behavior for specific connections?

Yes. While iroh handles internal retries automatically, you can customize timeout values using `ClientBuilder` methods like `connect_timeout` and `ping_interval`. For application-specific control, wrap connection futures with `tokio::time::timeout` to apply additional deadlines or implement custom retry loops based on specific error types.

### Where does iroh implement DNS retry logic?

The DNS retry logic resides in [`iroh/src/address_lookup/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup/pkarr.rs), where the resolver calculates `retry_after = Duration::from_secs(failed_attempts)` to implement progressive back-off. This ensures that DNS lookups retry automatically with increasing delays after transient resolution failures.