decolua/9router · error · Error

Headroom URL must use http or https

Error message

Headroom URL must use http or https

What it means

getTargetBase resolves the Headroom upstream base URL from settings (headroomUrl or DEFAULT_HEADROOM_URL) and validates that its protocol is http: or https: before building a proxy target. Any other scheme makes the throw. This protects the catch-all proxy route from targets it cannot safely fetch.

Source

Thrown at src/app/api/headroom/proxy/[...path]/route.js:26

  "connection",
  "keep-alive",
  "proxy-authenticate",
  "proxy-authorization",
  "te",
  "trailer",
  "transfer-encoding",
  "upgrade",
]);

const DASHBOARD_PREFIX = "/api/headroom/proxy";
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);

async function getTargetBase() {
  const settings = await getSettings();
  const url = settings.headroomUrl || DEFAULT_HEADROOM_URL;
  const target = new URL(url);
  if (!["http:", "https:"].includes(target.protocol)) {
    throw new Error("Headroom URL must use http or https");
  }
  return target;
}

function buildTargetUrl(base, path, search) {
  const target = new URL(base);
  target.pathname = `/${path.join("/")}`;
  target.search = search;
  return target;
}

function forwardedHeaders(request, target) {
  const headers = new Headers(request.headers);
  for (const header of headers.keys()) {
    if (HOP_BY_HOP_HEADERS.has(header.toLowerCase())) headers.delete(header);
  }
  headers.delete("host");
  // Never leak viewer credentials to a non-loopback Headroom host

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Open dashboard settings and set the Headroom URL to an http:// or https:// address, e.g. 'http://localhost:9000'.
  2. Correct settings.headroomUrl in the persisted settings store if edited directly.
  3. Ensure the URL includes the '//' after the scheme so it parses correctly.

Example fix

// before
headroomUrl: "ws://localhost:9000"
// after
headroomUrl: "http://localhost:9000"
Defensive patterns

Strategy: validation

Validate before calling

const u = new URL(headroomUrl);
if (!["http:", "https:"].includes(u.protocol)) {
  throw new Error("headroomUrl must start with http:// or https://");
}

Type guard

const isHttpUrl = (v) => {
  try { return ["http:", "https:"].includes(new URL(v).protocol); }
  catch { return false; }
};

Try / catch

try {
  await fetchProxy(path);
} catch (e) {
  if (e.message === "Headroom URL must use http or https") {
    console.error("Set headroomUrl to an http(s) URL in settings");
  } else throw e;
}

Prevention

When it happens

Trigger: A request to /api/headroom/proxy/* when settings.headroomUrl is set to a URL with a non-http(s) scheme (e.g. 'ws://headroom:9930', 'file://...') or is otherwise malformed into an unexpected scheme by the URL parser.

Common situations: Operator stored a websocket or bare hostname (e.g. 'headroom.local:9000', which new URL() can mis-parse) in the headroom settings, or migrated config from another tool that used a different scheme.

Related errors


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