decolua/9router · error · Error

Health check timeout after

Error message

Health check timeout after ${HEALTH_CHECK.timeoutMs}ms

What it means

The tailscale waitForHealth() polls the funnel URL until probeUrlAlive() succeeds, bounded by HEALTH_CHECK.timeoutMs; if the funnel never becomes reachable in time it throws 'Health check timeout after <N>ms'. It indicates the tailscale funnel was started but its public URL never served HTTP within the window.

Solutions

  1. Verify the local service answers on the configured port before enabling the funnel.
  2. Run `tailscale status` and `tailscale funnel status` to confirm the daemon is connected and the funnel is active.
  3. Re-authenticate tailscale (`tailscale login` / `tailscale up`) if the session expired.
  4. Confirm funnel is permitted for your tailnet (tailscale admin console / ACLs) and that MagicDNS is enabled.
  5. Increase HEALTH_CHECK.timeoutMs in src/lib/tunnel/tailscale/healthCheck.js on slow networks, then retry.
Defensive patterns

Strategy: retry

Validate before calling

// Preflight: is tailscale ready and is the local app up?
execSync("tailscale status", { stdio: "ignore" }); // throws if not connected
const res = await fetch(`http://localhost:${port}`).catch(() => null);
if (!res?.ok) throw new Error("local app not ready");

Type guard

const isHealthTimeout = (e) => e instanceof Error && /Health check timeout after \d+ms/.test(e.message);

Try / catch

try {
  await waitForHealth(funnelUrl, token);
} catch (e) {
  if (isHealthTimeout(e)) {
    console.error("Funnel never became healthy — check `tailscale status`, auth, and ACLs");
    return retryAfterCheck();
  }
  throw e;
}

Prevention

When it happens

Trigger: enableTailscale() completes `tailscale funnel` spawn but the URL stays dead for the full timeout: tailscale not logged in / expired auth, funnel disabled on the tailnet (ACL/policy), tailnet DNS not propagated, the local port not listening, or tailscaled disconnected from the DERP/coordination server.

Common situations: Fresh tailscale install where `tailscale up` was never run, corporate networks blocking tailscale UDP, funnel feature not enabled for the tailnet, slow MagicDNS propagation for a new hostname, or the local app (port 20128) not yet started.

Understand the failure class

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/d6e9cd0fc6d751d8. Report an issue: GitHub.

Appendix: source

Thrown at src/lib/tunnel/tailscale/healthCheck.js:28

  try {
    const res = await fetch(`${url}/api/health`, {
      signal: AbortSignal.timeout(HEALTH_CHECK.fetchTimeoutMs),
    });
    return res.ok;
  } catch {
    return false;
  }
}

export async function waitForHealth(url, cancelToken = { cancelled: false }) {
  const start = Date.now();
  while (Date.now() - start < HEALTH_CHECK.timeoutMs) {
    if (cancelToken.cancelled) throw new Error("cancelled");
    if (await probeUrlAlive(url)) return true;
    await new Promise((r) => setTimeout(r, HEALTH_CHECK.intervalMs));
  }
  throw new Error(`Health check timeout after ${HEALTH_CHECK.timeoutMs}ms`);
}

View on GitHub (pinned to 90b52e06ff)