# Iroh Connection Timeouts and Retry Logic: How QUIC Path Management Works in n0-computer/iroh

> Understand Iroh connection timeouts and retry logic. Discover how QUIC path management in n0-computer/iroh ensures reliable connections with layered timeouts and jittered exponential backoff.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: deep-dive
- Published: 2026-07-11

---

**Iroh balances low-latency direct paths with reliable relay paths using a layered timeout strategy that includes 5-second heartbeats, 15-second idle timeouts for direct paths, 30-second timeouts for relay paths, and jittered exponential backoff for retries.**

The n0-computer/iroh repository implements a resilient QUIC-based networking layer that automatically handles connection timeouts and retry logic across diverse network conditions. Understanding Iroh connection timeouts and retry logic is essential for debugging path failures and optimizing peer-to-peer connectivity. The implementation spans multiple core modules including [`socket.rs`](https://github.com/n0-computer/iroh/blob/main/socket.rs), [`endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/endpoint/quic.rs), and [`protocol.rs`](https://github.com/n0-computer/iroh/blob/main/protocol.rs), using specific timing constants and back-off algorithms to maintain stable connections.

## Heartbeat and Idle Detection Mechanisms

The core of Iroh’s timeout behavior lives in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs), where the system monitors path health through periodic heartbeats and idle detection timers.

### Direct Path Timeouts

For direct UDP connections, Iroh sends a lightweight heartbeat every `HEARTBEAT_INTERVAL` (5 seconds) to keep the path alive. If no packet—including heartbeats—is observed for `PATH_MAX_IDLE_TIMEOUT` (15 seconds), the direct path is considered stale and closed. This 3:1 ratio between idle timeout and heartbeat interval gives the retry logic multiple chances to detect connectivity before tearing down the connection.

### Relay Path Resilience

Relay paths use a longer `RELAY_PATH_MAX_IDLE_TIMEOUT` of 30 seconds because the underlying WebSocket connection may need several seconds to reconnect during temporary network outages. This extended timeout ensures that relay actors survive brief disruptions while attempting to re-establish connectivity.

## QUIC-Level Address Validation and Retry Tokens

Security is enforced through retry tokens configured in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs). The `set_retry_token_lifetime` method controls how long a client has to prove address ownership after receiving a **RETRY** packet.

When a server cannot yet trust a client’s IP address, it generates a RETRY packet containing a short-lived token. The client must resend its Initial packet with this token, proving it can receive packets at the claimed IP. This logic is implemented in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) around lines 166-195, where the server validates source addresses before establishing full connections.

## Retry and Back-Off Strategies

Iroh implements sophisticated retry logic with jitter to prevent thundering-herd effects when recovering from network disruptions.

### Address Lookup Retries

The `DirectAddrUpdateState::run` function in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) (lines 1510-1520) manages net-report probes with per-attempt timeouts (`NET_REPORT_TIMEOUT`). When a probe fails, the system calculates the retry delay using the number of failed attempts:

```rust
let retry_after = Duration::from_secs(failed_attempts);
republish.as_mut().reset(Instant::now() + retry_after);

```

This creates a linear back-off where each failed attempt increases the wait time by one second.

### Relay Reconnection Logic

When a relay connection drops, the actor schedules a reconnection attempt with jittered back-off. According to changelog entries #3447 and #3254, the implementation adds randomized delay to DNS retry calls and backs off progressively before attempting relay reconnections. This back-off is short enough to recover from typical Wi-Fi or cellular handoffs (2–10 seconds) while allowing the network time to stabilize.

## Implementation Constants and Configuration

The following constants define the core timing behavior in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs):

```rust
pub(crate) const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
pub(crate) const PATH_MAX_IDLE_TIMEOUT: Duration = Duration::from_secs(15);
pub(crate) const RELAY_PATH_MAX_IDLE_TIMEOUT: Duration = Duration::from_secs(30);

```

The retry token lifetime is configured through the QUIC endpoint builder in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs):

```rust
pub fn set_retry_token_lifetime(mut self, value: Duration) -> Self {
    self.inner.retry_token_lifetime(value);
    self
}

```

## Summary

- **Heartbeat interval**: 5 seconds (`HEARTBEAT_INTERVAL`) keeps paths alive through periodic lightweight packets.
- **Direct path idle timeout**: 15 seconds (`PATH_MAX_IDLE_TIMEOUT`) closes stale UDP connections when no traffic is observed.
- **Relay path idle timeout**: 30 seconds (`RELAY_PATH_MAX_IDLE_TIMEOUT`) accommodates WebSocket reconnection delays.
- **Retry token validation**: Configurable via `set_retry_token_lifetime` in [`endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/endpoint/quic.rs) to prevent spoofed connections.
- **Address lookup back-off**: Linear delay calculated as `Duration::from_secs(failed_attempts)` in `DirectAddrUpdateState`.
- **Relay reconnection**: Implements jittered exponential back-off to handle temporary network outages gracefully.

## Frequently Asked Questions

### What is the default heartbeat interval in Iroh?

The default heartbeat interval is **5 seconds**, defined by the `HEARTBEAT_INTERVAL` constant in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs). This ensures that active connections send regular keep-alive traffic to prevent NAT timeouts and detect path failures early.

### How does Iroh differentiate timeout handling between direct and relay paths?

Direct paths use a **15-second** idle timeout (`PATH_MAX_IDLE_TIMEOUT`) while relay paths tolerate **30 seconds** of silence (`RELAY_PATH_MAX_IDLE_TIMEOUT`). This distinction accounts for the additional latency and reconnection time required when WebSocket relay connections experience temporary outages.

### What triggers a QUIC retry token validation in Iroh?

A **RETRY** packet is sent by the server when it cannot validate the client’s source address, typically during initial connection establishment. The client must resend its Initial packet containing the token generated by the server, as implemented in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) around lines 166-195 and configured via `retry_token_lifetime` in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs).

### How is the retry delay calculated for address lookups?

The retry delay grows linearly with the number of failed attempts. Specifically, the `DirectAddrUpdateState::run` method in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) calculates `Duration::from_secs(failed_attempts)` to determine the seconds to wait before the next net-report probe attempt, ensuring progressive back-off without exponential explosion.