angular/angular-cli · error · Error

Header "forwarded" proto parameter must be either "http" or

Error message

Header "forwarded" proto parameter must be either "http" or "https".

What it means

Angular SSR validates the `forwarded` header's `proto` parameter against `/^https?$/i` when the header is trusted. If the parameter is missing-or-invalid (anything other than http/https, case-insensitive), the request is rejected. This guards against proxy-supplied `forwarded` headers containing malformed or malicious protocol values used for open-redirect or scheme-confusion attacks.

Source

Thrown at packages/angular/ssr/src/utils/validation.ts:209

  allowedHosts: ReadonlySet<string>,
  disableHostCheck: boolean,
): void {
  const headers = request.headers;
  for (const headerName of HOST_HEADERS_TO_VALIDATE) {
    const headerValue = getFirstHeaderValue(headers.get(headerName));
    if (headerValue && !disableHostCheck) {
      verifyHostAllowed(headerName, headerValue, allowedHosts);
    }
  }

  const forwarded = headers.get('forwarded');
  if (forwarded) {
    const forwardedParams = parseForwardedHeader(forwarded);
    if (forwardedParams.host && !disableHostCheck) {
      verifyHostAllowed('Forwarded "host"', forwardedParams.host, allowedHosts);
    }
    if (forwardedParams.proto && !VALID_PROTO_REGEX.test(forwardedParams.proto)) {
      throw new Error('Header "forwarded" proto parameter must be either "http" or "https".');
    }
  }

  const xForwardedPort = getFirstHeaderValue(headers.get('x-forwarded-port'));
  if (xForwardedPort && !VALID_PORT_REGEX.test(xForwardedPort)) {
    throw new Error('Header "x-forwarded-port" must be a numeric value.');
  }

  const xForwardedProto = getFirstHeaderValue(headers.get('x-forwarded-proto'));
  if (xForwardedProto && !VALID_PROTO_REGEX.test(xForwardedProto)) {
    throw new Error('Header "x-forwarded-proto" must be either "http" or "https".');
  }

  const xForwardedPrefix = getFirstHeaderValue(headers.get('x-forwarded-prefix'));
  if (xForwardedPrefix && !VALID_PREFIX_REGEX.test(xForwardedPrefix)) {
    throw new Error(
      'Header "x-forwarded-prefix" is invalid. It must start with a "/" and contain ' +
        'only alphanumeric characters, hyphens, and underscores, separated by single slashes.',

View on GitHub (pinned to bb72145f9a)

Solutions

  1. Fix the upstream proxy to emit `proto=http` or `proto=https` in its Forwarded header.
  2. Have the proxy overwrite (not append) client-supplied Forwarded headers.
  3. Remove `forwarded` from `trustProxyHeaders` if you rely on `x-forwarded-proto` instead.
  4. In tests, send a valid value like `Forwarded: for=1.2.3.4;host=example.com;proto=https`.

Example fix

// before
curl -H 'Forwarded: for=1.2.3.4;proto=HTTP/2' http://localhost:4200/
// after
curl -H 'Forwarded: for=1.2.3.4;proto=https' http://localhost:4200/
Defensive patterns

Strategy: validation

Validate before calling

const fwd = request.headers.get('forwarded');
const proto = fwd?.match(/proto=([^;]+)/i)?.[1]?.trim();
if (proto && !/^(http|https)$/i.test(proto)) throw new Error(`Invalid forwarded proto: ${proto}`);

Try / catch

try {
  validateHeaders(headers, allowedHosts, disableHostCheck);
} catch (e) {
  if ((e as Error).message.startsWith('Header "forwarded"')) {
    return res.status(400).end('Invalid Forwarded header');
  }
  throw e;
}

Prevention

When it happens

Trigger: A request carrying a `forwarded` header (e.g. `forwarded: for=1.2.3.4;proto=ftp` or `proto=HTTP/2`, or `proto=` empty) passes through validation while the `forwarded` header is included in `trustProxyHeaders`.

Common situations: A misconfigured reverse proxy emitting non-standard values in the forwarded proto field; manually crafted curl/test requests; a proxy that forwards the raw client-supplied `forwarded` header instead of overwriting it.

Related errors


AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30). Data as JSON: /api/errors/2118a0754438ac54. Report an issue: GitHub.