juanfont/headscale · warning · HTTPError

ErrSSHDstNodeNotFound

ErrSSHDstNodeNotFound

Error message

dst node not found

What it means

Returned by the SSH action handler when the dst_node_id in the URL does not match any node in the state store (GetNodeByID miss). The dst node is the tailscaled that would accept the incoming SSH connection; if it does not exist, no SSH verdict can be produced.

Source

Thrown at hscontrol/noise.go:402

			err,
		))

		return
	}

	// Authenticate the Noise session: the destination node is the
	// tailscaled instance asking us whether to permit an incoming SSH
	// connection, so its Noise session must belong to dst. Without this
	// check any unauthenticated client could open a Noise tunnel with a
	// throwaway machine key and pollute lastSSHAuth for arbitrary
	// (src, dst) pairs, defeating SSH check-mode's stolen-key
	// protections.
	dstNode, ok := ns.headscale.state.GetNodeByID(dstNodeID)
	if !ok {
		httpError(writer, NewHTTPError(
			http.StatusNotFound,
			"dst node not found",
			fmt.Errorf("%w: %d", ErrSSHDstNodeNotFound, dstNodeID),
		))

		return
	}

	if dstNode.MachineKey() != ns.machineKey {
		httpError(writer, NewHTTPError(
			http.StatusUnauthorized,
			"machine key does not match dst node",
			fmt.Errorf(
				"%w: machine key %s, dst node %d",
				ErrSSHMachineKeyMismatch, ns.machineKey.ShortString(), dstNodeID,
			),
		))

		return
	}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Verify the dst node still exists: headscale nodes list
  2. If it was deleted, let the client re-run 'tailscale ssh' to start a fresh session
  3. Check for ID confusion between src and dst when reading logs
Defensive patterns

Strategy: validation

Validate before calling

if _, ok := h.state.GetNodeByID(dstNodeID); !ok {
    return fmt.Errorf("dst node %d does not exist; it may have been deleted — retry the SSH session", dstNodeID)
}

Prevention

When it happens

Trigger: An SSH check-mode request naming a dst node that was deleted, expired, or never existed; a stale SSHAction URL from a client cached across node removal.

Common situations: Node removed with 'headscale nodes delete' while an SSH check session was pending; node IDs reused/shifted after database changes; crafted requests probing the endpoint.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/4b46a8ebd2c44294. Report an issue: GitHub.