nodejs/node · warning · Error

max redirects

Error message

max redirects

What it means

A plain Error (not InvalidArgumentError) thrown from RedirectHandler.onResponseStart when opts.throwOnMaxRedirect is true and the number of redirects already followed (this.history.length) has reached maxRedirections. This is the intended, opted-in behavior: the caller asked to be notified when the redirect cap is hit rather than receiving the redirect response silently.

Source

Thrown at deps/undici/src/lib/handler/redirect-handler.js:52

    this.opts.body = util.wrapRequestBody(this.opts.body)
    this.stripHeadersOnRedirect = normalizeStripHeaders(stripHeadersOnRedirect, 'stripHeadersOnRedirect')
    this.stripHeadersOnCrossOriginRedirect = normalizeStripHeaders(stripHeadersOnCrossOriginRedirect, 'stripHeadersOnCrossOriginRedirect')
    this.maxRedirections = maxRedirections
    this.handler = handler
    this.history = []
  }

  onRequestStart (controller, context) {
    this.handler.onRequestStart?.(controller, { ...context, history: this.history })
  }

  onRequestUpgrade (controller, statusCode, headers, socket) {
    this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
  }

  onResponseStart (controller, statusCode, headers, statusMessage) {
    if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) {
      throw new Error('max redirects')
    }

    let removeContentHeaders = statusCode === 303

    // https://tools.ietf.org/html/rfc7231#section-6.4.2
    // https://fetch.spec.whatwg.org/#http-redirect-fetch
    // In case of HTTP 301 or 302 with POST, change the method to GET
    // QUERY is safe (RFC 10008) and should not change method like GET.
    if ((statusCode === 301 || statusCode === 302) && this.opts.method === 'POST') {
      this.opts.method = 'GET'
      if (util.isStream(this.opts.body)) {
        util.destroy(this.opts.body.on('error', noop))
      }
      this.opts.body = null
      removeContentHeaders = true
    }

    // https://tools.ietf.org/html/rfc7231#section-6.4.4

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Increase maxRedirections to a value that comfortably covers the expected redirect chain (e.g. 5-10).
  2. If you would rather get the final 3xx response than an error, remove throwOnMaxRedirect (or set it to false).
  3. Wrap the request in try/catch and treat this error as a signal to retry against the last Location with a fresh request.

Example fix

// before
const agent = new Agent({ maxRedirections: 1, throwOnMaxRedirect: true })

// after
const agent = new Agent({ maxRedirections: 5 })
// or, opt out of throwing and inspect the 3xx response yourself:
// const agent = new Agent({ maxRedirections: 1 })
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await agent.request({ path, method: 'GET', maxRedirections, throwOnMaxRedirect: true })
} catch (e) {
  if (e.message === 'max redirects') {
    // bump budget or follow the last Location manually
  } else throw e
}

Prevention

When it happens

Trigger: Setting throwOnMaxRedirect: true together with a maxRedirections value (including 0) and then hitting a redirect chain whose length meets or exceeds the cap. With maxRedirections: 0 even the first 3xx response triggers it.

Common situations: Tight redirect budgets set to catch redirect loops; misconfigured maxRedirections that is too low for the target site's normal redirect chain; legitimately long login/SSO redirect flows.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/dcfce10e3d831598. Report an issue: GitHub.