Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Put browser cleanup in a finally block. That way, Puppeteer closes a browser your script launched whether page.goto() succeeds or rejects after a navigation timeout. Use await browser.close() to shut down that browser and its pages; if Puppeteer connected to a browser managed elsewhere, use browser.disconnect() instead so you detach without shutting down the remote browser.

Close the browser in a finally block

Puppeteer documents that Frame.goto() can throw when navigation exceeds its timeout, and that Browser.close() closes the browser and all associated pages. JavaScript’s finally block is the cleanup point for either outcome: it runs after the try body completes or throws. The following minimal pattern is suitable when a browser is launched for one navigation and should always be shut down:

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto(url, { timeout: 10_000 });
} finally {
  await browser.close();
}

The timeout value is in milliseconds, so 10_000 means 10 seconds. Replace url with the URL you are navigating to. Puppeteer’s Frame.goto() documentation lists a timeout as one of the conditions that can throw; its Browser.close() reference describes the shutdown scope.

Preserve the navigation error if shutdown also fails

A finally block guarantees that cleanup is attempted, but a failure from browser.close() can itself become the error reported by the surrounding code. If you need to retain the original navigation error as well as report a shutdown error, capture the navigation failure and handle cleanup separately. This version records both and then rethrows the navigation error when one occurred:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const browser = await puppeteer.launch();
let navigationError;

try {
  const page = await browser.newPage();
  await page.goto(url, { timeout: 10_000 });
} catch (error) {
  navigationError = error;
} finally {
  try {
    await browser.close();
  } catch (closeError) {
    console.error('Could not close Puppeteer browser:', closeError);
  }
}

if (navigationError) {
  throw navigationError;
}

Adapt the reporting to your application: for example, send both errors to its logger or error-handling layer. Do not silently discard the close error. If navigation succeeded but closing failed, this example logs the shutdown failure and continues; change that policy if a failed shutdown must fail the job.

Choose the cleanup method by ownership and scope

The right method depends on what your script owns and what it intends to end. Puppeteer distinguishes shutting down a browser from detaching from one, and exposes narrower cleanup methods for pages and contexts.

Method Scope and effect Use it when
browser.close() Closes the browser and all its associated pages. Your script launched the browser and is finished with that browser session.
page.close() Closes one page, leaving the browser session available. You want to discard the timed-out page but keep using the browser for other work.
context.close() Closes a non-default isolated browser context and its pages. The default context cannot be closed. Your script created a non-default context and is done with that isolated session.
browser.disconnect() Detaches Puppeteer from an externally managed browser; it does not shut down that browser or close its pages. Your script attached to a browser owned by another process and must leave it running.

The distinction between browser.close() and browser.disconnect() matters most in automation services or shared browser setups: closing a remotely managed browser can affect work beyond the current Puppeteer client. See Puppeteer’s browser-management guide, Page API and BrowserContext.close() reference.

Keep a browser open but discard the failed page

If a timed-out navigation is one step in a longer session, closing the whole browser may be too broad. Close only the page when that is the intended recovery, and keep browser-level cleanup in the outer owner’s lifecycle:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  try {
    await page.goto(url, { timeout: 10_000 });
  } catch (error) {
    await page.close();
    throw error;
  }
  // Continue browser work here if navigation succeeded.
} finally {
  await browser.close();
}

This example still closes the browser at the end because this script launched it. If a different component owns the browser, that owner—not a helper that merely borrowed a page—should decide when to close the browser.

Set and interpret navigation timeouts

Puppeteer navigation timeouts are milliseconds. The current WaitForOptions reference documents a default of 30,000 ms and says that timeout: 0 disables the timeout. A per-navigation option can set the limit for one call; page.setDefaultNavigationTimeout(timeout) sets the default for navigation methods including goto, back/forward, reload, setContent and waitForNavigation. See the version-labeled WaitForOptions reference and Page class reference.

// One navigation only
await page.goto(url, { timeout: 20_000 });

// Or set the navigation limit for this page
page.setDefaultNavigationTimeout(20_000);
await page.goto(url);

Choose the timeout for the site and the lifecycle event your task waits for. Setting it to zero removes the time limit; that can leave a task waiting indefinitely if the page never reaches the selected event. A longer limit may be appropriate for a known slow navigation, but it does not fix an unreachable server or a page that cannot load.

Diagnose the actual navigation failure

A rejected goto() is not necessarily a timeout. Puppeteer’s Frame.goto() reference also lists SSL errors, invalid target URLs, unreachable or unresponsive servers, failed main-resource loads, and blocklist or allowlist restrictions as exception cases. Record or report the actual error rather than labeling every rejection as a timeout.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Timeout exceeded: The navigation did not reach its awaited lifecycle condition before the configured limit. Check the URL, the site’s response and the selected wait condition; adjust the timeout only if the task reasonably needs more time.
  • SSL error: The navigation failed during the secure connection or certificate checks. Investigate the target’s TLS setup and the runtime environment rather than treating a longer timeout as a fix.
  • Invalid URL: Check that the value passed to goto() is a valid target URL, including its scheme where required.
  • Unreachable or unresponsive server: Verify that the target is available from the machine running Puppeteer and that network access is working.
  • Main-resource load failure: Inspect the target response and the error details; cleanup should still happen through finally.
  • Blocklist or allowlist restriction: Check the restrictions applied by the environment or browser setup. Increasing the navigation timeout does not remove a policy restriction.

These categories reflect documented Frame.goto() exception conditions; the specific cause in a run must be determined from its actual error and environment. Frame.goto() API reference.

Common cleanup mistakes and fixes

Closing only after successful navigation

Code that calls browser.close() on the next line after await page.goto() will not reach that line if the navigation throws. Put cleanup in finally so both the success and error paths attempt it.

Closing the wrong resource

page.close() does not mean “shut down the browser”; it ends one page. browser.close() ends the browser and its pages. For an externally managed browser, browser.disconnect() detaches the client without ending the remote session. Match the API to the resource your code owns and intends to stop.

Using an unbounded wait as a timeout fix

timeout: 0 disables the timeout rather than making navigation more reliable. Use it only when indefinite waiting is acceptable and another mechanism controls the job’s lifetime.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Replacing the original error with a cleanup error

If shutdown also rejects, make the error policy explicit: log both failures, preserve the navigation error when it is the primary failure, and ensure the shutdown failure is visible to monitoring. Avoid an empty catch that hides a browser process that may not have closed.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability and cost considerations

Cleanup belongs in the same layer that owns the browser lifecycle. Closing the whole browser ends all of its pages, while closing a page or context has a narrower effect; using the narrowest appropriate scope avoids ending work the script intends to keep. Conversely, when a script launches a browser for a single job, failing to close it after a rejected navigation can leave browser resources alive beyond that job.

There is no universal timeout value in the cited API references that guarantees fast or reliable navigation for every site. A longer limit trades a longer possible wait for fewer premature timeout failures on slow tasks; disabling it removes that bound altogether. The navigation timeout itself is not a browser cleanup mechanism, so do not rely on it to stop the launched browser. The documentation cited here does not establish a monetary cost per timeout or a performance benchmark; account for resource use and job limits according to your runtime or hosting provider.

Or skip the browser setup

If your goal is a website screenshot rather than controlling a browser session, ScreenshotNeo offers a screenshot API: one GET request can return a PNG, JPEG, WebP or PDF. For example, this cURL request saves a WebP shot:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for request options. Cookie banners are accepted before capture and 60+ known consent platforms, newsletter popups and chat widgets can be removed; each of those steps can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and response headers identify the page verdict and billing status. An MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account to try 1,000 screenshots a month with no card.

Frequently Asked Questions

Does a navigation timeout automatically close the Puppeteer browser?

No. The timeout is a navigation failure condition; your script must perform browser cleanup, such as in a finally block.

Which Puppeteer API should I use if the browser belongs to another process?

Use browser.disconnect() to detach the Puppeteer client without shutting down the externally managed browser.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.