Recommended Free Tools
Fix it by finding the redirect loop, not by simply raising the limit. Catch requests.exceptions.TooManyRedirects, inspect the chain with response.history, and make a one-hop request with allow_redirects=False. The repeated Location values usually reveal a bad URL, HTTP/HTTPS bounce, host mismatch, rewrite rule, or authentication-cookie loop. Correct that rule or call the canonical URL directly; increase Session.max_redirects only for a known, finite redirect sequence.
What the exception means
Requests follows redirects automatically for GET, OPTIONS, POST, PUT and DELETE. If the chain reaches the configured ceiling, it raises TooManyRedirects. The exception is a client-side guardrail: it does not prove that the server is down or that the network is unavailable.
The documented default maximum is 30 redirects. Requests records each redirect response in Response.history, ordered from the oldest hop to the newest. A timeout is a separate safeguard and should still be supplied on production requests.
Diagnose the chain before changing settings
Reproduce with a bounded timeout
Use a connect/read timeout tuple so a broken endpoint cannot hold the process indefinitely. When the limit is reached, the exception often carries the last response in exc.response.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →#1 Best Overall
import requests
url = "https://example.com/start"
try:
response = requests.get(url, timeout=(5, 20))
except requests.exceptions.TooManyRedirects as exc:
response = exc.response
print("redirect limit reached")
if response is not None:
print("last URL:", response.url)
for item in response.history:
print(
item.status_code,
item.url,
"->",
item.headers.get("Location"),
)
else:
print("final:", response.status_code, response.url)
for item in response.history:
print(
item.status_code,
item.url,
"->",
item.headers.get("Location"),
)
Log the status code, source URL, Location header and any relevant cookies for every hop. Do not log authorization tokens or session secrets; redact those values before sending diagnostics to a shared log.
Expose the first hop
Disable following temporarily to see exactly what the starting URL returns:
import requests
r = requests.get(
"https://example.com/start",
allow_redirects=False,
timeout=(5, 20),
)
print(r.status_code)
print(r.url)
print(r.headers.get("Location"))
print(r.cookies.get_dict())
A 301, 302, 303, 307 or 308 response with a Location header is the next hop. Follow that URL manually with the same diagnostic request, or use the logging loop below to inspect a bounded number of hops without allowing an unbounded client loop.
Print a bounded redirect trace
import requests
session = requests.Session()
current = "https://example.com/start"
seen = set()
for hop in range(15):
if current in seen:
print("cycle detected:", current)
break
seen.add(current)
r = session.get(current, allow_redirects=False, timeout=(5, 20))
location = r.headers.get("Location")
print(hop + 1, r.status_code, r.url, "->", location)
if r.status_code not in (301, 302, 303, 307, 308) or not location:
print("chain ends here")
break
current = requests.compat.urljoin(current, location)
else:
print("diagnostic hop limit reached")
This trace is deliberately finite. It lets you see a cycle while protecting a script from endlessly issuing requests.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #2
Recognize the common loop patterns
A URL cycle (A → B → A)
If two or more URLs repeat, compare each server’s Location value. A rewrite, canonical-link rule or application redirect is sending the client back to a URL it already visited. Remove one of the competing rules or make both rules converge on one canonical URL.
HTTP and HTTPS bouncing
A TLS terminator or reverse proxy may tell the application that the request is HTTP even though the visitor used HTTPS. The application then redirects to HTTPS, while the proxy redirects the resulting request back to HTTP. Fix the proxy’s forwarded-protocol configuration and the application’s “secure request” detection. Test the public URL, not only an internal service address.
www and apex-host disagreement
For example, www.example.com can redirect to example.com while another rule sends the apex host back to www. Choose one host, issue one permanent redirect to it, and remove the reverse rule. Check ports as well: an implicit HTTP-to-HTTPS redirect can combine with a host rewrite to create a longer cycle.
Trailing-slash and path canonicalization
Framework routing may add a slash, while a proxy or CMS removes it. Inspect the exact paths, case, URL encoding and query string in every hop. Configure canonicalization in one layer, or make the layers agree on the same form.
Authentication and cookie redirects
A login endpoint can redirect to a protected page, which redirects back to login when the session cookie is missing, rejected or scoped to the wrong domain/path. Inspect Set-Cookie and the cookies sent on the next request. Check secure and SameSite attributes, domain and path scope, clock skew for signed cookies, and whether your session is being discarded between calls. A redirect caused by an expired login is not fixed by raising the redirect limit; repair the authentication flow or authenticate the request correctly.
Apply the durable fix
Call the canonical URL directly
Once the chain identifies the intended final address, use that address in your code. This avoids unnecessary hops but does not replace correcting a server rule that other clients still encounter.
Correct the emitting layer
- Client URL construction: remove accidental scheme, host, slash or query transformations.
- Web server: consolidate rewrite and canonical-host rules.
- Reverse proxy/CDN: pass the original host and scheme consistently to the application.
- Application: return one canonical redirect and avoid redirecting an already-canonical request.
- Authentication: preserve the session cookie and verify login return URLs.
After changing a rule, repeat the no-follow test and then a normal request. Confirm that the chain terminates at the expected status and URL from the same environment where your Python process runs.
Use max_redirects only for a finite chain
import requests
session = requests.Session()
session.max_redirects = 10 # deliberate guardrail for a known finite chain
response = session.get("https://example.com/start", timeout=(5, 20))
print(response.status_code, response.url)
Raising the ceiling can be reasonable when a legacy sign-in flow legitimately requires more than the default number of hops. It only delays the exception if a cycle exists, increases latency and load, and can hide a configuration defect. Never treat a very large value as a loop fix, and do not remove timeouts.
Choose the right redirect behavior for each request
| Goal | Setting or method | What you learn |
|---|---|---|
| Inspect the first response | allow_redirects=False |
Status, first Location, cookies and headers |
| Follow a normal finite chain | Default behavior | Final response plus response.history |
| Support a known longer chain | Session.max_redirects = n |
A higher, still finite guardrail |
| Prevent a hanging request | timeout=(connect, read) |
Separate protection from redirect limits |
Troubleshooting checklist
exc.responseisNone: retain the original URL and run a separateallow_redirects=Falserequest; the exception did not provide a response object.- No
Locationheader appears: verify that you are examining the redirect response, not a later response, and print headers with sensitive values removed. - The browser works but Requests loops: compare cookies, authorization, user agent and proxy settings. A browser may already hold a valid session or use a different proxy path.
- Only POST loops or changes method: inspect 301/302/303 behavior and test the endpoint’s intended 307/308 semantics. Confirm that the server’s redirect target accepts the resulting method.
- Redirects differ by environment: compare DNS, proxy, CDN, forwarded headers, host and scheme. The public edge and internal test URL may not use the same rules.
- It times out instead of raising: set a connect/read timeout. A slow origin and a redirect loop are different failures and need separate measurements.
Or skip the browser setup
If your goal is a clean image or PDF of a page rather than debugging its redirect policy, ScreenshotNeo provides a single HTTP request. It accepts cookie and consent banners as a visitor, then removes more than 60 known consent platforms, newsletter popups and chat widgets before capture; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result.
Use the API documentation at https://screenshotneo.com/docs/ for all options. A cURL request:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
FAQ
Does TooManyRedirects always mean a server bug?
No. The loop can come from the URL your code builds, a proxy or CDN, an application rewrite, or an authentication cookie problem. The observed Location chain identifies which layer to investigate.
Can I disable redirects permanently?
You can pass allow_redirects=False per request, but that changes the response your application receives. Use it for diagnostics or when your code intentionally handles each hop; do not use it to conceal a broken canonical URL.
Best Value
What is the difference between response.url and response.history?
response.url is the URL of the response you hold, usually the final URL. response.history contains the earlier redirect responses in chronological order, allowing you to reconstruct the path.
Should I retry after this exception?
Not automatically. A retry repeats the same redirect policy and can add load. First determine whether the chain is deterministic; retry only after correcting the URL, session or server rule, with a bounded timeout and finite redirect limit.
Frequently Asked Questions
How many redirects does Requests allow by default?
The documented default is 30 redirects per request. The limit is a guardrail and is not evidence that 30 hops are valid for your endpoint.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →How can I see the very first redirect?
Send the request with allow_redirects=False and print status_code, url and headers.get("Location").
Is a timeout an alternative to a redirect limit?
No. A timeout bounds waiting for a connection or response; the redirect limit bounds how many responses Requests will follow. Production code generally needs both.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

