Why Pyppeteer Freezes After Launching Chrome and How to Fix It
Find where Pyppeteer stalls, enable diagnostics, check browser pairing and sandbox settings, and choose a reliable fix.

Short answer: a Chrome process appearing in the process list does not prove Pyppeteer completed its DevTools connection or created a page. Add markers around every await, enable debug logs, record versions and the executable path, then determine whether the stall is in launch(), browser.newPage(), page.goto(), or a later wait. Use a compatible browser binary, treat --no-sandbox only as a temporary security-sensitive diagnostic, and consider the Playwright Python migration path because the Pyppeteer project describes itself as unmaintained.
Find the exact await that stalls
Start with a minimal instrumented script. Flush each message so buffered output cannot hide the stopping point.

import asyncio
from pyppeteer import launch
async def main():
print("before launch", flush=True)
browser = await launch()
print("after launch", flush=True)
page = await browser.newPage()
print("after newPage", flush=True)
response = await page.goto(
"https://example.com",
{"waitUntil": "domcontentloaded", "timeout": 30000},
)
print("after goto", response.status if response else None, flush=True)
await browser.close()
asyncio.get_event_loop().run_until_complete(main())
If the last line is before launch, inspect the browser executable, permissions, downloaded Chromium and process startup. If it is after launch, focus on the DevTools connection and target creation. If it is after newPage, investigate navigation and wait conditions. A navigation timeout is a different failure from a page-creation stall.
Enable Pyppeteer diagnostics
The API reference documents debug logging with logLevel=logging.DEBUG for launch() and connect(). It also documents pyppeteer.DEBUG = True for errors that would otherwise be suppressed. Keep the lines around the final successful marker and the first warning or error.
import asyncio
import logging
import pyppeteer
from pyppeteer import launch
pyppeteer.DEBUG = True
async def main():
browser = await launch(logLevel=logging.DEBUG)
page = await browser.newPage()
await page.goto("https://example.com", {"timeout": 30000})
await browser.close()
asyncio.get_event_loop().run_until_complete(main())
See the Pyppeteer API reference for launch and navigation options. The Pyppeteer repository describes the bundled Chromium pairing and current maintenance status.
Record the environment before changing it
Save these values from the failing machine:
- Operating system and release.
- Python and Pyppeteer versions.
- Chrome or Chromium version and the full executable path.
- Local desktop, container, CI runner or service environment.
- Headless or headful mode.
- Whether the browser runs as root or as an unprivileged user.
Issue #441 is a useful symptom match: a reporter on Fedora 37, Python 3.11 and Chrome 115.0.5790.3 said browser.newPage() hung after launch. That report is environment-specific evidence, not a universal diagnosis.
Check the browser executable and version pairing
Pyppeteer supports executablePath. Its project documentation says it works best with the Chromium version it bundles, so compare the bundled browser with any system Chrome or Chromium one variable at a time.
import asyncio
from pyppeteer import launch
async def main():
browser = await launch(
executablePath="/path/to/chrome-or-chromium",
headless=True,
)
page = await browser.newPage()
await page.goto("https://example.com", {"timeout": 30000})
await browser.close()
asyncio.get_event_loop().run_until_complete(main())
Use a path that exists on the target system. In issue #441, a commenter reported success on Fedora 38 with Python 3.11 after selecting an OS-installed Chrome binary through executablePath. The original reporter had not confirmed a general resolution, so do not assume this change fixes every installation.
Sandbox flags: a diagnostic, not a default
A commenter in issue #441 also reported that disabling the Linux sandbox worked in that setup and explicitly warned that it is less safe. Do not add --no-sandbox automatically because Chrome starts or because a page hangs.
browser = await launch(
args=["--no-sandbox"], # temporary diagnostic only
)
If you test this, use it only to distinguish an environment or permission problem, document the result, and assess the security impact before processing untrusted pages or running in a shared service. Prefer configuring the sandbox correctly. The upstream Puppeteer Linux troubleshooting guide provides context, but its instructions may not map exactly to every Pyppeteer and Chromium version.
Separate page creation from navigation
Once newPage() succeeds, navigation can still wait indefinitely if the URL is slow, blocked or never reaches the selected event. Pyppeteer documents load, domcontentloaded and network-idle choices through waitUntil, plus navigation timeouts.
response = await page.goto(
"https://example.com",
{
"waitUntil": "domcontentloaded",
"timeout": 30000,
},
)
| Symptom | Likely area | Next action |
|---|---|---|
Stops before after launch |
Process startup, executable, permissions | Enable logs; verify binary and user |
Stops before after newPage |
DevTools connection or target initialization | Compare browser versions; inspect sandbox and environment |
Stops before after goto |
Navigation, DNS, page response or wait event | Set a finite timeout and choose an appropriate waitUntil |
Target closed |
Browser or target exited | Inspect browser stderr, headless/headful mode and resource limits |
Common errors and fixes
browser.newPage() never returns
Confirm the final marker, turn on debug logging, and compare the bundled Chromium with a known installed binary using executablePath. Check sandbox permissions before trying a temporary diagnostic flag.
TimeoutError from page.goto()
This is a navigation timeout, not proof that launch froze. Check DNS, proxy and page availability; use a finite timeout and change waitUntil from network-idle to domcontentloaded when the page keeps connections open.
Target closed
Issue #435 shows a separate headful failure report with Pyppeteer 1.0.2. Inspect browser logs, test headless and headful independently, and check memory, permissions and the selected executable. The report does not establish a universal headless or headful fix.
Chrome starts but the script is silent
Output may be buffered. Use flush=True, run Python unbuffered with python -u, and place markers immediately before and after each await.
It works locally but fails in CI or a container
Compare the user account, filesystem permissions, shared memory, sandbox support, installed libraries, executable path and headless setting. Reproduce with the same image and versions before changing application code.
Reliability and performance checklist
- Pin and record Python, Pyppeteer and browser versions.
- Use explicit timeouts for navigation and selector waits.
- Reuse one browser process for multiple pages when isolation requirements allow it; close pages and browsers in
finallyblocks. - Limit concurrent pages to the memory and CPU available in the deployment.
- Capture browser stderr and Pyppeteer debug logs with the job ID.
- Choose the smallest wait condition that proves the page is ready.
- Do not treat retries as a fix for a deterministic version or sandbox mismatch.
import asyncio
from pyppeteer import launch
async def capture(url):
browser = await launch()
try:
page = await browser.newPage()
await page.goto(url, {"waitUntil": "domcontentloaded", "timeout": 30000})
return await page.screenshot({"path": "shot.png", "fullPage": True})
finally:
await browser.close()
asyncio.get_event_loop().run_until_complete(capture("https://example.com"))

When to move to Playwright Python
The Pyppeteer repository calls the project unmaintained and says, “Please consider playwright-python as an alternative.” Treat migration as a maintenance decision, not proof that Playwright fixes every environment-specific freeze. List the APIs you use, port a small workflow, and validate browser downloads, navigation waits, screenshots and deployment permissions before switching production traffic.
Or skip the browser setup
If your goal is a reliable screenshot rather than maintaining Chrome, ScreenshotNeo provides a GET request that returns PNG, JPEG, WebP or PDF. Cookie and consent banners are accepted before capture, and more than 60 known consent platforms, newsletter popups and chat widgets are removed. Bot checks, blank pages, failed loads, timeouts and cache hits are not billed; response headers identify the page verdict and billing result. Its MCP server provides take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.
See the ScreenshotNeo API documentation for all options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
There are 1,000 free screenshots each month with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Does a visible Chrome process mean Pyppeteer launched successfully?
No. The process can exist while the DevTools connection or page target initialization is still blocked.
Should I always use --no-sandbox?
No. It reduces isolation. Use it only as a controlled diagnostic and assess the deployment risk.
Is an installed Chrome better than bundled Chromium?
Not generally. Pyppeteer says its bundled Chromium is the best-supported pairing, while one issue commenter reported an installed binary solving a specific Fedora setup.
Will changing waitUntil fix a newPage() hang?
No. waitUntil affects navigation after page creation, so first prove which await is stopping.


