# How to Use ALPN in iroh QUIC Connections: Protocol Negotiation Explained

> Learn how to use ALPN in iroh QUIC connections for seamless application protocol negotiation. Understand this critical TLS extension for effective stream handling and ensure compatible communication between peers.

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

---

**Application-Layer Protocol Negotiation (ALPN) is a TLS extension that allows iroh peers to agree on which application protocol to use over a QUIC connection, ensuring that both endpoints handle streams with the correct protocol handler.**

In the n0-computer/iroh framework, ALPN serves as the foundation for protocol selection in QUIC connections. Understanding how to configure and use ALPN in iroh QUIC connections is essential for building robust peer-to-peer applications that can negotiate compatible protocols during the TLS handshake.

## What is ALPN and Why It Matters in iroh

ALPN is a TLS extension that lets two endpoints agree on which application-specific protocol will run over a connection. In iroh, it serves three critical functions:

1. **Protocol selection** – Both peers must present matching ALPN identifiers; otherwise, the connection is rejected.
2. **Endpoint configuration** – You must declare which ALPN strings an endpoint accepts for inbound connections.
3. **Connection initiation** – The client specifies which ALPN to request when connecting to a remote peer.

### Protocol Selection and Validation

According to the iroh source code in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs), the ALPN identifier acts as a gatekeeper. If the ALPN values do not match between connecting peers, the connection establishment fails immediately. This guarantees that streams opened later are interpreted by the correct handler.

### The ALPN Data Format

ALPN values are opaque byte strings (`&[u8]`). While iroh does not enforce a naming scheme, the convention is to prefix identifiers with `/iroh/`, such as `/iroh/echo/1`.

## Configuring ALPN on the Server Side

To accept incoming connections, an iroh endpoint must explicitly register the ALPN strings it supports. In [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs), the builder pattern provides the `alpns()` method for this purpose. If the ALPN list is empty, the endpoint can still initiate outbound connections, but it cannot accept any inbound ones.

```rust
use iroh::{endpoint, Endpoint};
use iroh::endpoint::presets;

// Define the protocol identifier for this service
const MY_ALPN: &[u8] = b"/iroh/my-protocol/1";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Build an endpoint that will accept connections using MY_ALPN
    let ep = Endpoint::builder(presets::N0)
        .alpns(vec![MY_ALPN.to_vec()])   // <‑‑ register the ALPN we accept
        .bind()
        .await?;

    // Wait for an inbound connection
    let conn = ep.accept().await?.await?;

    // Verify that the negotiated ALPN matches what we expect
    assert_eq!(conn.alpn(), MY_ALPN);

    // Use the connection (e.g., open a stream) …
    let (mut send, mut recv) = conn.open_bi().await?;
    // …
    Ok(())
}

```

## Initiating Connections with ALPN

When connecting to a remote peer, the client specifies the desired ALPN using the `connect()` method. As implemented in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs), this ALPN is sent during the QUIC handshake and must match one advertised by the remote endpoint.

```rust
use iroh::{Endpoint, EndpointAddr};
use iroh::endpoint::presets;

const MY_ALPN: &[u8] = b"/iroh/my-protocol/1";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a client endpoint (no ALPN list needed for outgoing)
    let ep = Endpoint::builder(presets::N0).bind().await?;

    // The remote endpoint’s address (could be a relay URL or direct address)
    let remote: EndpointAddr = /* obtain address */;
    
    // Initiate the connection, passing the ALPN we want to use
    let conn = ep.connect(remote, MY_ALPN).await?;
    
    // The remote side will only accept this if it advertised MY_ALPN
    assert_eq!(conn.alpn(), MY_ALPN);

    // Use the connection …
    let (mut send, mut recv) = conn.open_bi().await?;
    // …
    Ok(())
}

```

## Supporting Multiple Protocol Versions

For backward compatibility, iroh allows advertising additional ALPN identifiers beyond the primary one. The `connect_with_opts()` function accepts a `ConnectOptions` struct that can include extra ALPN strings. This is useful when supporting older clients that use a previous protocol identifier.

```rust
use iroh::{Endpoint, ConnectOptions};
use iroh::endpoint::presets;

const NEW_ALPN: &[u8] = b"/iroh/my-protocol/2";
const LEGACY_ALPN: &[u8] = b"/iroh/my-protocol/1";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ep = Endpoint::builder(presets::N0).bind().await?;
    let remote = /* obtain address */;

    let opts = ConnectOptions::new()
        .alpns(vec![LEGACY_ALPN.to_vec()]); // advertise the legacy identifier

    let conn = ep.connect_with_opts(remote, NEW_ALPN, opts).await?;
    
    // The remote endpoint will negotiate either NEW_ALPN or LEGACY_ALPN
    // conn.alpn() reveals which identifier was finally chosen
    Ok(())
}

```

The remote endpoint will negotiate either `NEW_ALPN` or `LEGACY_ALPN`; the resulting connection’s `conn.alpn()` method reveals which identifier was finally chosen.

## Summary

- **ALPN prevents protocol mismatches** by requiring both iroh peers to present identical protocol identifiers before a QUIC connection is established.
- **Server endpoints must register ALPNs** using the `alpns()` builder method in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) to accept inbound connections.
- **Clients specify ALPN during connection** using `connect()` or `connect_with_opts()` as defined in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs).
- **Use `ConnectOptions`** to advertise multiple protocol versions for backward compatibility during the handshake.

## Frequently Asked Questions

### What happens if the ALPN doesn't match between iroh peers?

The connection is rejected during the TLS handshake. According to the implementation in [`iroh/src/lib.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs), both endpoints must agree on the same ALPN identifier; otherwise, the connection establishment fails immediately, preventing protocol confusion.

### Can an iroh endpoint accept multiple different ALPNs?

Yes. The `alpns()` method in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) accepts a `Vec<Vec<u8>>`, allowing you to register multiple protocol identifiers. The endpoint will accept connections that match any of the registered ALPNs.

### Is the /iroh/ prefix required for ALPN identifiers in iroh?

No. ALPN values are opaque byte strings (`&[u8]`), and iroh does not impose any naming constraints. The `/iroh/` prefix is merely a convention used within the ecosystem to avoid collisions.

### How do I verify which ALPN was negotiated in an iroh connection?

Call the `alpn()` method on the `Connection` object after the handshake completes. This returns the byte string that was agreed upon, which you can assert against your expected constants to ensure the correct protocol handler is used.