unclecode/crawl4ai · error · HTTPException
URL blocked (SSRF protection): {e}
Error message
URL blocked (SSRF protection): {e} What it means
HTTP 400 from validate_url_destination: the crawl URL resolved to a non-global IP (private, loopback, link-local, or a v4-mapped/NAT64 embedded form), so the SSRF egress guard rejected it. Bypassed only when CRAWL4AI_ALLOW_INTERNAL_URLS=true or the URL starts with raw:.
Source
Thrown at deploy/docker/utils.py:366
}
ALLOW_INTERNAL_URLS = os.environ.get("CRAWL4AI_ALLOW_INTERNAL_URLS", "false").lower() == "true"
def validate_url_destination(url: str) -> None:
"""Block crawl URLs targeting internal/private networks (SSRF protection).
Skipped when CRAWL4AI_ALLOW_INTERNAL_URLS=true.
Skipped for raw: URLs (inline HTML, no network fetch)."""
if ALLOW_INTERNAL_URLS:
return
if str(url).startswith(("raw:", "raw://")):
return
try:
validate_webhook_url(url)
except ValueError as e:
from fastapi import HTTPException
raise HTTPException(status_code=400, detail=f"URL blocked (SSRF protection): {e}")
def _expand_ip_candidates(ip):
"""Return [ip] plus any IPv4 form wrapped inside the IPv6 address.
SSRF guards must check the unwrapped form because ::ffff:127.0.0.1 and
::127.0.0.1 route to 127.0.0.1 but would not match IPv4 blocklists directly."""
candidates = [ip]
if isinstance(ip, ipaddress.IPv6Address):
if ip.ipv4_mapped is not None:
candidates.append(ip.ipv4_mapped)
else:
as_int = int(ip)
if 0 < as_int < 2**32:
candidates.append(ipaddress.IPv4Address(as_int))
return candidates
def validate_webhook_url(url: str) -> None:View on GitHub (pinned to 7e80152142)
Solutions
- Crawl the internal target from a self-hosted server started with CRAWL4AI_ALLOW_INTERNAL_URLS=true (only on trusted networks)
- For inline HTML you control, use the raw:<html> URL scheme which skips the network fetch and the guard
- For local testing, run the library in-process (AsyncWebCrawler) instead of through the deployed server
- If the URL is genuinely public but blocked, check your DNS - the name may be resolving internally (split-horizon); fix resolver config or use a truly public hostname
Example fix
# before
POST /crawl {"urls": ["http://localhost:3000/page"]}
# after - self-hosted with the guard relaxed
docker run -e CRAWL4AI_ALLOW_INTERNAL_URLS=true -p 11235:11235 unclecode/crawl4ai
# or inline HTML on the hosted server
POST /crawl {"urls": ["raw:<html><body>hi</body></html>"]} Defensive patterns
Strategy: validation
Validate before calling
import ipaddress, socket
from urllib.parse import urlparse
def is_public_target(url: str) -> bool:
host = urlparse(url).hostname or ""
try:
infos = socket.getaddrinfo(host, None)
except socket.gaierror:
return False
return all(ipaddress.ip_address(i[4][0]).is_global for i in infos) Type guard
def is_crawlable_url(url: str) -> bool:
return bool(urlparse(url).hostname) and is_public_target(url) Try / catch
try:
resp = requests.post(f"{S}/crawl", json={"urls": [u]})
resp.raise_for_status()
except requests.HTTPError as e:
if e.response.status_code == 400 and "SSRF" in e.response.text:
skip_or_tunnel(u) # route via self-hosted instance or public mirror Prevention
- Run the same is_global check the server uses before submitting batches of user-supplied URLs
- For local/intranet targets, always self-host with CRAWL4AI_ALLOW_INTERNAL_URLS=true rather than fighting the guard
- Use raw: URLs for pure HTML-processing calls so no network egress is attempted
When it happens
Trigger: POST /crawl with a URL whose hostname resolves to 10.x/192.168.x/127.0.0.1/169.254.x/::1 (or ::ffff:10.x etc.); also http://localhost:8080, http://kubernetes.default.svc inside a cluster, or a public name that DNS-rebinds to an internal IP.
Common situations: Using the hosted server to fetch a local dev site; crawling internal service names in Kubernetes; testing against localhost - none of which the public server permits by design. Sometimes a corporate DNS returns an internal IP for an apparently public hostname.
Related errors
- URL blocked
- Rejected request: {e}
- URL must start with {schemes}
- Rejected config: {e}
- webhook header not allowed: {name}
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/b284c146981ba8c7.
Report an issue: GitHub.