unclecode/crawl4ai · warning · HTTPException

Crawl exceeded the time limit

Error message

Crawl exceeded the time limit

What it means

HTTP 504 'Crawl exceeded the time limit' (deploy/docker/api.py:817): the per-crawl wall-clock deadline (asyncio timeout wrapping the crawl) expired before crawler.arun returned. The server enforces a maximum crawl duration; slow pages, deep waits (wait_for, delay_on_redirect), huge sitemaps, or a hung browser trip it. It is a gateway-style timeout: the crawl was killed, no result will appear for this request.

Source

Thrown at deploy/docker/api.py:817

            response["hooks"] = hooks_status

        return response

    except (UntrustedConfigError, HookValidationError) as e:
        # An untrusted request body tried to set a forbidden power-field,
        # construct a disallowed type, or specify an invalid hook. Client error.
        try:
            from monitor import get_monitor
            await get_monitor().track_request_end(
                request_id, success=False, error=str(e), status_code=400
            )
        except:
            pass
        raise HTTPException(status_code=400, detail=f"Rejected request: {e}")

    except asyncio.TimeoutError:
        # Per-crawl wall-clock deadline exceeded.
        raise HTTPException(status_code=504, detail="Crawl exceeded the time limit")

    except HTTPException:
        # Deliberate status (e.g. 400 SSRF "URL blocked") must pass through
        # rather than be genericized to 500 by the handler below.
        raise

    except Exception as e:
        logger.error(f"Crawl error: {str(e)}", exc_info=True)

        # Track request error
        try:
            from monitor import get_monitor
            await get_monitor().track_request_end(
                request_id, success=False, error=str(e), status_code=500
            )
        except:
            pass

View on GitHub (pinned to 7e80152142)

Solutions

  1. Raise the per-crawl time limit (server config) if the target legitimately needs longer.
  2. Tighten request-side knobs: shorter page_timeout, realistic wait_for with timeout, js_code that doesn't loop.
  3. Retry once — genuinely hung sockets sometimes succeed on a fresh connection; use bounded backoff, not tight retry loops.
  4. Pre-test the URL's load time externally and skip/queue known-slow targets instead of hitting the deadline.

Example fix

# before
r = client.post("/crawl", json={"url": slow_url})  # 504

# after
r = client.post("/crawl", json={
    "url": slow_url,
    "crawler_config": {"page_timeout": 60000, "wait_for": "body"},
})
if r.status_code == 504:
    time.sleep(5)
    r = client.post("/crawl", json={"url": slow_url, "crawler_config": {"page_timeout": 120000}})
Defensive patterns

Strategy: retry

Validate before calling

async def url_loads_within(url: str, budget_s: float) -> bool:
    try:
        await asyncio.wait_for(head_or_get(url), timeout=budget_s)
        return True
    except asyncio.TimeoutError:
        return False

# skip or lower wait_for requirements for URLs that fail the budget check

Try / catch

r = await client.post("/crawl", json=body)
if r.status_code == 504:
    body["crawler_config"] = {**body.get("crawler_config", {}), "page_timeout": 90000, "wait_for": "body"}
    await asyncio.sleep(3)
    r = await client.post("/crawl", json=body)  # single bounded retry, then give up

Prevention

When it happens

Trigger: Crawling a page whose resources hang (server keeps connection open); configured wait_for='selector that never appears'; page_action/delay settings plus slow network exceeding the server's crawl timeout; browser deadlock in the container.

Common situations: Default timeout too small for legitimately heavy pages; crawling rate-limited hosts that drip bytes; playwright event never firing so navigation waits forever.

Related errors


AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14). Data as JSON: /api/errors/1f03697db791e7d0. Report an issue: GitHub.