{"record":{"id":"51f6552f86d903a0","repo":"BerriAI/litellm","slug":"too-many-redirects","errorCode":null,"errorMessage":"Too many redirects","messagePattern":"Too many redirects","errorType":"exception","errorClass":"SSRFError","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/url_utils.py","lineNumber":412,"sourceCode":"    if not getattr(litellm, \"user_url_validation\", True):\n        kwargs.setdefault(\"follow_redirects\", True)\n        return client.get(url, **kwargs)\n    kwargs.pop(\"follow_redirects\", None)\n    caller_headers: Final = kwargs.pop(\"headers\", {})\n    for _ in range(_MAX_REDIRECTS):\n        validated_url, original_host = validate_url(url)\n        response = client.get(\n            validated_url,\n            headers={**caller_headers, \"Host\": original_host},\n            follow_redirects=False,\n            **kwargs,\n        )\n        if not response.is_redirect:\n            return response\n        # Resolve the next hop against the ORIGINAL (pre-rewrite) URL so\n        # relative Location headers keep the original hostname.\n        url = _extract_redirect_url(response, url)\n    raise SSRFError(\"Too many redirects\")\n\n\nasync def async_safe_get(client: Any, url: str, **kwargs: Any) -> Any:\n    \"\"\"Async version of safe_get.\"\"\"\n    if not getattr(litellm, \"user_url_validation\", True):\n        kwargs.setdefault(\"follow_redirects\", True)\n        return await client.get(url, **kwargs)\n    kwargs.pop(\"follow_redirects\", None)\n    caller_headers: Final = kwargs.pop(\"headers\", {})\n    for _ in range(_MAX_REDIRECTS):\n        validated_url, original_host = validate_url(url)\n        response = await client.get(\n            validated_url,\n            headers={**caller_headers, \"Host\": original_host},\n            follow_redirects=False,\n            **kwargs,\n        )\n        if not response.is_redirect:","sourceCodeStart":394,"sourceCodeEnd":430,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/url_utils.py#L394-L430","documentation":"Raised by safe_get (sync) after the redirect loop exceeds _MAX_REDIRECTS (10) hops. Each hop is validated for SSRF, and if every response keeps redirecting, the loop terminates with this SSRFError instead of following forever. It signals either a genuine redirect loop (A -> B -> A) or a redirect chain longer than 10 hops on the target server.","triggerScenarios":"safe_get on a URL participating in a redirect cycle (e.g. http -> https -> http on the same path, or two endpoints redirecting to each other), or a legitimately long chain (11+ hops) of same-origin redirects — each hop must also pass origin checks, so loops among a handful of URLs are the usual cause.","commonSituations":"Misconfigured web servers forcing scheme/port toggles that loop; login pages redirecting in a circle when unauthenticated; trailing-slash redirect loops (path with and without slash each redirecting to the other); OAuth flows longer than 10 hops.","solutions":["Trace the chain manually (curl -IL or curl with --max-redirs) to find where it loops and fix the server-side redirect logic.","Request the final destination URL directly, bypassing the chain.","Fix common loop causes: consistent scheme (always https), consistent trailing slash, consistent host (www vs apex).","Note the cap is hardcoded at 10 in litellm — chains longer than that cannot be followed via safe_get."],"exampleFix":"# before: loop between http and https on server\nresp = safe_get(client, \"http://api.example.com/f\")\n\n# after: use the canonical scheme/URL directly\nresp = safe_get(client, \"https://api.example.com/f\")","handlingStrategy":"try-catch","validationCode":"import httpx\n\ndef resolves_within_limit(url: str, limit: int = 10) -> bool:\n    with httpx.Client(follow_redirects=False) as c:\n        current, hops = url, 0\n        while hops < limit:\n            r = c.head(current, timeout=10)\n            if not r.is_redirect:\n                return True\n            current = str(httpx.URL(current).join(r.headers[\"location\"]))\n            hops += 1\n        return False","typeGuard":null,"tryCatchPattern":"from litellm.litellm_core_utils.url_utils import SSRFError\n\ntry:\n    resp = safe_get(client, url)\nexcept SSRFError as e:\n    if \"Too many redirects\" in str(e):\n        return bad_gateway(\"redirect loop at upstream\")\n    raise","preventionTips":["Pre-resolve chains with curl -IL to detect loops before wiring a URL into litellm.","Enforce consistent scheme/slash/host on your servers to avoid redirect ping-pong.","Do not retry on this error — a loop will loop again; fix the upstream."],"tags":["ssrf","redirect","redirect-loop","http"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}