---
title: Troubleshooting
description: Dials that hang, streams that never end, "no address", "no relay", and other first-hour failures.
icon: life-ring
---

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:

1. **Check what path could possibly work.** Two peers behind different NATs
   with only `ip:` addresses in their `EndpointAddr` have no path at all. Give
   the receiving side a relay: `iroh.WithRelayMode(relay.ModeDefault())`, then
   `ep.Online(ctx)` before publishing its address.
2. **Check the address was minted after the relay connected.** `ep.Addr()`
   taken before `Online` may have no relay URL. That is the most common way a
   ticket ends up unreachable. Use `ep.WatchAddr()` if you re-publish.
3. **Check the ALPN strings match exactly.** They are compared byte for byte.
4. **Check the clocks and the network.** `ep.NetReport()` (with
   `iroh.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](relays-and-discovery).

## The connection works but is slow

Check whether you are still on the relay path:

```go
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](architecture).

## 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(...)` at `Bind`, or let
  `iroh.NewRouter` do it. An endpoint with no ALPNs accepts nothing.
- Keep the `Router` alive. `defer router.Shutdown(ctx)` in a `main` that
  returns immediately shuts down the server you just started.
- Do not run your own `Accept` loop alongside a `Router`.

## 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](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](https://docs.iroh.computer) and in
[n0-computer/iroh](https://github.com/n0-computer/iroh). Go API questions
belong in [github.com/tmc/go-iroh](https://github.com/tmc/go-iroh).
