psf/requests · error · TooManyRedirects
Exceeded {self.max_redirects} redirects.
Error message
Exceeded {self.max_redirects} redirects. What it means
Raised as `requests.exceptions.TooManyRedirects` from `Session.resolve_redirects()` in `src/requests/sessions.py:217` when the accumulated redirect history reaches `self.max_redirects` (default 30, from `DEFAULT_REDIRECT_LIMIT`). It is a loop guard: each hop is appended to `resp.history`, and once the limit is hit requests stops following and raises, attaching the last response as `e.response`.
Source
Thrown at src/requests/sessions.py:217
hist: list[Response] = [] # keep track of history
url = self.get_redirect_target(resp)
previous_fragment = urlparse(req.url).fragment
while url:
prepared_request = req.copy()
# Update history and keep track of redirects.
resp.history = hist[:]
hist.append(resp)
try:
resp.content # Consume socket so it can be released
except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
resp.raw.read(decode_content=False)
if len(resp.history) >= self.max_redirects:
raise TooManyRedirects(
f"Exceeded {self.max_redirects} redirects.", response=resp
)
# Release the connection back into the pool.
resp.close()
# Handle redirection without scheme (see: RFC 1808 Section 4)
if url.startswith("//"):
parsed_rurl = urlparse(resp.url)
url = ":".join([to_native_string(parsed_rurl.scheme), url])
# Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
parsed = urlparse(url)
if parsed.fragment == "" and previous_fragment:
parsed = parsed._replace(fragment=previous_fragment)
elif parsed.fragment:
previous_fragment = parsed.fragment
url = parsed.geturl()View on GitHub ↗ (pinned to 414f0513c3)
Solutions
- Inspect the loop: catch the exception and print `e.response.history` / `e.response.headers['Location']` to see where it bounces, or request with `allow_redirects=False` to see the first hop.
- Use a `requests.Session()` so cookies set during the redirect chain persist — auth/consent loops are the top cause.
- Fix the request so the server stops redirecting: correct scheme/host, valid credentials or token, expected User-Agent header.
- Only if the chain is legitimately long, raise the limit: `session.max_redirects = 60`.
Example fix
# before
r = requests.get('http://example.com/dashboard') # bounces to /login forever (cookies not kept)
# after
s = requests.Session()
s.post('https://example.com/login', data=creds) # session keeps auth cookie
r = s.get('https://example.com/dashboard') Defensive patterns
Strategy: try-catch
Validate before calling
# Bound or raise the limit deliberately per session s = requests.Session() s.max_redirects = 5
Try / catch
try:
resp = session.get(url)
except requests.exceptions.TooManyRedirects as e:
# likely a redirect loop; inspect the partial history
log.error('Redirect loop for %s: %s', url, [r.url for r in e.response.history] if e.response is not None else e) Prevention
- Treat TooManyRedirects as a redirect loop, not a transient failure — do not blindly retry
- Set session.max_redirects to the smallest value your endpoints legitimately need
- Use allow_redirects=False and follow Location manually when you need per-hop control or cookie/auth inspection
- Log response.history when debugging to see the redirect chain
When it happens
Trigger: Any call with `allow_redirects=True` (the default for GET) where the server chain exceeds `max_redirects` hops — most often a redirect loop (A→B→A) or a login wall that redirects every request back to itself. Also triggered quickly if the user lowered `session.max_redirects`.
Common situations: Missing/expired auth cookies causing an endless bounce to a login page; http↔https or www↔apex rewrite loops in misconfigured nginx/CDN rules; sites that redirect based on a cookie or User-Agent the client never persists (using bare `requests.get` instead of a `Session` so cookies are dropped between hops); URL-normalization loops (trailing slash added and removed).
Related errors
- An error occurred when rewinding request body for redirect.
- Unable to rewind request body for redirect.
- You can only send PreparedRequests.
- Files must be provided.
- Data must not be a string.
AI-assisted analysis of psf/requests@414f0513c3 (2026-07-31).
Data as JSON: /data/errors/86a3b690b49e5a2a.json.
Report an issue: GitHub ↗.