This error occurs when an HTTP client follows too many redirect responses, exceeding the configured limit. It commonly happens with axios, node-fetch, or the follow-redirects package when a server creates a redirect loop or when legitimate redirects exceed the maxRedirects threshold (default 21).
The "Maximum number of redirects exceeded" error is thrown by HTTP clients when a request follows more redirect responses than the configured maximum allows. This safety mechanism prevents infinite redirect loops that could hang your application indefinitely. Most Node.js HTTP libraries use the follow-redirects package under the hood, which sets a default limit of 21 redirects. When this limit is exceeded, the ERR_FR_TOO_MANY_REDIRECTS error is emitted and the request fails. Circular redirects are the most common cause - when URL A redirects to URL B, which redirects back to URL A, creating an endless loop. However, the error can also occur with legitimate multi-step redirects that happen to exceed the threshold, particularly in complex authentication flows or CDN configurations.
Add request logging to see the full redirect chain before it fails:
const axios = require('axios');
axios.interceptors.request.use(request => {
console.log('Requesting:', request.url);
return request;
});
axios.interceptors.response.use(response => {
if (response.status >= 300 && response.status < 400) {
console.log('Redirect from:', response.config.url);
console.log('Redirect to:', response.headers.location);
}
return response;
});
try {
await axios.get('https://example.com/api/endpoint');
} catch (err) {
console.error('Failed after redirects:', err.message);
}This reveals whether you have a circular loop (same URLs repeating) or just too many legitimate redirects.
If you control the server, fix the redirect configuration to break the loop:
// BAD: Creates circular redirect
app.get('/api/login', (req, res) => {
if (!req.session.user) {
res.redirect('/auth'); // Redirects to auth
}
});
app.get('/auth', (req, res) => {
if (!req.query.token) {
res.redirect('/api/login'); // Redirects back to login - LOOP!
}
});
// GOOD: Properly handle authentication flow
app.get('/api/login', (req, res) => {
if (!req.session.user) {
res.redirect('/auth?returnUrl=/api/login');
} else {
res.json({ user: req.session.user });
}
});
app.get('/auth', (req, res) => {
// Show login form or handle OAuth without redirecting back
res.render('login', { returnUrl: req.query.returnUrl });
});Ensure each redirect moves forward in the flow rather than creating cycles.
If the redirect chain is legitimate but exceeds the default limit, increase the threshold:
// With axios
const axios = require('axios');
const response = await axios.get('https://example.com/api', {
maxRedirects: 30 // Default is 21
});
// With follow-redirects directly
const { https } = require('follow-redirects');
https.get({
host: 'example.com',
path: '/api',
maxRedirects: 30
}, (response) => {
// Handle response
});
// With node-fetch (using follow-redirects wrapper)
const fetch = require('node-fetch');
const response = await fetch('https://example.com/api', {
follow: 30 // Maximum redirects to follow
});Only increase this value if you've verified the redirects are legitimate and not circular.
For complex scenarios, disable automatic redirects and handle them yourself:
// With axios
const axios = require('axios');
async function fetchWithManualRedirects(url, maxRedirects = 5) {
let redirectCount = 0;
let currentUrl = url;
while (redirectCount < maxRedirects) {
const response = await axios.get(currentUrl, {
maxRedirects: 0,
validateStatus: (status) => status < 400 // Don't throw on 3xx
});
if (response.status >= 300 && response.status < 400) {
const location = response.headers.location;
if (location === currentUrl) {
throw new Error('Circular redirect detected');
}
console.log(`Redirect ${redirectCount + 1}: ${currentUrl} -> ${location}`);
currentUrl = new URL(location, currentUrl).href;
redirectCount++;
} else {
return response;
}
}
throw new Error('Maximum redirects exceeded');
}
const response = await fetchWithManualRedirects('https://example.com/api');This gives you full control to detect circular redirects and log the redirect chain.
Implement circular redirect detection using the beforeRedirect option:
const axios = require('axios');
const visitedUrls = new Set();
const response = await axios.get('https://example.com/api', {
beforeRedirect: (options, responseDetails) => {
const nextUrl = options.href || options.url;
if (visitedUrls.has(nextUrl)) {
throw new Error(`Circular redirect detected: ${nextUrl}`);
}
visitedUrls.add(nextUrl);
console.log('Redirecting to:', nextUrl);
}
});This aborts the request immediately when a circular redirect is detected, providing a clearer error message.
Understanding the follow-redirects Package
Most Node.js HTTP clients (axios, request, node-fetch wrappers) use the follow-redirects package internally. This package handles HTTP redirects by creating a new request for each redirect response. The default maxRedirects of 21 is based on the HTTP specification's recommendation.
Detecting Redirect Loops in Production
Implement monitoring to detect redirect issues before they cause errors:
const axios = require('axios');
axios.interceptors.response.use(
response => response,
error => {
if (error.code === 'ERR_FR_TOO_MANY_REDIRECTS') {
// Log to monitoring service
console.error('Redirect loop detected', {
url: error.config.url,
method: error.config.method,
timestamp: new Date().toISOString()
});
}
throw error;
}
);Handling Authentication Redirects
OAuth and SSO flows often involve multiple redirects. Configure clients appropriately:
// For OAuth flows that may have many redirect steps
const authClient = axios.create({
maxRedirects: 30,
beforeRedirect: (options, responseDetails) => {
// Log OAuth redirect chain for debugging
console.log('OAuth redirect:', options.href);
}
});Testing for Redirect Loops
Create integration tests to catch redirect configuration issues:
const axios = require('axios');
describe('API Endpoints', () => {
it('should not create redirect loops', async () => {
const visitedUrls = new Set();
await axios.get('https://api.example.com/endpoint', {
maxRedirects: 10,
beforeRedirect: (options) => {
const url = options.href || options.url;
expect(visitedUrls.has(url)).toBe(false);
visitedUrls.add(url);
}
});
});
});Proxy and CDN Considerations
When using proxies or CDNs, redirect chains can become complex. Consider using the absolute URL form for redirects to avoid resolution issues:
// Server-side: Use absolute URLs in redirect responses
app.get('/api/old-endpoint', (req, res) => {
res.redirect(301, 'https://api.example.com/api/new-endpoint');
});Security Implications
Excessive redirects can be used in attacks to cause denial of service or confuse clients. Always validate redirect destinations and consider implementing rate limiting for endpoints that trigger redirects.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams