decolua/9router · error · Error

Health check timeout after ${HEALTH_CHECK.timeoutMs}ms

Error message

Health check timeout after ${HEALTH_CHECK.timeoutMs}ms

What it means

waitForHealth() polls the tunnel URL every HEALTH_CHECK.intervalMs for up to HEALTH_CHECK.timeoutMs; if probeUrlAlive() never returns true within that window it throws 'Health check timeout after <N>ms'. It means the tunnel was enabled but the public URL never answered HTTP within the allotted time.

Source

Thrown at src/lib/tunnel/cloudflare/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)

Solutions

  1. Verify the local service is actually listening on the configured port (curl http://localhost:20128/dashboard).
  2. Check cloudflared logs for edge connection or authentication failures.
  3. Increase HEALTH_CHECK.timeoutMs / adjust intervalMs in src/lib/tunnel/cloudflare/healthCheck.js if the network is slow.
  4. Confirm outbound HTTPS to Cloudflare edge (region1/region2.v2.argotunnel.com, port 7844 or 443) is not blocked.
  5. Retry enableTunnel() once connectivity is restored.
Defensive patterns

Strategy: retry

Validate before calling

// Preflight: confirm local service is up before enabling the tunnel
const res = await fetch(`http://localhost:${port}/dashboard`).catch(() => null);
if (!res || !res.ok) throw new Error(`Local service not listening on ${port}`);

Type guard

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

Try / catch

try {
  await waitForHealth(url, token);
} catch (e) {
  if (isHealthTimeout(e)) {
    // retry once, or surface actionable message about local port / egress
    return retryEnableOnce();
  }
  throw e;
}

Prevention

When it happens

Trigger: enableTunnel() creates a cloudflared tunnel whose public URL stays unreachable for the whole timeout window: cloudflared failed to connect to Cloudflare edge, the local service on localPort isn't listening, network egress to Cloudflare is blocked, or the tunnel binary crashed after startup.

Common situations: Local app not started yet (port 20128 not listening), firewall/proxy blocking outbound connections to Cloudflare, cloudflared binary failing auth or quota limits, DNS propagation delay for a newly named tunnel, or an overly small HEALTH_CHECK.timeoutMs for slow networks.

Understand the failure class

Related errors


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