---
title: Relays and discovery
description: Reach a peer you only know by public key — relay modes, DNS and pkarr lookup, mDNS, and self-hosting.
icon: signal-stream
---

A direct IP address only works when both peers can route to each other. This
page covers the two mechanisms that make an endpoint ID enough: relays (a path
that always works) and discovery (turning an ID into an address).

## Relay modes

Relays are configured with `iroh.WithRelayMode` and the `relay` package:

| Mode | Use when |
| --- | --- |
| `relay.ModeDefault()` | you want the number0 production relays |
| `relay.ModeStaging()` | you are testing against number0 staging |
| `relay.ModeCustomURLs(urls...)` | you run your own relays |
| `relay.ModeCustom(m)` | you need per-relay `relay.Config` (QUIC settings) |
| `relay.ModeDisabled()` | direct paths only; no relay fallback |

```go
ep, err := iroh.Bind(ctx, iroh.WithRelayMode(relay.ModeDefault()))
if err != nil {
	return err
}
if err := ep.Online(ctx); err != nil { // wait for a home relay connection
	return err
}
fmt.Println(ep.Addr().RelayURLs())
```

`Endpoint.Online` blocks until a home relay connection exists. Until then your
`Addr()` may have no relay URL in it, so a ticket minted too early is not
reachable. `Endpoint.HomeRelayStatus()` observes the connection, and
`InsertRelay` / `RemoveRelay` adjust the map at runtime.

When several relays are configured, `relay.RankByLatency` with a
`relay.Prober` (`relay.HTTPConnectProber`) measures them; with `WithNetReport()`
the endpoint's own `NetReport().PreferredRelay` reports the relay with the best
recent latency, chosen with hysteresis.

The relay is a fallback path, not the destination — see
[How go-iroh works](architecture) for the upgrade to a direct path.

## Discovery: from ID to address

Discovery is optional. If you already have an address — from a ticket, a config
file, or a previous session — you can dial without any lookup service.

The abstraction is `iroh.AddressLookupServices`: a registry with publisher and
resolver halves, registered on an endpoint with `iroh.WithAddressLookup`. It
"publishes the endpoint's own info to every publisher and merges resolver
streams" (its doc comment). Resolution is an iterator of `Item` values, each
tagged with the provenance of the service that produced it.

Shipped services:

| Service | Constructors | Provenance |
| --- | --- | --- |
| DNS | `NewDNSAddressLookup(origin, resolver)`, `N0DNSAddressLookup(resolver)` | `DNSProvenance` |
| pkarr | `NewPkarrPublisher` / `NewPkarrResolver`, `N0PkarrPublisher` / `N0PkarrResolver` | `PkarrProvenance` |
| Static | `StaticLookupFromAddrs(addrs...)` | `static_lookup` |
| Memory | `NewMemoryLookup()` + `AddEndpointAddr` | `memory_lookup` |
| mDNS | `mdns.New(id, opts...)` in `iroh/mdns` | local link |

The `N0…` constructors use the number0 production endpoints:
`iroh.N0DNSPkarrRelayProd` is `https://dns.iroh.link/pkarr`, with
`N0DNSPkarrRelayStaging` alongside it, and the DNS lookup uses
`dns.N0DNSEndpointOriginProd`.

A typical internet-facing setup publishes with pkarr and resolves with both
pkarr and DNS:

```go
publisher, err := iroh.N0PkarrPublisher(sk, nil)
if err != nil {
	return err
}
resolver, err := iroh.N0PkarrResolver(nil)
if err != nil {
	return err
}
var services iroh.AddressLookupServices
services.AddPublisher(publisher)
services.AddResolver(resolver)
services.AddResolver(iroh.N0DNSAddressLookup(nil))

ep, err := iroh.Bind(ctx,
	iroh.WithSecretKey(sk),
	iroh.WithAddressLookup(&services),
)
```

Publishing is what makes a bare `netaddr.NewEndpointAddr(id)` dialable by
someone who has only your ID.

pkarr records are signed with your endpoint's secret key, so a pkarr relay
distributes them without being trusted to be honest about their contents. The
underlying codec is the `pkarr` package; the DNS/TXT encoding is in `dns`.

For LAN-only peers, `iroh/mdns` implements both halves over multicast DNS
(default service name `irohv1`) and is not built for `js`.

## Self-hosting

The core module ships both servers, so you do not need number0
infrastructure:

- `cmd/iroh-relay` — a self-hostable relay server; the `relayserver` package is
  the embeddable form.
- `cmd/iroh-dns-server` — the pkarr HTTP and DNS surfaces; the `dnsserver`
  package is the embeddable form.

Point endpoints at them with `relay.ModeCustomURLs(...)` and
`NewPkarrPublisher` / `NewPkarrResolver` / `NewDNSAddressLookup` with your own
URL and origin. See [Commands](commands) for flags and a health check.

## Next steps

- [Commands](commands) — running your own relay and DNS server.
- [Endpoints and connections](endpoints-and-connections) — dialing once the
  address resolves.
- [Troubleshooting](troubleshooting) — "no address", "no relay", and dials that
  never connect.
