navigation_failed

navigation_failed means the browser started to open your URL but never received a page. DNS failed, the connection was refused or reset, or the site redirected in a loop. The URL is valid. The problem is between the renderer and the target server.

Field Value
HTTP status 422 Unprocessable Entity
error.type target_error
error.code navigation_failed. Some failures arrive today with the generic code screenshot_failed and the same browser message, so match on both
retryable true
Credit used No

It belongs to the target errors in the error code reference.

Example response

{
  "error": {
    "type": "target_error",
    "code": "navigation_failed",
    "message": "net::ERR_NAME_NOT_RESOLVED at https://no-such-host.invalid/",
    "retryable": true
  },
  "request_id": "req_8c27f0b1e94d3a65"
}

The message usually carries the browser's network error name, which starts with net::ERR_. That name tells you what went wrong. Log the full message.

What the network error names mean

We opened test URLs in headless Chrome 148 and recorded the errors for DNS, redirect loops, certificates and timeouts. The connection rows use Chrome's standard names, which we did not reproduce. The "Retry?" column is our advice; the API marks every target error as retryable: true.

Error name Meaning Retry?
ERR_NAME_NOT_RESOLVED The host name has no DNS record No. Check the spelling and the DNS records
ERR_TOO_MANY_REDIRECTS More than 20 redirects; usually a loop No. Fix the redirect rules on the site
ERR_CONNECTION_REFUSED Nothing listens on the port Once, later. The site can be restarting
ERR_CONNECTION_RESET, ERR_CONNECTION_CLOSED The server or a firewall cut the connection Once, later. If it repeats, the site probably blocks the renderer
ERR_CERT_*, ERR_SSL_* Certificate or TLS problem No. See ssl_error
Navigation timeout No response in time See timeout

Some other results are not this error:

  • HTTP 404 or 500 from the site. The browser loads the error page, and you get a screenshot of it. In our test, https://httpbin.org/status/500 loaded normally with status 500. To make such pages fail, use content checks.
  • A private or internal target, such as localhost, 10.0.0.5 or a port other than 80 and 443. The renderer refuses it before navigation. See blocked.

Test from outside your network

Your laptop is not a good test, because it can reach internal hosts. Run these checks from a server outside your company network, or use a public resolver:

# Does the name resolve on the public internet?
dig +short @1.1.1.1 example.com

# Does the site answer, and where does it redirect?
curl -sS -o /dev/null -L --max-redirs 20 \
  -w 'status=%{http_code} redirects=%{num_redirects} ip=%{remote_ip}\n' \
  https://example.com/

If dig prints nothing, the name does not exist in public DNS. That is typical for new domains before the records spread, for typos, and for internal names such as intranet.company.lan. If curl reports Maximum (20) redirects followed, fix the loop first. A common cause is a site that redirects between www and the bare domain, or between HTTP and HTTPS.

If both commands work from outside but the API still fails, the site probably blocks traffic from cloud providers. The bot protection guide explains the options. For pages that you own, you can relax the bot rules for the pages you capture, or send the markup with html instead of a URL.

Handle it in code

Retry once after a delay, but not for names that a retry cannot fix. This Python function uses the error name in the message:

# navigation.py  (Python 3.9+, requests)
import os
import time

import requests

API = os.environ.get("RS_BASE_URL", "https://api.renderscreenshot.com")
# Network errors that a retry a few seconds later will not fix.
PERMANENT = ("ERR_NAME_NOT_RESOLVED", "ERR_TOO_MANY_REDIRECTS", "ERR_INVALID_URL", "ERR_CERT_", "ERR_SSL_")


class TargetUnreachable(Exception):
    pass


def screenshot(url, attempts=2, wait_seconds=20):
    for attempt in range(1, attempts + 1):
        res = requests.post(
            f"{API}/v1/screenshot",
            headers={"Authorization": f"Bearer {os.environ['RS_API_KEY']}"},
            json={"url": url},
            timeout=(10, 90),
        )
        if res.ok:
            return res.content

        error = res.json()["error"]
        # Match the documented code, or the browser's net::ERR_ name in a generic target error.
        if error["code"] != "navigation_failed" and "net::ERR_" not in error.get("message", ""):
            res.raise_for_status()
        if attempt == attempts or any(name in error["message"] for name in PERMANENT):
            raise TargetUnreachable(f"{url}: {error['message']}")
        time.sleep(wait_seconds)  # one late retry covers a restart or a short outage

Tested on 2026-10-15. The network error table came from Puppeteer 24.43.1 with Chrome 148 on Node 22.18. The Python function ran with Python 3.14 and requests 2.32.3 against a local mock that returns the API's error envelope. ERR_NAME_NOT_RESOLVED failed at once, and ERR_CONNECTION_RESET was tried twice.

Related in this topic

Was this page helpful?