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/500loaded normally with status 500. To make such pages fail, use content checks. - A private or internal target, such as
localhost,10.0.0.5or 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
- Error handling and retries: the general retry rules.
- ssl_error: the connection opened, but the certificate was rejected.