# How iroh Handles Direct Connection Failures: Automatic Relay Fallback Explained

> Discover how iroh handles direct connection failures with automatic relay fallback. Learn how encrypted connections are maintained through intermediary relays after a timeout without manual setup.

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

---

**When direct peer-to-peer connections fail in iroh, the Endpoint automatically falls back to relay servers after a 10-second timeout, maintaining encrypted end-to-end connections through intermediary relays without requiring manual configuration.**

The n0-computer/iroh library implements robust NAT traversal by automatically switching to relay infrastructure when direct paths are unavailable. This mechanism ensures that applications remain connected even when peers are behind symmetric NATs, firewalled networks, or when address information has become stale.

## How Direct Connections Fail in iroh

When an iroh `Endpoint` initiates a connection to a remote peer, it first attempts to establish a direct path using known IP addresses. These direct attempts can fail when:

- **Symmetric NATs** prevent direct hole punching
- **Firewall rules** block incoming UDP packets
- **Stale address information** points to outdated IP addresses
- **Network partitions** temporarily isolate the peer

The failure detection happens transparently inside the connection logic, with the implementation attempting all available direct addresses before considering fallback options.

## The Automatic Relay Fallback Mechanism

The fallback process is handled entirely within the `Endpoint` implementation, following a specific resolution and timeout sequence.

### Address Resolution and the 10-Second Timeout

Connection attempts begin in `Endpoint::connect_with_opts`, which calls `self.inner.resolve_remote(endpoint_addr).await` to determine available paths. According to the source code in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) (lines 1127-1135), the resolver examines the `EndpointAddr` and implements the following logic:

- If the address contains direct IPs, they are tried first
- If the direct IP list is empty or all attempts time out, the resolver queries configured Address Lookup services (DNS, PKARR) for additional addresses

The direct connection attempt is subject to a **10-second timeout**. As noted in the source comments within [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs): *"Start connecting via noq. This will time out after 10 seconds if no reachable address is available."* When this timeout expires without establishing a reachable direct address, the fallback logic immediately proceeds to the next available address type.

### MappedAddr and Path Selection

The resolver returns a `MappedAddr` structure, defined in [`iroh/src/socket/remote_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map.rs), which contains the chosen private socket address via `private_socket_addr()`. This abstraction is critical because:

- A `MappedAddr` can represent either a direct IP address or a relay URL
- The consuming code does not need to differentiate between direct and relayed paths
- The selection happens automatically based on availability

### NOQ Integration and QUIC Streaming

Once the `MappedAddr` is determined, the endpoint builds a QUIC client configuration and passes the destination address to NOQ (the internal QUIC implementation). If the `MappedAddr` resolves to a relay address, NOQ automatically opens a QUIC stream to the relay server rather than attempting a direct peer-to-peer socket connection.

## Configuring Relay Maps and Modes

The set of available relays is controlled by a `RelayMap`, which determines which relay infrastructure the endpoint may use when direct connections fail.

### Default Public Relays

By default, the endpoint builder configures the "number 0" public relays using `RelayMode::Default`. This provides immediate connectivity without configuration:

```rust
let ep = iroh::Endpoint::builder(iroh::endpoint::presets::N0)
    .relay_mode(iroh::endpoint::RelayMode::Default)
    .bind()
    .await?;

```

### Custom Relay Deployments

For private deployments or testing scenarios, you can replace the default map with custom relay URLs via `Builder::relay_mode` (implemented in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) at lines 558-571):

```rust
let custom_map = {
    let url: iroh::RelayUrl = "https://my-relay.example".parse()?;
    iroh::RelayConfig::new(url, iroh::QuicTransportConfig::default()).into()
};

let ep = iroh::Endpoint::builder(presets::N0)
    .relay_mode(iroh::RelayMode::Custom(custom_map))
    .bind()
    .await?;

```

When the remote's direct IPs are unreachable, the connection automatically uses the relay defined in your custom map.

## Detecting Relayed Connections

After establishing a connection, you can inspect whether the traffic is being relayed. The `Connection` type exposes an `is_relay()` method that returns `true` when packets are flowing through a relay server:

```rust
let conn = ep.connect(remote_addr, b"my-alpn").await?;
println!("Is relayed? {}", conn.is_relay());

```

For testing purposes, the `iroh::tests::util::is_relayed` helper provides additional verification capabilities. The test suite in [`iroh/tests/patchbay.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay.rs) (around line 188) demonstrates how connections can start relayed and potentially recover to direct mode, or remain relayed depending on network conditions.

## Summary

- **Automatic fallback**: When direct IP addresses are unreachable, iroh automatically switches to relay paths after a 10-second timeout
- **Transparent abstraction**: The `MappedAddr` type handles both direct and relay addresses uniformly, requiring no code changes to support fallback
- **End-to-end encryption**: Relayed connections remain encrypted; relay servers only forward packets without accessing content
- **Configurable infrastructure**: Use `RelayMode::Default` for public relays or `RelayMode::Custom` for private relay deployments
- **Runtime detection**: Query `conn.is_relay()` to determine if an active connection is using relay infrastructure

## Frequently Asked Questions

### How long does iroh wait before falling back to a relay?

Iroh implements a **10-second timeout** for direct connection attempts. This duration is hardcoded in the connection logic within [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs). If no reachable direct address is available within this window, the endpoint immediately attempts the next available address, which will be a relay URL from the active `RelayMap`.

### Is traffic still encrypted when using iroh relay servers?

Yes. When an iroh connection uses relay fallback, the traffic remains **end-to-end encrypted** between the peers. The relay server acts as a packet forwarder and cannot decrypt the content. The encryption is handled by the QUIC layer within NOQ, ensuring that relay operators cannot inspect connection data.

### Can I use custom relay servers instead of the default public ones?

Yes. You can configure custom relays using the `Builder::relay_mode` method with `RelayMode::Custom`. Pass a `RelayMap` containing your custom relay URLs to override the default public relay infrastructure. This is essential for private deployments or when compliance requirements prohibit using public relay services.

### How can I check if an active connection is relayed?

Use the `is_relay()` method on the `Connection` object. This returns a boolean indicating whether the connection is currently traversing a relay server. For example: `if conn.is_relay() { println!("Using relay"); }`. Additionally, the test utilities in `iroh::tests::util` provide helper functions for verifying relayed status in test scenarios.