Table of Contents
Troubleshooting
Dials that hang, streams that never end, “no address”, “no relay”, and other first-hour failures.
Symptom-first. Each entry names what to check and which page explains the mechanism.
Connect fails immediately
| Error | Meaning | Fix |
|---|---|---|
iroh.ErrNoAddress |
the EndpointAddr had no path and no lookup service could resolve one |
add WithIP/WithRelayURL to the address, or register a resolver with WithAddressLookup |
iroh.ErrNoRelay |
a relay path was required but no relay was usable | check WithRelayMode; relay.ModeDisabled() removes relays entirely |
iroh.ErrSelfConnect |
you dialed your own endpoint ID | you are reusing one secret key for both sides |
iroh.ErrConnectRejected / ErrHandshakeRejected |
the peer refused | usually an ALPN mismatch — the dialed ALPN must be one the peer accepts |
iroh.ErrEndpointClosed |
the endpoint was shut down | a defer ep.Shutdown(ctx) that ran earlier than you expected |
iroh.ErrEndpointAcceptLoopInUse |
two accept loops on one endpoint | a Router already owns accepts; do not also call Accept |
Connect hangs, then times out
A single Connect call is bounded by iroh.ConnectTimeout (10s) when no
reachable address succeeds. If you consistently hit it:
- Check what path could possibly work. Two peers behind different NATs
with only
ip:addresses in theirEndpointAddrhave no path at all. Give the receiving side a relay:iroh.WithRelayMode(relay.ModeDefault()), thenep.Online(ctx)before publishing its address. - Check the address was minted after the relay connected.
ep.Addr()taken beforeOnlinemay have no relay URL. That is the most common way a ticket ends up unreachable. Useep.WatchAddr()if you re-publish. - Check the ALPN strings match exactly. They are compared byte for byte.
- Check the clocks and the network.
ep.NetReport()(withiroh.WithNetReport()) reports whether IPv4/IPv6 QAD round trips completed and whether your observed address varies by destination — the symmetric-NAT signal that predicts hole-punching failure.
See Relays and discovery.
The connection works but is slow
Check whether you are still on the relay path:
for _, p := range conn.Paths() {
fmt.Println(p.ID, p.Validated, p.Addr, p.RTT)
}
A relay path is expected at connection setup; the direct path appears once hole
punching succeeds. If a direct path never appears, the NAT topology may not
permit one — that is a working configuration, just a slower one. WatchPaths
lets you observe the upgrade instead of sampling for it. Background:
How go-iroh works.
A stream never returns EOF
The reader is waiting for a half-close that never came. On a bidirectional
stream, Close() closes your send side; the peer’s io.Copy or ReadAll
cannot return until you call it. In the echo tutorial, the client writes, calls
s.Close(), and only then reads.
Symmetrically, a handler that returns without closing its stream leaves the client blocked.
Nothing accepts my connections
- Register the ALPN: either
iroh.WithALPNs(...)atBind, or letiroh.NewRouterdo it. An endpoint with no ALPNs accepts nothing. - Keep the
Routeralive.defer router.Shutdown(ctx)in amainthat returns immediately shuts down the server you just started. - Do not run your own
Acceptloop alongside aRouter.
Browser (js/wasm) builds compile but do not connect
Browser endpoints are relay-only: direct UDP QUIC, direct paths, and NAT
traversal are not available in browser WebAssembly. Build with
iroh.WithoutIPTransports() and a relay mode, and expect relay latency. See
the wasmrelay experiment module for working demos.
go get or go build fails on the Go version
go-iroh’s go.mod declares go 1.26. Check go version, and check your
go.mod’s own go directive is not older than what the toolchain requires.
Tests hang instead of failing
The live Rust interop gates are opt-in and depend on external binaries and network topology; a gate that cannot run should skip, not hang. If a local test hangs, suspect the half-close pattern above in the test itself. See Testing and interop.
Still stuck
The protocol-level questions — why hole punching failed, what the relay does, how discovery records are structured — are answered upstream at docs.iroh.computer and in n0-computer/iroh. Go API questions belong in github.com/tmc/go-iroh.