PrefectHQ/fastmcp · error · SSRFFetchError
Timeout fetching {url}
Error message
Timeout fetching {url} What it means
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).
Source
Thrown at fastmcp_slim/fastmcp/server/auth/ssrf.py:537
)
chunks.append(chunk)
return SSRFFetchResponse(
content=b"".join(chunks),
status_code=response.status_code,
headers=dict(response.headers),
)
except httpx2.TimeoutException as e:
last_error = e
continue
except httpx2.RequestError as e:
last_error = e
continue
if last_error is not None:
if isinstance(last_error, httpx2.TimeoutException):
raise SSRFFetchError(f"Timeout fetching {url}") from last_error
raise SSRFFetchError(f"Error fetching {url}: {last_error}") from last_error
raise SSRFFetchError(f"Error fetching {url}: no fetch targets succeeded")
View on GitHub (pinned to 1f02114297)
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.
Example fix
// before
content = await ssrf_safe_fetch(url) # every resolved IP times out at 10s
// after
try:
content = await ssrf_safe_fetch(url, timeout=20.0, overall_timeout=90.0)
except SSRFFetchError as e:
logger.warning("OAuth metadata fetch failed: %s", e)
content = None Defensive patterns
Strategy: retry
Validate before calling
import socket
try:
infos = socket.getaddrinfo(host, 443)
except socket.gaierror:
raise RuntimeError(f"cannot resolve {host}")
# optionally probe one IP out-of-band before the guarded fetch Try / catch
try:
content = await ssrf_safe_fetch(url)
except SSRFFetchError as e:
if "Timeout fetching" in str(e):
await asyncio.sleep(5)
content = await ssrf_safe_fetch(url) # one bounded retry
else:
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Overall timeout exceeded: {url}
- Error fetching {url}: {last_error}
- Error fetching {url}: no fetch targets succeeded
- 408
- User server did not start on port {mcp_port}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/3dda5b24146ada9c.
Report an issue: GitHub.