{"record":{"id":"3dda5b24146ada9c","repo":"PrefectHQ/fastmcp","slug":"timeout-fetching-url","errorCode":null,"errorMessage":"Timeout fetching {url}","messagePattern":"Timeout fetching (.+?)","errorType":"exception","errorClass":"SSRFFetchError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/ssrf.py","lineNumber":537,"sourceCode":"                        )\n                    chunks.append(chunk)\n\n                return SSRFFetchResponse(\n                    content=b\"\".join(chunks),\n                    status_code=response.status_code,\n                    headers=dict(response.headers),\n                )\n\n        except httpx2.TimeoutException as e:\n            last_error = e\n            continue\n        except httpx2.RequestError as e:\n            last_error = e\n            continue\n\n    if last_error is not None:\n        if isinstance(last_error, httpx2.TimeoutException):\n            raise SSRFFetchError(f\"Timeout fetching {url}\") from last_error\n        raise SSRFFetchError(f\"Error fetching {url}: {last_error}\") from last_error\n\n    raise SSRFFetchError(f\"Error fetching {url}: no fetch targets succeeded\")\n","sourceCodeStart":519,"sourceCodeEnd":541,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/ssrf.py#L519-L541","documentation":"After every fetch target (one per pinned resolved IP) fails with an httpx TimeoutException, ssrf_safe_fetch_response raises SSRFFetchError('Timeout fetching {url}') chained from the last timeout. Unlike the 'Overall timeout exceeded' variant, each individual request hit its own connect/read/write/pool timeout, and the aggregate budget was not the limiting factor (or was not yet exceeded).","triggerScenarios":"All resolved IPs of the hostname fail to connect or respond within the per-request timeout (default 10s each); e.g. host is down, firewalled with DROP, or DNS resolves to stale IPs.","commonSituations":"Target server offline or overloaded; security group / firewall silently dropping packets (no RST, so full timeout); IPv6 connectivity broken so the v6 target always times out; DNS pointing at decommissioned hosts.","solutions":["Verify the target host is up and reachable (curl/ping the resolved IPs directly).","Check firewall/security-group rules — DROP (vs REJECT) produces connect timeouts.","Fix DNS records if they point to stale/decommissioned IPs.","Increase timeout and overall_timeout if the host is merely slow.","Catch SSRFFetchError with the timeout message and retry with exponential backoff."],"exampleFix":"// before\ncontent = await ssrf_safe_fetch(url)  # every resolved IP times out at 10s\n// after\ntry:\n    content = await ssrf_safe_fetch(url, timeout=20.0, overall_timeout=90.0)\nexcept SSRFFetchError as e:\n    logger.warning(\"OAuth metadata fetch failed: %s\", e)\n    content = None","handlingStrategy":"retry","validationCode":"import socket\ntry:\n    infos = socket.getaddrinfo(host, 443)\nexcept socket.gaierror:\n    raise RuntimeError(f\"cannot resolve {host}\")\n# optionally probe one IP out-of-band before the guarded fetch","typeGuard":null,"tryCatchPattern":"try:\n    content = await ssrf_safe_fetch(url)\nexcept SSRFFetchError as e:\n    if \"Timeout fetching\" in str(e):\n        await asyncio.sleep(5)\n        content = await ssrf_safe_fetch(url)  # one bounded retry\n    else:\n        raise","preventionTips":["Check firewall rules use REJECT not DROP for faster failure","Fix stale DNS records pointing at decommissioned hosts","Verify IPv6 path works or ensure AAAA records are absent if unusable"],"tags":["network","timeout","ssrf","dns","httpx"],"backgroundTag":"connection-timeout","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}