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.4View on GitHub (pinned to 1b2de5e052)
Solutions
- Increase maxRedirections to a value that comfortably covers the expected redirect chain (e.g. 5-10).
- If you would rather get the final 3xx response than an error, remove throwOnMaxRedirect (or set it to false).
- 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
- Size maxRedirections to the longest legitimate redirect chain for the target.
- Only set throwOnMaxRedirect when you genuinely want to abort on cap.
- Log the redirect history to diagnose chains that grow over time.
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
- maxRedirections must be a positive number
- throwOnMaxRedirect must be a boolean
- Redirect loop detected. Cannot redirect to ${origin}. This t
- ${optionName} must be an array
- ${optionName} must contain header names
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/dcfce10e3d831598.
Report an issue: GitHub.