# What Is the Role of Relay Servers in Iroh?

> Explore how Iroh relay servers act as encrypted fallback transports, forwarding QUIC datagrams when direct connections fail, guaranteeing connectivity for all users.

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

---

**Relay servers in Iroh serve as encrypted fallback transports that forward QUIC datagrams between peers when direct hole-punching through NATs fails, ensuring universal connectivity without compromising end-to-end encryption.**

Iroh implements a "dial-by-public-key" architecture where endpoints first attempt to establish direct peer-to-peer connections. When symmetric NATs, firewall rules, or UDP restrictions block direct paths, the role of relay servers in Iroh becomes critical as they provide a resilient WebSocket-based tunnel that preserves the protocol's zero-trust security model.

## Why Relay Servers Matter in Iroh's Network Stack

Iroh endpoints prioritize direct QUIC connections using NAT hole-punching. However, certain network conditions—such as symmetric NATs, strict corporate firewalls, or mobile carrier restrictions—prevent direct UDP connectivity. In these scenarios, Iroh falls back to a **relay server**, which acts as a publicly reachable HTTP/WebSocket proxy that forwards encrypted traffic between peers.

According to the n0-computer/iroh source code, the relay is not a traditional VPN or proxy; it operates at the datagram level, forwarding `ClientToRelayMsg` and `RelayToClientMsg` packets without access to plaintext payloads.

## Core Responsibilities of Relay Servers

The implementation in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs) defines several critical duties that ensure reliable connectivity across diverse network conditions.

### Fallback Transport via WebSocket

When direct QUIC establishment fails, clients open a WebSocket connection to the relay server. The relay then forwards encrypted datagrams between the two peers using the `ClientToRelayMsg` and `RelayToClientMsg` protocols. This fallback is transparent to the application layer, which continues to use QUIC semantics.

The HTTP service infrastructure is established in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs) (beginning at line 12), with request handlers such as `root_handler`, `probe_handler`, and `serve_no_content_handler` wired into the server routing table.

### QUIC Address Discovery (QAD)

Beyond simple relaying, the server performs **QUIC Address Discovery (QAD)** to enable fast-path optimization. After the initial WebSocket exchange, the relay advertises a dedicated QUIC endpoint that clients can use for subsequent connections, reducing latency for future sessions.

The QAD service spawns from the `Server::spawn` function (lines 441-456 in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs)), creating a `QuicServer` instance that runs alongside the WebSocket relay.

### Authentication and Access Control

Each client presents an `EndpointId` (derived from its public key) and optionally an authentication token. The relay implements the `AccessControl` trait to enforce connection policies:

- **Default behavior**: The `AllowAll` implementation (lines 336-342 in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs)) permits all connections for open relays.
- **Custom policies**: Implement the `AccessControl` trait to create blocklists, rate limiting, or token-based authentication.

The `RelayConfig::access` field (line 145) holds an `Arc<dyn DynAccessControl>`, allowing runtime configuration of access policies.

### Health Monitoring and Metrics

Production relay deployments expose standard endpoints for operational monitoring:
- `/healthz` for health checks
- [`/robots.txt`](https://github.com/n0-computer/iroh/blob/main//robots.txt) and `/generate_204` for captive portal detection
- Prometheus-compatible metrics endpoints

These handlers are defined in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs) (lines 86-115) and help mobile operators and network administrators detect and manage relay traffic.

### Client Key Caching

High-load relays utilize a memory cache for client public keys to reduce allocation overhead. The default capacity is defined in [`iroh-relay/src/defaults.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/defaults.rs) (line 18) as `DEFAULT_KEY_CACHE_CAPACITY`, set to 56 MiB to support approximately 1 million concurrent clients.

## How the Relay Map Works

Clients do not hardcode single relay addresses. Instead, they use a **relay map** ([`iroh-relay/src/relay_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/relay_map.rs)), a client-side data structure containing one or more `RelayConfig` entries. This map allows endpoints to:

- Try multiple relays in sequence
- Attach common authentication tokens across all entries
- Update relay URLs at runtime without recompilation

The map is constructed from `RelayUrl` instances (e.g., `https://relay.iroh.link`) and integrated into the client builder before establishing connections.

## Practical Implementation: Code Examples

### Starting a Local Relay Server for Testing

The `Server::spawn` function (lines 891-950 in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs)) provides a programmatic way to launch relays for integration testing:

```rust
use iroh_relay::{Server, ServerConfig, RelayConfig};
use std::net::Ipv4Addr;

// Create a simple relay that listens on a random local port
let relay_cfg = RelayConfig::new((Ipv4Addr::LOCALHOST, 0));
let server = Server::spawn(ServerConfig {
    relay: Some(relay_cfg),
    quic: None,
    metrics_addr: None,
}).await.unwrap();

// Obtain the HTTP address (e.g., `http://127.0.0.1:12345`)
let http_url = format!("http://{}", server.http_addr().unwrap());
println!("Relay listening at {http_url}");

```

### Configuring a Client to Use Public Relays

Clients use `RelayMap` to discover and configure relay endpoints. The `RelayMap::with_auth_token` method (lines 148-159 in [`iroh-relay/src/relay_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/relay_map.rs)) attaches authentication credentials:

```rust
use iroh_base::RelayUrl;
use iroh_relay::RelayMap;
use iroh::client::ClientBuilder;

// Parse a public relay URL
let relay_url: RelayUrl = "https://relay.iroh.link".parse().unwrap();

// Build a RelayMap with a single entry
let relay_map = RelayMap::from_iter(vec![relay_url.clone()]);

// Attach authentication if required
let relay_map = relay_map.with_auth_token("my-secret-token");

// Construct the client with relay fallback enabled
let client = ClientBuilder::new(relay_map, secret_key, dns_resolver())
    .connect()
    .await?;

```

### Implementing Custom Access Control

Create custom access policies by implementing the `AccessControl` trait (lines 78-96 in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs)):

```rust
use std::sync::Arc;
use iroh_relay::{Access, AccessControl, ClientRequest};

struct BlocklistAccessControl {
    blocked: EndpointId,
}

#[async_trait::async_trait]
impl AccessControl for BlocklistAccessControl {
    async fn on_connect(&self, request: &ClientRequest) -> Access {
        if request.endpoint_id() == self.blocked {
            Access::Deny {
                reason: Some("blocked endpoint".into()),
            }
        } else {
            Access::Allow
        }
    }
}

// Apply the custom policy when building the relay
let access = Arc::new(BlocklistAccessControl { blocked: some_endpoint_id });
let relay_cfg = RelayConfig::new((std::net::Ipv4Addr::LOCALHOST, 0))
    .with_access(access);

```

## Summary

- **Relay servers** in Iroh provide encrypted fallback connectivity when direct hole-punching fails, using WebSocket tunnels that preserve end-to-end encryption.
- The **relay server** implementation in [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs) handles WebSocket transport, QUIC Address Discovery (QAD), authentication, and health monitoring.
- **Access control** is configurable via the `AccessControl` trait, with `AllowAll` as the default permissive implementation.
- Clients use a **relay map** ([`iroh-relay/src/relay_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/relay_map.rs)) to manage multiple relay endpoints and authentication tokens.
- Production relays utilize **key caching** (56 MiB default) to efficiently handle millions of concurrent clients.

## Frequently Asked Questions

### Does the relay server decrypt my traffic?

No. The relay server forwards encrypted QUIC datagrams between peers using the `ClientToRelayMsg` and `RelayToClientMsg` protocols. It never possesses the private keys necessary to decrypt payloads, maintaining Iroh's end-to-end encryption guarantees even when traffic routes through public relays.

### How does a client choose which relay server to use?

Clients consult the **relay map** ([`iroh-relay/src/relay_map.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/relay_map.rs)), which contains one or more `RelayConfig` entries. The client attempts connections to relays in sequence until it establishes a successful WebSocket tunnel. This map can be configured at runtime with multiple URLs for high availability.

### Can I run my own private relay server?

Yes. You can spawn a custom relay using `Server::spawn` from [`iroh-relay/src/server.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/server.rs) with a custom `RelayConfig` and `AccessControl` implementation. This allows you to deploy private relays behind corporate firewalls or in specific geographic regions while maintaining compatibility with the Iroh protocol.

### What is the difference between relay mode and direct QUIC mode?

Direct QUIC mode establishes a peer-to-peer UDP connection between endpoints, providing optimal latency and throughput. **Relay mode** activates only when direct hole-punching fails, tunneling encrypted datagrams through a WebSocket connection to a publicly reachable relay. The application layer remains unaware of which transport mode is active, as Iroh handles the fallback automatically.