# Best Practices for Using Iroh Modules in the n0-computer/iroh Library

> Master Iroh modules with n0-computer/iroh best practices. Configure Endpoint Builder with presets, define relay modes, and ensure robust address lookup for production QUIC connections. Enhance your P2P applications today.

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

---

**Start with `presets::N0` to configure the `Endpoint` `Builder`, explicitly define relay modes and crypto providers, and implement proper address lookup for production peer-to-peer QUIC connections.**

The n0-computer/iroh library offers a modular architecture for building peer-to-peer applications, centralizing QUIC connection management, relay handling, and address discovery behind the **`Endpoint`** type. Mastering the **`Builder`** pattern and its associated presets is critical for avoiding common misconfiguration pitfalls. These best practices for using Iroh modules will help you establish secure, performant connections while leveraging the library's full-featured transport and discovery capabilities.

## Start with Presets to Eliminate Configuration Errors

The **`Endpoint`** is the central type in Iroh, and you construct it using a **`Builder`** that accepts a preset configuration. Presets are reusable bundles of sane defaults that eliminate the most common source of misconfiguration.

**`presets::N0`** provides a fully functional endpoint with a crypto provider, DNS address lookup, and the default Number-0 relays. This is the recommended starting point for most internet-facing applications. For scenarios requiring tighter control, **`presets::Minimal`** offers a stripped-down foundation where you manually configure each component.

The preset implementation in [`iroh/src/endpoint/presets.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/presets.rs) handles conditional compilation logic for crypto providers. If you use `presets::Minimal` or build without a preset, you must explicitly set the crypto provider as shown in lines 58-78:

```rust
use std::sync::Arc;

builder = builder.crypto_provider(
    Arc::new(rustls::crypto::ring::default_provider())
);

```

## Configure Relay Modes and Crypto Providers Explicitly

After selecting a preset, configure the **Relay Mode** to determine which relay servers your endpoint contacts. The `RelayMode` enum is re-exported in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs) and supports three distinct modes:

- **`RelayMode::Default`**: Uses the default Number-0 relay infrastructure.
- **`RelayMode::Disabled`**: Essential for LAN-only tools or environments with guaranteed direct connectivity.
- **`RelayMode::Custom`**: Points to corporate or self-hosted relay servers using a `HashMap<RelayUrl, RelaySettings>`.

The conversion from `RelayMode` to transport occurs in `Builder::relay_mode` within [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) (lines 542-574). Always set this explicitly to document your network topology:

```rust
use iroh::endpoint::RelayMode;

let builder = Endpoint::builder(presets::N0)
    .relay_mode(RelayMode::Default);

```

## Manage Address Discovery and External Addresses

Iroh's modular architecture includes sophisticated address discovery via [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs). The default N0 DNS/PKARR service is sufficient for most scenarios, but production applications require careful management of external addressing.

Add static public IPs known ahead of time using `builder.external_addr(addr)`. For dynamic addresses discovered at runtime, call `endpoint.add_external_addr(addr).await` after binding. These addresses are advertised to peers and significantly improve NAT traversal success rates.

For private networks, implement a custom `DnsAddressLookup` pointing at an internal DNS server:

```rust
use iroh::address_lookup::{DnsAddressLookup, AddrFilter};

let custom_dns = DnsAddressLookup::new("my.internal.dns".into())
    .with_filter(AddrFilter::allow_ipv4());

```

Control which addresses are published using `builder.addr_filter(filter)` to prune sensitive IPs or relay URLs from public view.

## Optimize Transport Configuration and Port Mapping

The transport layer in [`iroh/src/socket/transports.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports.rs) handles low-level IP and relay transports. When customizing, start with a clean slate using `builder.clear_ip_transports()` before adding specific bind addresses:

```rust
let endpoint = Endpoint::builder(presets::Minimal)
    .clear_ip_transports()
    .bind_addr_with_opts(
        "192.0.2.10:0",
        iroh::endpoint::BindOpts::default()
            .set_prefix_len(24)
            .set_is_default_route(true),
    )?;

```

The **Portmapper** ([`iroh/src/portmapper.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/portmapper.rs)) provides optional UPnP/PCP/NAT-PMP for automatic gateway configuration. Enable this only with user consent, as UPnP can unintentionally expose services. In containerized or restrictive environments, explicitly disable it:

```rust
use iroh::portmapper::PortmapperConfig;

builder.portmapper_config(PortmapperConfig::Disabled);

```

## Configure TLS and Application-Level Protocol Negotiation

TLS configuration in [`iroh/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls.rs) provides authentication and encryption for QUIC streams and HTTPS calls to relays. When not using presets, you must provide a crypto provider via `builder.crypto_provider()`.

The **ALPN** (Application-Layer Protocol Negotiation) vector identifies your protocol on the QUIC connection. Always set at least one ALPN on the builder or on an already-bound endpoint using `set_alpns`. Avoid generic strings that may clash with other services:

```rust
let endpoint = Endpoint::builder(presets::N0)
    .alpns(vec![b"my-app/1.0".to_vec()])
    .bind()
    .await?;

```

## Leverage Hooks for Observability and Policy

The hooks system in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs) allows you to inject logic at specific lifecycle stages. Implement `EndpointHooks` to audit connections or enforce policy during the handshake:

```rust
use iroh::endpoint::hooks::{EndpointHooks, AfterHandshakeOutcome};

struct AuditHook;

impl EndpointHooks for AuditHook {
    async fn after_handshake(&self, remote: iroh::EndpointId) -> AfterHandshakeOutcome {
        tracing::info!("Handshake completed with {}", remote.fmt_short());
        AfterHandshakeOutcome::Accept
    }
}

let ep = Endpoint::builder(presets::N0)
    .hooks(AuditHook)
    .bind()
    .await?;

```

## Utilize Net Report for Diagnostics

The net report component in [`iroh/src/net_report/mod.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/mod.rs) probes relays to discover NAT behavior, captive portals, and latency. While this feature is unstable, it provides invaluable diagnostics during development. Configure it via the builder:

```rust
builder.net_report_config(NetReportConfig::default())

```

Access runtime metrics programmatically using `endpoint.net_report()` when available.

## Ensure Graceful Shutdown and Resource Cleanup

Always call `endpoint.close().await` before dropping the last `Endpoint` handle. This ensures the underlying QUIC socket terminates cleanly and stops all background tasks including address lookup, portmapper, and net report services.

Avoid blocking calls in async contexts. All Iroh API methods are async and require the Tokio runtime, which the crate depends on. Never call `.await` inside `std::thread::spawn` without a proper runtime context.

## Practical Implementation Examples

### Simple Client-Server with N0 Preset

This example demonstrates bi-directional stream exchange using the recommended preset and ALPN configuration:

```rust
use iroh::{Endpoint, endpoint::presets};
use n0_error::Result;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

#[tokio::main]
async fn main() -> Result<()> {
    // Server side
    let server = Endpoint::builder(presets::N0)
        .alpns(vec![b"my-proto".to_vec()])
        .bind()
        .await?;

    tokio::spawn(async move {
        let conn = server.accept().await?.await?;
        let (mut send, mut recv) = conn.accept_bi().await?;
        let mut buf = vec![0; 5];
        recv.read_exact(&mut buf).await?;
        println!("Server received: {:?}", std::str::from_utf8(&buf));
        send.write_all(b"world").await?;
        send.finish().await?;
        Ok::<_, n0_error::Error>(())
    });

    // Client side
    let client = Endpoint::builder(presets::N0)
        .alpns(vec![b"my-proto".to_vec()])
        .bind()
        .await?;

    let server_addr = client.addr();
    let conn = client.connect(server_addr, b"my-proto").await?;
    let (mut send, mut recv) = conn.open_bi().await?;
    send.write_all(b"hello").await?;
    send.finish().await?;
    
    let mut resp = Vec::new();
    recv.read_to_end(&mut resp).await?;
    println!("Client got: {:?}", std::str::from_utf8(&resp));
    Ok(())
}

```

### Custom Relay and Transport Configuration

For self-hosted infrastructure or specific network requirements:

```rust
use iroh::{
    endpoint::{Builder, RelayMode, presets},
    RelayUrl,
};
use std::collections::HashMap;

let custom_relay: RelayUrl = "https://my-relay.example.com".parse()?;

let endpoint = Builder::new(presets::Minimal)
    .relay_mode(RelayMode::Custom(
        HashMap::from([(custom_relay.clone(), Default::default())]),
    ))
    .clear_ip_transports()
    .bind_addr_with_opts(
        "192.0.2.10:0",
        iroh::endpoint::BindOpts::default()
            .set_prefix_len(24)
            .set_is_default_route(true),
    )?
    .portmapper_config(PortmapperConfig::Disabled)
    .bind()
    .await?;

```

## Summary

- **Use presets** (`presets::N0` or `presets::Minimal`) from [`iroh/src/endpoint/presets.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/presets.rs) to establish sane defaults and avoid crypto provider misconfiguration.
- **Explicitly configure relay modes** via `Builder::relay_mode` in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) (lines 542-574) to control relay server connectivity.
- **Manage external addresses** through `external_addr()` and `add_external_addr()` to improve NAT traversal.
- **Disable the portmapper** in containerized environments using `PortmapperConfig::Disabled` from [`iroh/src/portmapper.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/portmapper.rs).
- **Configure TLS** via [`iroh/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls.rs) when not using presets, ensuring you provide a valid crypto provider.
- **Set specific ALPN identifiers** to prevent protocol conflicts on QUIC connections.
- **Implement hooks** from [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs) for connection auditing and policy enforcement.
- **Always call `close().await`** before dropping the endpoint to ensure clean resource cleanup.

## Frequently Asked Questions

### What is the difference between presets::N0 and presets::Minimal?

**`presets::N0`** bundles the default Number-0 relay configuration, DNS address lookup via PKARR, and the ring crypto provider, making it ideal for general internet connectivity. **`presets::Minimal`** provides a stripped-down foundation without pre-configured relays or address lookup services, requiring you to manually specify each component in [`iroh/src/endpoint/presets.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/presets.rs). Use Minimal when you need complete control over network dependencies or are building specialized LAN-only applications.

### When should I disable the portmapper in Iroh?

Disable the portmapper using `builder.portmapper_config(PortmapperConfig::Disabled)` when running in containerized environments, restrictive corporate networks, or any scenario where UPnP/NAT-PMP is unavailable or prohibited. According to [`iroh/src/portmapper.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/portmapper.rs), enabling portmapping without user consent can unintentionally expose services through gateway routers, so it should only be activated when explicitly needed for NAT traversal in trusted environments.

### How do I configure custom relays in Iroh?

Use `RelayMode::Custom` with a `HashMap<RelayUrl, RelaySettings>` to point at corporate or self-hosted relay infrastructure. As implemented in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) lines 542-574, the `Builder::relay_mode` method converts this configuration into transport-layer relay connections. Combine this with `clear_ip_transports()` if you want to replace rather than supplement the default relay map.

### What is the purpose of ALPN in Iroh endpoints?

ALPN (Application-Layer Protocol Negotiation) vectors identify your specific protocol implementation on the QUIC connection, preventing conflicts with other services that might use the same port. Set ALPNs via `builder.alpns()` or `endpoint.set_alpns()` using specific byte strings like `b"my-app/1.0"` rather than generic identifiers. This ensures that incoming connections are routed to the correct protocol handler within your application.