# Why Iroh Examples Are Not Working: Common Fixes and Correct Usage

> Troubleshoot Iroh examples not working with common fixes for unreachable relays, Cargo, and QUIC streams. Ensure correct setup for successful execution.

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

---

**Iroh examples fail most often due to unreachable relay servers, incorrect Cargo invocation, or missing `finish()` calls on QUIC streams, yet run correctly when the listener starts before the client and network dependencies are configured.**

The `iroh` crate from the n0-computer/iroh repository provides a peer-to-peer QUIC stack with built-in hole-punching and relay fallback. If your Iroh examples are not working, the issue typically stems from runtime dependencies or invocation order rather than broken source code. This guide explains the correct way to run the examples and how to resolve the most common failure modes.

## Understanding the Iroh Architecture

Before troubleshooting, you need to understand the core components that the examples exercise. The **`Endpoint`** struct in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs) represents a local node capable of both dialing remote peers and accepting inbound connections. When you run an example, it typically constructs an `Endpoint` via `Endpoint::bind(presets::N0).await`, which connects to a home relay and begins listening.

The **`Router`** struct in [`iroh/src/protocol/mod.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol/mod.rs) dispatches incoming connections to protocol handlers based on ALPN strings. The echo example demonstrates this by registering a handler that responds to the `b"iroh-example/echo/0"` ALPN identifier. Connections are authenticated automatically using the endpoint's permanent `SecretKey`, with the corresponding `PublicKey` (also called `EndpointId`) used for routing and TLS verification.

## Common Failure Modes and Fixes

Most example failures fall into five categories. Use this reference to diagnose your specific error.

### Relay Resolution Failures

If you encounter a panic stating "failed to resolve relay address", your network cannot reach the default relay at `relay0.n0.computer` on port 443. This prevents the endpoint from establishing its home relay connection, which is required for NAT traversal.

Fix this by setting the `IROH_RELAY` environment variable to a custom relay URL, or configure it programmatically:

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

let endpoint = Endpoint::builder(presets::N0)
    .relay_url("https://my-relay.example".parse()?)
    .bind()
    .await?;

```

### Process Exits Immediately

When running `cargo run --example echo`, the process may build successfully but exit before any data transfers. This happens because the example spawns a router but the main task ends before the asynchronous runtime completes the connection handshake.

Ensure you await the connection lifecycle. In the echo example, the listener must call `router.endpoint().online().await` before the client attempts to connect, and the client should await `conn.closed().await` or keep the runtime alive until the transfer completes.

### Missing Feature Flags

Compilation errors referencing `unstable-net-report` or other modules indicate the crate was built without required optional features. The examples may depend on unstable or experimental APIs that are not enabled by default.

Add the necessary features to your [`Cargo.toml`](https://github.com/n0-computer/iroh/blob/main/Cargo.toml):

```toml
[dependencies]
iroh = { version = "0.x", features = ["unstable-net-report"] }

```

### TLS Handshake Failures

A TLS handshake fails when the peer's `PublicKey` is not recognized or the ALPN strings differ between client and server. In [`iroh/src/tls/mod.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/mod.rs), the library validates that the remote endpoint's certificate matches the expected `EndpointId`.

Verify that both sides use the exact same ALPN byte string:

```rust
const ALPN: &[u8] = b"iroh-example/echo/0"; // Must match exactly on both sides

```

### Hanging Stream Reads

If your stream read blocks forever, the sender likely never called `finish()`. In QUIC, the receiver waits for the stream to be closed before returning from `read_to_end()`.

Always signal the end of data:

```rust
send.write_all(b"payload").await?;
send.finish()?; // Critical: without this, recv.read_to_end() hangs
let response = recv.read_to_end(1000).await?;

```

## How to Run the Examples Correctly

The bundled examples in `iroh/examples/` demonstrate proper usage patterns. Follow these steps to ensure they execute successfully.

### Running the Echo Server and Client

The echo example in [`iroh/examples/echo.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/echo.rs) shows a complete round-trip. It requires running both a listener and a client in sequence.

**Server implementation** (registers the protocol handler):

```rust
use iroh::{
    Endpoint,
    protocol::{ProtocolHandler, Router, AcceptError},
    endpoint::{Connection, presets},
};
use n0_error::{Result, StdResultExt};

const ALPN: &[u8] = b"iroh-example/echo/0";

#[derive(Debug, Clone)]
struct Echo;

impl ProtocolHandler for Echo {
    async fn accept(&self, connection: Connection) -> Result<(), AcceptError> {
        let (mut send, mut recv) = connection.accept_bi().await?;
        tokio::io::copy(&mut recv, &mut send).await?;
        send.finish()?;
        connection.closed().await;
        Ok(())
    }
}

async fn start_accept_side() -> Result<Router> {
    let endpoint = Endpoint::bind(presets::N0).await?;
    let router = Router::builder(endpoint).accept(ALPN, Echo).spawn();
    Ok(router)
}

```

**Client implementation** (dials the listener):

```rust
async fn connect_side(addr: EndpointAddr) -> Result<()> {
    let endpoint = Endpoint::bind(presets::N0).await?;
    let conn = endpoint.connect(addr, ALPN).await?;
    let (mut send, mut recv) = conn.open_bi().await.anyerr()?;
    
    send.write_all(b"Hello, world!").await.anyerr()?;
    send.finish().anyerr()?;
    
    let response = recv.read_to_end(1000).await.anyerr()?;
    assert_eq!(&response, b"Hello, world!");
    
    conn.close(0u32.into(), b"bye!");
    endpoint.close().await;
    Ok(())
}

```

**Execution order**:

1. Start the listener with `router.endpoint().online().await` to ensure it is reachable
2. Retrieve the advertised address via `router.endpoint().addr()`
3. Pass this address to the client side
4. Call `router.shutdown().await` after the client completes

### Running the Blob Transfer Example

The transfer example in [`iroh/examples/transfer.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/transfer.rs) demonstrates blob storage using the `iroh-blobs` crate:

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

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let ep = Endpoint::bind(presets::N0).await?;
    let client = BlobClient::new(ep.clone());
    let data = b"The quick brown fox jumps over the lazy dog";
    let hash = client.put(data.as_ref()).await?;
    let fetched = client.get(&hash).await?.read_to_end(usize::MAX).await?;
    assert_eq!(fetched, data);
    Ok(())
}

```

This example requires the `iroh-blobs` dependency and a running endpoint, but does not require a separate listener since it operates on the local node.

## Key Source Files for Debugging

When tracing through failures, consult these implementation files:

- [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs) — Exports `Endpoint`, `RelayUrl`, and `EndpointId`
- [`iroh/src/endpoint/mod.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/mod.rs) — Contains `Endpoint::bind()` and connection handling
- [`iroh/src/protocol/mod.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol/mod.rs) — Implements `Router` and `ProtocolHandler` traits
- [`iroh-relay/src/server/http_server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server/http_server.rs) — Relay server implementation for fallback transport
- [`iroh-dns/src/dns.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/dns.rs) — DNS-based address lookup via Pkarr

## Summary

- **Iroh examples not working** usually indicates missing relay connectivity, incorrect feature flags, or improper async runtime handling rather than code bugs
- Always start the **listener** before the **client** and wait for `endpoint.online().await`
- Ensure **ALPN strings match exactly** between connecting peers to avoid TLS failures
- Call **`send.finish()`** on all streams to prevent receiver hangs
- Set `IROH_RELAY` or use `Endpoint::builder().relay_url()` if the default relay is unreachable

## Frequently Asked Questions

### Why does the echo example exit immediately without printing anything?

The example process exits because the main async task completes before the connection handshake finishes. The listener must await `router.endpoint().online().await` to ensure the node is reachable, and the client must keep the runtime alive until `conn.closed().await` resolves. Use `tokio::join!` to run both sides concurrently in a single process, or run the listener in a separate terminal first.

### How do I fix "failed to resolve relay address" panics?

This error occurs when the default relay at `relay0.n0.computer` is unreachable due to network restrictions or DNS blocking. Export the `IROH_RELAY` environment variable with an alternative URL, or configure the endpoint builder with `.relay_url("https://custom-relay.example")` before calling `.bind().await`.

### Why does my stream read hang forever even though data was sent?

The receiver blocks because QUIC streams require an explicit end-of-stream signal. The sender must call `send.finish().await` (or `send.finish()?` depending on your error handling) after writing the final bytes. Without this call, `recv.read_to_end()` waits indefinitely for more data.

### Do I need to run my own relay server to use the examples?

No, the examples work out-of-the-box with the default public relay infrastructure provided by n0.computer. However, if you are in a restricted network environment, you may need to deploy your own relay server using the code in [`iroh-relay/src/server/http_server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server/http_server.rs) and point your endpoint to it via the relay URL configuration.