headroomlabs-ai/headroom · error · Error

Remote Headroom proxy not reachable at ${explicitUrl}. Ensur

Error message

Remote Headroom proxy not reachable at ${explicitUrl}. Ensure the proxy is running at that address.

What it means

A non-local proxyUrl (hostname other than 127.0.0.1/localhost) was configured, the probe could not reach it, and the manager refuses to auto-start anything for remote URLs by design — auto-start only ever spawns a local subprocess. The error tells you the remote proxy must be running and reachable before OpenClaw connects.

Source

Thrown at plugins/openclaw/src/proxy-manager.ts:108

      if (probe.reachable && probe.isHeadroom) {
        this.proxyUrl = url;
        this.logger.info(`Headroom proxy already running at ${url}`);
        return url;
      }
    }

    if (explicitUrl) {
      const explicitProbe = probeByUrl.get(explicitUrl);
      if (explicitProbe?.reachable && !explicitProbe.isHeadroom) {
        throw new Error(
          `Service reachable at ${explicitUrl}, but it does not appear to be a Headroom proxy (${explicitProbe.reason ?? "unknown service"}).`,
        );
      }
    }

    // Remote URLs are connect-only — never auto-start a subprocess for them
    if (explicitUrl && !isLocalProxyUrl(explicitUrl)) {
      throw new Error(
        `Remote Headroom proxy not reachable at ${explicitUrl}. Ensure the proxy is running at that address.`,
      );
    }

    // Auto-start is only available for local proxies
    if (this.config.autoStart === true) {
      const startupUrl = explicitUrl ?? defaultCandidates[0];
      const startupProbe = probeByUrl.get(startupUrl);
      if (startupProbe?.reachable && !startupProbe.isHeadroom) {
        throw new Error(
          `Cannot auto-start Headroom at ${startupUrl}: port is in use by a non-Headroom service (${startupProbe.reason ?? "unknown service"}).`,
        );
      }

      this.logger.info(
        `No Headroom proxy detected${explicitUrl ? ` at ${startupUrl}` : " on default local endpoints"}; attempting to auto-start...`,
      );
      await this.startHeadroomProxy(startupUrl, port);

View on GitHub (pinned to 322425c43b)

Solutions

  1. Start (or restart) the Headroom proxy on the remote host and confirm it binds an address reachable from the client (not just remote-loopback): headroom proxy --host 0.0.0.0 --port 8787
  2. Verify reachability from the client machine: curl http://<host>:8787 — fix DNS, firewall, or VPN issues it reveals
  3. If the proxy is actually local, use http://127.0.0.1:<port> so the manager can auto-start it
  4. If the remote service starts slowly, delay client startup until it is up (health check before constructing the manager)

Example fix

# on the remote host — before: bound to loopback only
headroom proxy --host 127.0.0.1 --port 8787

# after: bind so clients can reach it
headroom proxy --host 0.0.0.0 --port 8787
Defensive patterns

Strategy: validation

Validate before calling

async function remoteProxyUp(url: string, timeoutMs = 3000): Promise<boolean> {
  const ctrl = new AbortController();
  const t = setTimeout(() => ctrl.abort(), timeoutMs);
  try {
    await fetch(url, { signal: ctrl.signal });
    return true; // reachable at HTTP level; identity checked by the manager
  } catch {
    return false;
  } finally {
    clearTimeout(t);
  }
}

const url = "http://proxy.internal:8787";
if (!await remoteProxyUp(url)) {
  throw new Error(`Remote proxy ${url} not up yet; wait for it before starting the client`);

Type guard

import { isLocalProxyUrl } from "./proxy-manager.js";

function requiresRunningProxy(proxyUrl: string): boolean {
  // Remote URLs are connect-only: never expect auto-start for them
  return !isLocalProxyUrl(proxyUrl);
}

Try / catch

try {
  await manager.resolveProxyUrl();
} catch (e) {
  if (e instanceof Error && e.message.includes("Remote Headroom proxy not reachable")) {
    // infrastructure issue: surface to ops, do not retry in-process
    throw new Error(`Headroom proxy host is down or unreachable — check the remote service`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Configuring proxyUrl like http://proxy.internal:8787 or http://10.0.0.5:8787 when nothing is listening there, when a firewall blocks it, when the hostname does not resolve, or when the remote Headroom process is not started yet.

Common situations: Shared/team Headroom proxy on a remote host that is down; Docker/K8s service name misspelled or not yet up when the client starts; VPN or network policy blocking the port; remote Headroom bound to 127.0.0.1 instead of 0.0.0.0.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/db3820f7541d0820. Report an issue: GitHub.