Resolving TimeoutError: Navigation timeout exceeded in Puppeteer
Learn how to diagnose and fix 'TimeoutError: Navigation timeout exceeded' in Puppeteer by optimizing waitUntil states and timeout settings.
14 Nov 2025, 16:46 UTC

The Problem: Navigation Timeouts
When using page.goto(), Puppeteer defaults to a 30-second timeout. If the page does not reach the required loading state within this window, the script throws a TimeoutError: Navigation timeout exceeded and terminates. This often happens not because the server is down, but because the browser is waiting for a specific event—like a heavy tracking script or a WebSocket connection—that never signals completion.
Diagnostic Matrix
Use this table to match your observed behavior to the likely cause before applying fixes.
| Symptom | Likely Cause | Primary Diagnostic |
|---|---|---|
| Timeout occurs consistently across all URLs | Network/Proxy failure or DNS issues | Test URL accessibility via curl or a standard browser |
| Timeout occurs only on heavy, JS-rich pages | WaitUntil state never reached (e.g., constant polling) | Run in headful mode to see if the page is actually rendered |
| Timeout occurs intermittently on slow connections | Default 30s limit is insufficient for server response | Check server response headers for TTFB (Time to First Byte) |
| Immediate timeout or 403/401 errors | Headless detection or Bot blocking | Compare results between headless: true and headless: false |
Step-by-Step Resolution Path
Follow these checks in order. Do not increase the timeout limit until you have verified the waitUntil state.
1. Adjust the Navigation State
Puppeteer's waitUntil option determines when the navigation is considered "finished." Choosing the wrong state is the most common cause of timeouts.
load: (Default) Waits for theloadevent. Fails if a single image or iframe hangs.domcontentloaded: Waits for the HTML to be parsed. Fastest, but may miss async content.networkidle0: Waits until there are no more than 0 network connections for 500ms. Risk: Will timeout if the page has a persistent WebSocket or polling heartbeat.networkidle2: Waits until there are no more than 2 network connections for 500ms. Recommended for pages with background analytics.
2. Modify the Timeout Duration
If the page is simply slow, you can increase the limit. You can do this globally for the page instance or specifically for a single request.
// Option A: Global limit for all navigations on this page
await page.setDefaultNavigationTimeout(60000); // Set to 60 seconds
// Option B: Specific limit for one navigation
await page.goto('https://example.com', {
timeout: 60000,
waitUntil: 'networkidle2'
});
3. Handle Headless Detection
Some servers detect the HeadlessChrome user agent and drop the connection without closing it, leading to a timeout. Test this by modifying the user agent to mimic a real browser.
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36');
Practical Comparison: State Impact
Consider a page that loads a main dashboard but keeps a WebSocket open for live stock tickers. The following table shows how different configurations behave:
| Configuration | Result | Reason |
|---|---|---|
{ waitUntil: 'networkidle0' } |
TimeoutError | The WebSocket connection prevents network activity from ever hitting zero. |
{ waitUntil: 'networkidle2' } |
Success | Allows the WebSocket to remain open while considering the page loaded. |
{ waitUntil: 'domcontentloaded' } |
Success (Fast) | Triggers as soon as the DOM is ready, ignoring all network activity. |
Verification and Limitations
To verify the fix, wrap your navigation in a try-catch block and log the time taken. If the error is resolved, the catch block will be bypassed.
Warning: Avoid setting timeout: 0. While this disables the timeout entirely, it can cause your CI/CD pipeline to hang indefinitely if a page becomes unresponsive, consuming runner credits and blocking deployments.
Escalation Criteria
If the following conditions are met, the issue is likely external to Puppeteer's configuration:
- The page timeouts even with
waitUntil: 'domcontentloaded'and a 120s timeout. - The URL is unreachable via
curl -I [URL]from the same environment. - The site works in a standard browser but fails in Puppeteer regardless of User-Agent changes (suggests advanced TLS fingerprinting or CAPTCHA blocking).
Rollback
Since these changes only affect the script's execution parameters and not the system state, rollback consists of reverting the waitUntil and timeout values to their defaults ('load' and 30000ms respectively).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.