# How to Configure Custom Relay Servers in Iroh: A Complete Guide

> Learn how to configure custom relay servers in Iroh with this complete guide. Follow simple steps to set up your RelayMap and enhance your P2P network.

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

---

**To configure custom relay servers in Iroh, construct a `RelayMap` containing your `RelayUrl` and `RelayConfig` pairs, then pass it to `Endpoint::builder().relay_mode(RelayMode::Custom(map))` before calling `bind()`.**

Iroh uses **relay servers** as fallback infrastructure to forward QUIC traffic when direct hole-punching between peers fails. By default, the library connects to the public relay fleet operated by the project, but you can configure custom relay servers in Iroh to route traffic through private infrastructure, reduce latency with geographically close nodes, or isolate test environments. This implementation requires coordinating four core types across the `iroh-base`, `iroh-relay`, and `iroh` crates.

## Core Components for Custom Relay Configuration

Four primary types work together to enable custom relay configuration, each defined in specific source files within the `n0-computer/iroh` repository:

- **`RelayUrl`** ([`iroh-base/src/relay_url.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/relay_url.rs)) – A thin wrapper around `url::Url` that validates relay addresses.
- **`RelayConfig`** ([`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs)) – Holds the HTTP/QUIC bind address, TLS configuration, and access control settings for a single relay instance.
- **`RelayMap`** ([`iroh-relay/src/relay_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/relay_map.rs)) – A thread-safe map from `RelayUrl` to `Arc<RelayConfig>` that the endpoint consults to locate available relays.
- **`RelayMode`** ([`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)) – An enum consumed by the `EndpointBuilder`; use `RelayMode::Default` for public relays or `RelayMode::Custom` with your map for private infrastructure.

## Step-by-Step Configuration Process

### Parse the Relay URL

First, create a `RelayUrl` from a string using the standard `FromStr` implementation located in [`iroh-base/src/relay_url.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/relay_url.rs):

```rust
use iroh_base::RelayUrl;

let url = "https://my.relay.example.com".parse::<RelayUrl>()?;

```

This validates the URL format and scheme requirements specific to Iroh relay protocols.

### Create a RelayConfig

Next, instantiate a `RelayConfig` by pairing the URL with a `QuicConfig` (defined in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs)). The QUIC configuration controls transport settings for the relay client connection:

```rust
use iroh_relay::{RelayConfig, quic::QuicConfig};

let quic = QuicConfig::default();
let cfg = RelayConfig::new(url.clone(), quic);

```

`RelayConfig::new` binds an internal HTTP listener and prepares the QUIC server parameters according to the implementation in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs).

### Build the RelayMap

Insert the configuration into a `RelayMap` using the functional `insert` method from [`iroh-relay/src/relay_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/relay_map.rs). This returns a new map instance rather than mutating in place:

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

let relay_map = RelayMap::empty()
    .insert(url.clone(), Arc::new(cfg))
    .expect("first insertion cannot fail");

```

The map uses `Arc<RelayConfig>` internally to enable thread-safe sharing across async tasks.

### Configure the Endpoint

Finally, pass the map to the endpoint builder via `RelayMode::Custom`. The conversion from `RelayMode` to internal transport configuration occurs in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) (around line 1920 in the match logic):

```rust
use iroh::{Endpoint, RelayMode};

let endpoint = Endpoint::builder()
    .relay_mode(RelayMode::Custom(relay_map))
    .bind()
    .await?;

```

Once bound, the endpoint will consult your custom map when fallback relay connections are required.

## Practical Implementation Examples

### Minimal Custom Relay Setup

This complete example demonstrates wiring a single custom relay into an Iroh endpoint:

```rust
use iroh::{Endpoint, RelayMode};
use iroh_base::RelayUrl;
use iroh_relay::{RelayConfig, RelayMap, quic::QuicConfig};
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Parse the relay URL
    let url = "https://my.relay.example.com".parse::<RelayUrl>()?;
    
    // Build configuration with default QUIC settings
    let quic = QuicConfig::default();
    let cfg = RelayConfig::new(url.clone(), quic);
    
    // Insert into the map
    let relay_map = RelayMap::empty()
        .insert(url, Arc::new(cfg))
        .expect("first insertion cannot fail");
    
    // Create endpoint with custom relay mode
    let endpoint = Endpoint::builder()
        .relay_mode(RelayMode::Custom(relay_map))
        .bind()
        .await?;
    
    Ok(())
}

```

### Loading Relays from TOML Configuration

For production deployments, load relay URLs from a configuration file and build the map programmatically:

```rust
use iroh::{Endpoint, RelayMode};
use iroh_base::RelayUrl;
use iroh_relay::{RelayConfig, RelayMap, quic::QuicConfig};
use std::{fs, sync::Arc};

#[derive(serde::Deserialize)]
struct Config {
    relays: Vec<RelayEntry>,
}

#[derive(serde::Deserialize)]
struct RelayEntry {
    url: String,
}

fn build_map_from_toml(toml_str: &str) -> anyhow::Result<RelayMap> {
    let cfg: Config = toml::from_str(toml_str)?;
    let mut map = RelayMap::empty();

    for entry in cfg.relays {
        let url = entry.url.parse::<RelayUrl>()?;
        let quic = QuicConfig::default();
        let relay_cfg = RelayConfig::new(url.clone(), quic);
        map = map.insert(url, Arc::new(relay_cfg))
            .expect("first insertion cannot fail");
    }
    Ok(map)
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let toml = fs::read_to_string("example.config.toml")?;
    let relay_map = build_map_from_toml(&toml)?;

    let endpoint = Endpoint::builder()
        .relay_mode(RelayMode::Custom(relay_map))
        .bind()
        .await?;
    Ok(())
}

```

### Running a Local Test Relay

Mirror the test harness pattern from [`iroh/tests/patchbay/util.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay/util.rs) to spawn an in-process relay for integration testing:

```rust
use iroh::{Endpoint, RelayMode};
use iroh_base::RelayUrl;
use iroh_relay::{RelayConfig, RelayMap, quic::QuicConfig, server::{Server, RelayServerConfig}};
use std::sync::Arc;

async fn run_local_relay() -> anyhow::Result<(RelayMap, Server)> {
    // Bind to arbitrary port on local interface
    let bind_ip = ([0, 0, 0, 0], 0);
    let mut config = RelayServerConfig::new(bind_ip);
    // Use auto-generated self-signed cert for testing
    config.tls = None;

    // Start the server (spawns QUIC listener internally)
    let server = Server::new(config).await?;
    let url: RelayUrl = format!("https://{}", server.listen_addr()).parse()?;

    // Build RelayConfig pointing at the local server
    let relay_cfg = RelayConfig::new(url.clone(), QuicConfig::default());
    let map = RelayMap::empty()
        .insert(url, Arc::new(relay_cfg))
        .expect("first insertion cannot fail");
    
    Ok((map, server))
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let (relay_map, _server) = run_local_relay().await?;

    let endpoint = Endpoint::builder()
        .relay_mode(RelayMode::Custom(relay_map))
        .bind()
        .await?;
    
    Ok(())
}

```

This pattern allows tests to run without external network dependencies.

## Why Configure Custom Relay Servers?

**Private networks** – Corporate or campus deployments often require traffic to remain within internal infrastructure rather than traversing public relays.

**Testing and CI** – The `lab_with_relay` pattern in [`iroh/tests/patchbay/util.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay/util.rs) demonstrates how to spin up temporary relays bound to `[::]:80`, enabling hermetic integration tests.

**Performance optimization** – Deploying relays geographically close to your user base reduces latency compared to the globally distributed default relay fleet.

## Summary

- **Custom relay configuration** requires building a `RelayMap` containing `RelayUrl` → `Arc<RelayConfig>` mappings.
- **Use `RelayMode::Custom`** when constructing the `Endpoint` via `Endpoint::builder().relay_mode()` to override the default public relay set.
- **Key source files** include [`iroh-base/src/relay_url.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/relay_url.rs) for URL parsing, [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs) for configuration structs, and [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) for the relay mode integration.
- **Thread safety** is handled automatically via `Arc<RelayConfig>` inside the `RelayMap` implementation ([`iroh-relay/src/relay_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/relay_map.rs)).
- **Local testing** can mirror the harness in [`iroh/tests/patchbay/util.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/tests/patchbay/util.rs) by spawning in-process relay servers with auto-generated TLS certificates.

## Frequently Asked Questions

### What is the default relay mode in Iroh?

The default mode is `RelayMode::Default`, which automatically connects to the public relay infrastructure operated by the Iroh project. To use custom infrastructure, switch to `RelayMode::Custom` and provide your own `RelayMap` as shown in the configuration examples above.

### Can I run a relay server without TLS for local development?

Yes. Set `config.tls = None` in your `RelayServerConfig` (defined in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs)) when creating the server. The library will typically auto-generate self-signed certificates for local QUIC development, or you can disable TLS entirely for testing purposes as demonstrated in the local test relay example.

### How does `RelayMap` handle concurrent access?

`RelayMap` is designed to be thread-safe and uses `Arc<RelayConfig>` internally to share configuration data across async tasks without cloning the underlying configuration struct. This implementation resides in [`iroh-relay/src/relay_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/relay_map.rs) and allows safe concurrent reads from multiple connection attempts.

### What is the difference between `RelayConfig` and `RelayServerConfig`?

`RelayConfig` (used in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs)) configures a client-side view of a relay—telling the Iroh endpoint how to connect to a specific relay. `RelayServerConfig` configures the actual relay server process itself, including the HTTP bind address, TLS certificates, and access controls when running your own relay infrastructure.