# How Iroh Uses QUIC for Peer-to-Peer Connections: Direct Paths Through NATs

> Learn how Iroh uses QUIC for peer-to-peer connections. Discover direct paths through NATs enabled by relay-assisted discovery and public-key authentication.

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

---

**Iroh establishes direct peer-to-peer connections using QUIC by combining relay-assisted address discovery with public-key authentication, enabling nodes behind NATs to negotiate direct UDP paths without X.509 certificates.**

The [n0-computer/iroh](https://github.com/n0-computer/iroh) repository implements a peer-to-peer networking stack that leverages QUIC for secure, low-latency communication. Unlike traditional client-server QUIC implementations, Iroh uses QUIC for peer-to-peer connections by embedding custom NAT traversal logic directly into the connection lifecycle. This article examines how Iroh's `Endpoint` API orchestrates relay registration, QUIC address discovery, and direct path establishment to create resilient P2P links.

## Endpoint Creation and QUIC Transport Configuration

Iroh's QUIC implementation begins with the `Endpoint` builder pattern defined in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs). When calling `Endpoint::builder(...).bind()`, the system creates a UDP socket, generates a **secret key** for the node, and constructs a `QuicTransportConfig` that tunes the underlying `noq` QUIC stack for peer-to-peer scenarios.

The configuration logic resides in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs), where default transport settings are optimized for hole-punching and NAT traversal. This file re-exports core types from the `noq` crate, including `Connection`, `OpenBi`, and `RecvStream`, which the high-level API uses to manage QUIC streams.

```rust
// Create a default endpoint that can talk to relays and perform QAD.
let ep = iroh::Endpoint::bind(iroh::endpoint::presets::N0).await?;

```

## Relay Registration and QUIC Address Discovery

Before establishing direct paths, each endpoint must register with a **Relay server** to ensure reachability. The registration process, handled in [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs), establishes an HTTP/1.1 connection that upgrades to a custom binary protocol. The endpoint registers its **EndpointId** (derived from its public key), allowing the relay to forward encrypted datagrams when direct communication is not yet possible.

The relay facilitates **QUIC Address Discovery (QAD)** through [`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs), which defines a dedicated ALPN (`/iroh-qad/0`) for address discovery services. The relay server observes each node's external IP and port, publishing this information within QUIC-encrypted frames. Endpoints trigger QAD probes via [`iroh/src/net_report/probes.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/probes.rs) to learn their public-facing UDP addresses and discover the direct socket addresses of remote peers.

## Direct QUIC Handshake and Public Key Authentication

Armed with the remote peer's external address, Iroh attempts to bypass the relay entirely. The endpoint sends a **QUIC Initial** packet directly to the discovered UDP socket, initiating a handshake through the `noq` stack.

Crucially, this handshake uses **TLS 1.3 with public keys** rather than traditional X.509 certificates. Peers authenticate each other using their **EndpointId** (public keys), eliminating the need for certificate authorities in pure peer-to-peer scenarios. The `Endpoint::connect` method in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs) orchestrates this process, returning a `Connection` object that represents a direct QUIC channel once the handshake succeeds.

```rust
// Connect to a remote peer (EndpointId is a public key) using an ALPN label.
let conn = ep.connect(remote_endpoint_id, b"my-alpn").await?;

```

## Path Selection and Relay Fallback

Iroh maintains redundant connectivity paths to ensure session resilience. The system tracks both **direct** UDP paths and **relay** paths simultaneously in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs). When a direct QUIC connection drops—such as when a NAT mapping expires—the endpoint can instantly fall back to the relay path without terminating higher-level streams.

The [`remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/remote_state.rs) module marks connections as "direct" when valid IP paths are confirmed, while [`iroh/src/socket/metrics.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/metrics.rs) exposes counters (`paths_direct`) that reflect the current state of direct connectivity. This dual-path approach ensures that applications experience seamless connectivity even as network conditions change.

## Working with QUIC Streams

Once established, direct QUIC connections support cheap, multiplexed streams. Iroh provides **bidirectional** (`open_bi`) and **unidirectional** (`open_uni`) streams that share the underlying connection, allowing multiple simultaneous conversations without head-of-line blocking.

The [`iroh/examples/listen.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/listen.rs) file demonstrates accepting incoming connections and reading from streams, while the library documentation in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs) shows how to open bidirectional streams for request-response patterns.

```rust
// Open a bidirectional stream, send a message and receive the reply.
let (mut send, mut recv) = conn.open_bi().await?;
send.write_all(b"hello").await?;
send.finish().await?;
let reply = recv.read_to_end(1024).await?;
println!("got: {}", String::from_utf8_lossy(&reply));

```

## Summary

Iroh implements QUIC for peer-to-peer connections through a sophisticated multi-stage process:

- **Endpoint initialization** creates QUIC configurations tuned for NAT traversal in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs).
- **Relay registration** establishes fallback connectivity and enables address discovery via [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs).
- **QUIC Address Discovery** reveals public UDP endpoints through the relay's QAD server in [`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs).
- **Direct authentication** uses public-key TLS 1.3 handshakes without X.509 certificates, implemented through the `noq` stack.
- **Path management** maintains parallel direct and relay paths, with automatic fallback handled in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs).

## Frequently Asked Questions

### How does Iroh handle NAT traversal without traditional STUN/TURN servers?

Iroh uses a **relay-assisted** approach combined with **QUIC Address Discovery (QAD)**. Nodes register with Iroh relay servers that observe their external UDP addresses and share them with peers. This allows endpoints to attempt direct UDP hole punching using the discovered addresses, falling back to the relay only when necessary. According to the source code in [`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs), the relay runs a dedicated QUIC server for address discovery that publishes observed IPs within encrypted QUIC frames.

### What authentication mechanism does Iroh use for QUIC connections?

Iroh authenticates peers using **public keys** embedded in the TLS 1.3 handshake rather than X.509 certificates. Each node's **EndpointId** is derived from its public key, allowing mutual authentication without certificate authorities. This is implemented in the `noq` QUIC stack, which is re-exported in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs). When `Endpoint::connect` is called, the handshake validates that the remote peer possesses the private key corresponding to the expected public key.

### How does Iroh maintain connectivity when direct paths fail?

The system implements **seamless path migration** by maintaining both direct and relay paths simultaneously. As implemented in [`iroh/src/socket/remote_map/remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/remote_map/remote_state.rs), the endpoint tracks whether a connection has established a direct IP path. If the direct QUIC connection drops due to NAT timeout or firewall changes, the endpoint instantly falls back to the relay path without breaking application-level streams. Metrics in [`iroh/src/socket/metrics.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/metrics.rs) track these transitions via the `paths_direct` counter.

### Can Iroh endpoints communicate without any relay infrastructure?

While Iroh can establish direct connections between nodes with public IP addresses or within the same network, the relay infrastructure is required for the initial **QUIC Address Discovery** process when nodes are behind NATs. The code in [`iroh/src/net_report/probes.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/net_report/probes.rs) triggers QAD probes that rely on the relay to observe and report external addresses. However, once direct paths are established and confirmed in [`remote_state.rs`](https://github.com/n0-computer/iroh/blob/main/remote_state.rs), communication can occur entirely over direct UDP/QUIC without relay involvement.