ipfs/kubo · error

GetClosestPeers not supported: WAN DHT is not available

Error message

GetClosestPeers not supported: WAN DHT is not available

What it means

This error comes from the HTTP Routing API's GetClosestPeers handler in Kubo. When a node's DHT client is a dual.DHT (separate WAN and LAN DHT instances), the public HTTP routing API only delegates to the WAN DHT, since LAN DHT holds private-network peers that must not be exposed over a public API. If the WAN half of the dual DHT is nil (never initialized), the endpoint cannot serve the request and returns this error instead.

Solutions

  1. Ensure the node is configured to run a public (WAN) DHT, e.g. set `ipfs config Routing.Type dhtclient` (or `dhtserver`) and restart the daemon so the dual DHT initializes its WAN instance.
  2. If the node must stay in a private network, do not expose the public HTTP Routing API from it; point clients at a node that has a WAN DHT.
  3. Verify at runtime that the node has a WAN DHT before serving routing requests, e.g. by checking the node's routing configuration or by catching this error and returning a clearer 4xx/5xx to the API caller.

Example fix

// Node configuration (shell), ensure WAN DHT exists:
// before (private/none routing)
ipfs config Routing.Type none
// after
ipfs config Routing.Type dhtclient

// Client side, guard the call:
peers, err := rt.GetClosestPeers(ctx, key)
if err != nil && strings.Contains(err.Error(), "WAN DHT is not available") {
    // fall back to a different routing node
}
Defensive patterns

Strategy: fallback

Validate before calling

// Confirm the node runs a public DHT before calling the endpoint:
// ipfs config Routing.Type   -> expect 'dhtclient' or 'dhtserver'
// (a nil WAN DHT usually means private-network or routing=none setups)

Type guard

func wanDHTAvailable(n *core.IpfsNode) bool {
    d, ok := n.DHTClient.(*dual.DHT)
    return ok && d.WAN != nil
}

Try / catch

peers, err := rt.GetClosestPeers(ctx, key)
if err != nil {
    if strings.Contains(err.Error(), "WAN DHT is not available") {
        peers, err = fallbackRouter.GetClosestPeers(ctx, key)
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Calling the HTTP Routing API (`routing/v1`) GetClosestPeers endpoint on a node whose DHTClient is *dual.DHT with dhtClient.WAN == nil, i.e. the node was configured without a WAN/public DHT (e.g. `Routing.Type` not creating a public DHT, or running in a private-network/LAN-only setup).

Common situations: Operators running nodes in private networks (Libp2p private networks with a swarm key) where the WAN DHT is intentionally disabled; misconfigured Routing.Type (e.g. 'none' or custom) leading to a dual DHT with no WAN half; exposing the routing API on a node that was never meant to be a public routing provider.

Related errors


AI-assisted analysis of ipfs/kubo@329838acdf (2026-09-03). Data as JSON: /api/errors/a7aec96b85ad692e. Report an issue: GitHub.

Appendix: source

Thrown at core/corehttp/routing.go:123

	// Per the spec, if the peer ID is empty, we should use self.
	if key == cid.Undef {
		return nil, errors.New("GetClosestPeers key is undefined")
	}

	keyStr := string(key.Hash())
	var peers []peer.ID
	var err error

	if r.n.DHTClient == nil {
		return nil, fmt.Errorf("GetClosestPeers not supported: DHT is not available")
	}

	switch dhtClient := r.n.DHTClient.(type) {
	case *dual.DHT:
		// Only use WAN DHT for public HTTP Routing API.
		// LAN DHT contains private network peers that should not be exposed publicly.
		if dhtClient.WAN == nil {
			return nil, fmt.Errorf("GetClosestPeers not supported: WAN DHT is not available")
		}
		peers, err = dhtClient.WAN.GetClosestPeers(ctx, keyStr)
	case *fullrt.FullRT:
		peers, err = dhtClient.GetClosestPeers(ctx, keyStr)
	case *dht.IpfsDHT:
		peers, err = dhtClient.GetClosestPeers(ctx, keyStr)
	default:
		return nil, fmt.Errorf("GetClosestPeers not supported for DHT type %T", r.n.DHTClient)
	}

	// A lookup cut short by the deadline returns the closest peers found so far
	// along with the context error. The HTTP routing server caps every request
	// (server.DefaultRoutingTimeout), so on a slow query this is the difference
	// between a useful answer and a 500.
	if err != nil && len(peers) == 0 {
		return nil, err
	}

View on GitHub (pinned to 329838acdf)