How to Configure Relay Servers vs. Direct Connections in iroh
Use the relay_mode method on the Endpoint builder to select production, staging, custom, or disabled relay servers, while direct IP connections are automatically attempted first and can be forced via clear_ip_transports() or clear_relay_transports().
The iroh networking library (n0-computer/iroh) establishes peer-to-peer connections using two distinct transport mechanisms: direct IP paths and relay-mediated tunnels. Understanding how to configure relay servers versus direct connections in iroh allows you to optimize connectivity for environments ranging from open internet deployments to strict corporate NATs.
Direct vs. Relay Transport Mechanisms
Iroh endpoints communicate over two transport types that operate sequentially during connection establishment.
Direct (IP) transports are used when peers can reach each other via public or NAT-traversed IP addresses. The endpoint discovers these addresses through address-lookup services and attempts them first. These transports are added automatically when you bind an address or can be removed explicitly via builder methods.
Relay transports function as a fallback mechanism. When direct paths cannot be established, the endpoint automatically falls back to a relay server that forwards encrypted traffic between peers. This behavior is controlled through the RelayMode enum and configured via the endpoint builder.
The RelayMode Enum
The core of relay configuration resides in iroh/src/endpoint.rs (lines 1912-1924). The RelayMode enum determines which relay servers an endpoint will use:
RelayMode::Disabled– Disables all relay servers. Only direct IP paths are possible.RelayMode::Default– Uses the production relay map shipped with iroh.RelayMode::Staging– Uses the staging relay map for testing environments.RelayMode::Custom(RelayMap)– Accepts a user-provided list of relay URLs for self-hosted or private relay infrastructure.
Defining Custom Relay Maps
A custom relay configuration requires the RelayUrl type defined in iroh-base/src/relay_url.rs (lines 7-22). This type wraps url::Url in an Arc for efficient cloning and validates URLs like "https://my.relay.example.com.".
To construct a custom relay map:
use iroh::{RelayMode, RelayUrl};
let relay_map = RelayMode::custom([
"https://my.relay-1.example.com.".parse::<RelayUrl>()?,
"https://my.relay-2.example.com.".parse::<RelayUrl>()?,
]);
The RelayMode::custom helper (lines 51-54 of endpoint.rs) creates a RelayMap from any iterator of RelayUrl values.
Builder Methods for Transport Control
The Endpoint builder in iroh/src/endpoint.rs provides specific methods to fine-tune transport behavior:
relay_mode(RelayMode)(lines 557-575) – Inserts or replaces the relay transport configuration according to the supplied mode.clear_relay_transports()(lines 1099-1103) – Removes all relay transports from the builder, creating a direct-only endpoint.clear_ip_transports()(lines 500-507) – Removes all IP transports, forcing the endpoint to use only relay connections.
Connection Preference and Fallback Logic
When an endpoint attempts to connect to a peer, it first tries all known direct addresses via IP transports. If none succeed within the timeout window, the endpoint automatically falls back to configured relay transports. This logic is implemented in iroh/src/socket/remote_map.rs, where the has_been_direct flag tracks whether a direct path has been successfully established.
You do not need to manually enable direct connections—they are always attempted first. You only need to adjust configuration when you want to:
- Force relay use for testing behind strict NATs by calling
clear_ip_transports(). - Disable relays entirely by calling
clear_relay_transports()or usingRelayMode::Disabled. - Use private relay servers by passing a custom
RelayMapviarelay_mode(RelayMode::Custom(...)).
Configuration Examples
Using Default Production Relays
The simplest configuration uses the presets::N0 preset, which enables both direct IP transports and the default production relay map:
use iroh::{Endpoint, endpoint::presets};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let endpoint = Endpoint::builder(presets::N0).bind().await?;
Ok(())
}
Configuring Custom Relay Servers
To use your own relay infrastructure while keeping direct connections as the primary path:
use iroh::{Endpoint, RelayMode, RelayUrl, endpoint::presets};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let custom = RelayMode::custom([
"https://relay-1.mycorp.net.".parse::<RelayUrl>()?,
"https://relay-2.mycorp.net.".parse::<RelayUrl>()?,
]);
let endpoint = Endpoint::builder(presets::N0)
.relay_mode(custom)
.bind()
.await?;
Ok(())
}
Direct-Only Mode
To disable relays entirely and allow only direct connections:
use iroh::{Endpoint, endpoint::presets};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let endpoint = Endpoint::builder(presets::N0)
.clear_relay_transports()
.bind()
.await?;
Ok(())
}
Relay-Only Mode
To force all traffic through relays (useful for testing relay infrastructure or operating in highly restricted networks):
use iroh::{Endpoint, endpoint::presets};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let endpoint = Endpoint::builder(presets::N0)
.clear_ip_transports()
.bind()
.await?;
Ok(())
}
Environment Variable for Staging
Setting IROH_FORCE_STAGING_RELAYS forces the endpoint to use the staging relay map instead of production. This is implemented in the default_relay_mode function (lines 564-574 of endpoint.rs):
export IROH_FORCE_STAGING_RELAYS=1
cargo run --example connect
Summary
- Direct connections are attempted automatically before any relay configuration takes effect.
- RelayMode controls which relay servers are available:
Disabled,Default,Staging, orCustom. - Custom relays require
RelayUrlvalues and are passed viarelay_mode(RelayMode::Custom(...)). - Builder methods
clear_relay_transports()andclear_ip_transports()force direct-only or relay-only operation. - Environment variable
IROH_FORCE_STAGING_RELAYSswitches to staging relays without code changes.
Frequently Asked Questions
How does iroh decide between direct and relay connections?
According to the source code in iroh/src/socket/remote_map.rs, iroh attempts all known direct IP addresses first. If no direct path succeeds within the connection timeout, the endpoint automatically falls back to configured relay servers. Once a direct path is established, the endpoint marks the connection with has_been_direct and may prefer that path for subsequent traffic.
Can I run iroh without any relay servers?
Yes. Pass RelayMode::Disabled to relay_mode() or call clear_relay_transports() on the endpoint builder. This creates a direct-only endpoint that will fail to connect if NAT traversal cannot establish a direct path between peers.
What is the difference between RelayMode::Default and RelayMode::Staging?
RelayMode::Default uses the production relay infrastructure operated by the iroh team, while RelayMode::Staging points to staging servers intended for testing. You can also force staging mode at runtime by setting the IROH_FORCE_STAGING_RELAYS environment variable, which overrides the default selection in default_relay_mode() (lines 564-574 of iroh/src/endpoint.rs).
How do I configure iroh to use my own relay servers?
Use RelayMode::Custom with a collection of RelayUrl values. Parse your relay URLs using "https://your.relay.com.".parse::<RelayUrl>()? and pass them to RelayMode::custom(). This method is defined at lines 51-54 of iroh/src/endpoint.rs and creates a RelayMap that the endpoint will use for fallback connections when direct paths fail.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →