# Performance Tradeoffs Between Relay and Direct Connections in iroh

> Explore performance tradeoffs between iroh relay and direct connections. Understand latency, bandwidth, and connectivity implications to choose the best option for your needs.

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

---

**Relay connections guarantee connectivity across NATs and firewalls but introduce additional latency and bandwidth overhead, whereas direct connections offer lower latency and higher throughput but require both endpoints to be directly reachable.**

When building peer-to-peer applications with the iroh networking library, understanding the performance tradeoffs between relay and direct connections is crucial for optimizing your application's behavior. The iroh codebase implements two distinct transport paths—relay-mediated and direct UDP/QUIC—each with specific characteristics regarding latency, resource usage, and NAT traversal capabilities. This analysis examines the implementation details found in the `n0-computer/iroh` repository to help you choose the right connection strategy for your use case.

## Connection Architecture: Relay vs Direct Paths

### Relay-Based Connection Flow

In the relay-based path, traffic routes through a **relay server** that both peers know about. According to [`iroh/docs/relays.md`](https://github.com/n0-computer/iroh/blob/main/iroh/docs/relays.md), the relay acts as a middle-man for hole-punching and packet forwarding, providing a reachable address when direct connectivity isn't possible. The relay logic lives primarily in the `iroh-relay` crate, with the `RelayUrl` type defined in [`iroh-base/src/relay_url.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/relay_url.rs) handling URL parsing and validation.

### Direct UDP/QUIC Connections

Direct connections bypass intermediaries entirely. As implemented in [`iroh/src/socket/transports/ip.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/ip.rs), peers send packets directly to each other over UDP using QUIC. The `DirectAddr` structures in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) (lines 176-178) represent UDP socket addresses that endpoints use for direct communication. This path consumes bandwidth only on the two endpoints, eliminating the relay bottleneck.

## Performance Tradeoffs Analysis

### Latency Implications

Relay connections add an extra network hop, increasing round-trip time (RTT) latency. The iroh codebase mitigates this by measuring latency to each known relay at startup and selecting the "home" relay with the lowest latency. However, direct connections still typically offer lower latency because packets travel the shortest possible route without relay-induced delays.

### Bandwidth and Throughput

Every byte in a relay connection traverses the relay server, consuming its bandwidth and CPU resources. This makes the relay a potential bottleneck when many high-throughput streams share the same server. Direct connections avoid this limitation entirely, with bandwidth consumed only on the endpoints, resulting in higher overall throughput.

### NAT Traversal and Reachability

Relay connections are essential when either endpoint sits behind a symmetric NAT or firewall that blocks inbound UDP. The relay provides a reachable address for hole-punching, guaranteeing connectivity. Direct connections only work when both endpoints are directly reachable without symmetric NAT or inbound firewall blocks. If NAT traversal fails, iroh automatically falls back to a relay connection.

### Reliability and Resource Usage

While relay connections depend on the relay server's uptime, they remain robust as long as the selected relay stays online. Direct connections are vulnerable to NAT time-outs, packet loss, or network paths that silently drop UDP, potentially requiring periodic keep-alives. From a resource perspective, relay mode requires running the `iroh-relay` binary (implemented in [`iroh-relay/src/main.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/main.rs)) with TLS certificates and load balancing, whereas direct connections require no extra server resources.

### Security Considerations

Both connection types use end-to-end encryption, but relay connections expose packet sizes and timing to the relay server, which can also enforce policies like rate-limiting. Direct connections offer pure end-to-end encryption with metadata limited to source and destination IPs.

## Implementation in the iroh Codebase

The transport selection logic is split across specific modules. The relay client implementation resides in [`iroh/src/socket/transports/relay.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay.rs), handling the connection lifecycle and packet forwarding through the relay. For direct connections, [`iroh/src/socket/transports/ip.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/ip.rs) manages UDP socket handling and address discovery.

When an endpoint starts, it performs latency tests against configured relays to select the optimal home relay. If both peers report reachable UDP addresses, iroh prefers the direct path to avoid relay overhead. Otherwise, it falls back to the relay path, ensuring connectivity across restrictive network environments.

## Configuring Connection Modes

You can control the connection behavior through the endpoint configuration. Here are examples showing how to force direct mode, specify a relay, or allow automatic selection:

```rust
use iroh::net::{Endpoint, RelayUrl};
use iroh::transport::TransportOpts;

// Force direct connections only (no relay)
let mut opts = TransportOpts::default();
opts.relay = None;  // Disable relay usage
let ep = Endpoint::builder()
    .transport(opts)
    .bind()
    .await?;  // Endpoint will only attempt direct UDP

// Connect via a specific relay
let relay = RelayUrl::try_from("https://relay-0.example.com")?;
let mut opts = TransportOpts::default();
opts.relay = Some(relay);  // Override auto-selection
let ep = Endpoint::builder()
    .transport(opts)
    .bind()
    .await?;  // Uses specified relay for hole-punching

// Automatic relay selection (recommended)
let ep = iroh::Endpoint::builder()
    .bind()
    .await?;  // iroh tests latency and selects the fastest home relay

```

These configurations compile against iroh crate version 0.13 and above. The `TransportOpts::relay` field controls whether the endpoint uses the relay transport ([`src/socket/transports/relay.rs`](https://github.com/n0-computer/iroh/blob/main/src/socket/transports/relay.rs)) or the direct UDP transport ([`src/socket/transports/ip.rs`](https://github.com/n0-computer/iroh/blob/main/src/socket/transports/ip.rs)).

## Summary

- **Relay connections** provide guaranteed connectivity across NATs and firewalls at the cost of additional latency and bandwidth overhead.
- **Direct connections** deliver optimal performance with lower latency and higher throughput but require both endpoints to be directly reachable.
- The iroh implementation automatically selects the best path, falling back to relays when direct connectivity fails.
- Key files include [`iroh-base/src/relay_url.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/relay_url.rs) for relay configuration, [`iroh/src/socket/transports/ip.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/ip.rs) for direct UDP handling, and [`iroh/src/socket/transports/relay.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay.rs) for relay client logic.
- Configure connection behavior via `TransportOpts` when building your endpoint.

## Frequently Asked Questions

### When should I use a relay instead of a direct connection?

Use a relay when either endpoint is behind a symmetric NAT or restrictive firewall that blocks inbound UDP packets. The relay acts as a trusted intermediary for hole-punching, ensuring connectivity even when direct paths are blocked by network infrastructure.

### How does iroh select which relay to use?

iroh measures latency to each configured relay at startup and selects the "home" relay with the lowest RTT. This process is documented in [`iroh/docs/relays.md`](https://github.com/n0-computer/iroh/blob/main/iroh/docs/relays.md) and implemented in the relay client code to minimize the performance penalty of relayed traffic.

### Can I force my application to only use direct connections?

Yes, by setting `TransportOpts::relay` to `None` when constructing your endpoint. However, this will prevent connections from succeeding if either peer is behind a restrictive NAT or firewall, so use this option only when you control the network environment of both endpoints.

### What are the security implications of using relay servers?

While traffic remains encrypted end-to-end, the relay server can observe packet sizes and timing metadata. The relay may also enforce rate-limiting policies. For applications requiring minimal metadata exposure, direct connections are preferable as they eliminate the relay as a potential observer.