denoland/deno · error · NodeError

ERR_PROXY_INVALID_CONFIG

ERR_PROXY_INVALID_CONFIG

Error message

Invalid proxy URL for ${kind}: must be a string

What it means

In strict mode (an explicit proxyEnv object passed to http.setGlobalProxyFromEnv(env) or new http.Agent({ proxyEnv })), parseProxyUrl throws ERR_PROXY_INVALID_CONFIG 'must be a string' when the value for http_proxy/https_proxy/HTTPS_PROXY-style keys is present but not a string (number, boolean, object).

Source

Thrown at ext/node/polyfills/_http_proxy.js:110

function readEnvKey(env, keys) {
  for (let i = 0; i < keys.length; i++) {
    const k = keys[i];
    if (env[k] !== undefined && env[k] !== "") {
      return env[k];
    }
  }
  return undefined;
}

const CRLF_RE = new SafeRegExp(/[\r\n]/);

function parseProxyUrl(raw, kind, mode) {
  if (raw === undefined || raw === null || raw === "") return null;
  if (typeof raw !== "string") {
    if (mode === "env") {
      throw new TypeError(`Invalid URL: ${raw}`);
    }
    throw new ERR_PROXY_INVALID_CONFIG(
      `Invalid proxy URL for ${kind}: must be a string`,
    );
  }
  // CRLF injection guard - check raw string before URL parsing strips them.
  // Matches Node's CRLF rejection in the proxy URL validator. We surface this
  // even for env-derived URLs so the auth tests get the expected error class.
  if (RegExpPrototypeExec(CRLF_RE, raw) !== null) {
    throw new ERR_PROXY_INVALID_CONFIG(`Invalid proxy URL: ${raw}`);
  }
  let url;
  try {
    url = new URL(raw);
  } catch {
    if (mode === "env") {
      throw new TypeError(`Invalid URL: ${raw}`);
    }
    throw new ERR_PROXY_INVALID_CONFIG(`Invalid proxy URL: ${raw}`);
  }

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Pass URL strings in proxyEnv objects: { https_proxy: 'http://proxy:8080' }
  2. Validate your config object before handing it to the API (see validation below)
  3. Omit the key entirely (or leave it undefined) when no proxy should be configured

Example fix

// before
http.setGlobalProxyFromEnv({ http_proxy: config.port }); // number -> throws

// after
http.setGlobalProxyFromEnv({ http_proxy: `http://${config.host}:${config.port}` });
Defensive patterns

Strategy: type-guard

Validate before calling

function normalizeProxyEnv(env: Record<string, unknown>) {
  const out: Record<string, string> = {};
  for (const [k, v] of Object.entries(env)) {
    if (v === undefined || v === null || v === '') continue;
    out[k] = String(v); // coerce numbers/booleans to strings
  }
  return out;
}

Type guard

function isStringProxyEnv(env: unknown): env is Record<string, string> {
  return !!env && typeof env === 'object' && !Array.isArray(env) &&
    Object.values(env).every((v) => v === undefined || typeof v === 'string');
}

Try / catch

try {
  http.setGlobalProxyFromEnv(proxyEnv);
} catch (e) {
  if (e?.code === 'ERR_PROXY_INVALID_CONFIG' && /must be a string/.test(e.message)) {
    http.setGlobalProxyFromEnv(normalizeProxyEnv(proxyEnv)); // retry coerced
  } else throw e;
}

Prevention

When it happens

Trigger: setGlobalProxyFromEnv({ http_proxy: 123 }) or new Agent({ proxyEnv: { https_proxy: true } }); note undefined/null/'' are treated as unset and return null instead of throwing.

Common situations: Proxy config parsed from JSON/YAML where the port or whole URL ended up as a number/boolean; config templating that interpolates values without quoting.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/4be7d864d0ab09dc. Report an issue: GitHub.