Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Debug headless Chrome PDF output in this order: verify the browser and command actually start, prove the page is ready, then inspect print CSS, fonts, colors, and timing-dependent code. Chrome’s --print-to-pdf path and Puppeteer’s page.pdf() path fail in different ways, so record the exact browser, library, operating system, launch flags, URL, and invocation before changing settings. A blank or incomplete file is usually a readiness or rendering problem; a missing file or immediate process exit is usually startup, navigation, permissions, or command failure.
1. Identify the PDF generation path
Start by writing down the complete reproduction details. Include the installed Chrome or Chromium version, Puppeteer version if used, operating system, whether the browser runs as root or inside a container, launch arguments, URL, and the exact command or JavaScript call. Reproduce with the same build before comparing a local machine with CI.
| Path | Typical invocation | First diagnostic question |
|---|---|---|
| Chrome command line | chrome --headless --print-to-pdf=out.pdf https://example.com |
Did the process start, navigate, and write the file? |
| Puppeteer | await page.pdf({path: 'out.pdf'}) |
Did navigation and application readiness finish before page.pdf()? |
Flag names vary by Chrome release. Current command-line documentation uses --no-pdf-header-footer; older builds may use --print-to-pdf-no-header. Check the help output for the installed executable rather than copying a flag from an unrelated version.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →2. Separate startup failures from page failures
When no PDF is produced
- Run the executable directly and capture stderr. A missing binary, invalid flag, blocked display dependency, permission error, or early crash must be fixed before investigating CSS.
- Confirm the output directory is writable and that a previous file is not being mistaken for the new result. Delete the old PDF before each run and inspect its modification time and size.
- Use an absolute output path while debugging. Relative paths can resolve differently in a service, container, or worker process.
- Check the navigation URL, redirects, authentication, and certificate errors. A browser that reaches a login page or an error document can still create a valid-looking but useless PDF.
Linux sandbox errors
Puppeteer documents the Linux error No usable sandbox! when the host does not provide a usable sandbox. Fix the host or container sandbox first. Using --no-sandbox removes a security boundary and should be reserved for content you absolutely trust; it is not a general reliability switch. If you must use it temporarily, isolate the process, restrict its network and filesystem access, and restore sandboxing for production workloads.
#1 Best Overall
Collect useful startup evidence
Log the complete launch configuration, process exit code, stderr, browser version, and output-file metadata. In Puppeteer, attach listeners for page.on('console'), page.on('pageerror'), and failed requests. This distinguishes a JavaScript exception or missing asset from a browser process that never reached the page.
3. Prove the page is ready before printing
Why a fixed delay is not enough
Chrome’s --timeout waits up to a maximum real-time interval before capturing, even when loading continues. A longer value can hide a race without proving that your application finished rendering. A page may fetch data after network idleness, update a chart on a timer, or replace a loading shell after a framework-specific event.
Puppeteer readiness sequence
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
// Add only the launch flags your environment requires.
});
const page = await browser.newPage();
page.on('console', message => console.log('[console]', message.type(), message.text()));
page.on('pageerror', error => console.error('[pageerror]', error));
page.on('requestfailed', request => {
console.error('[requestfailed]', request.url(), request.failure()?.errorText);
});
await page.goto('https://example.com/report', {
waitUntil: 'domcontentloaded',
timeout: 60_000
});
await page.waitForNetworkIdle({idleTime: 500, timeout: 30_000});
// Prefer an application-owned signal over an arbitrary sleep.
await page.waitForSelector('[data-report-ready="true"]', {timeout: 30_000});
await page.pdf({
path: 'report.pdf',
printBackground: true,
format: 'A4',
displayHeaderFooter: false
});
await browser.close();
The Puppeteer PDF guide demonstrates waiting for networkidle2 and states that PDF generation waits for fonts by default. That does not guarantee that every application task is complete. Wait for the selector, event, or DOM state your application defines as ready. If no such signal exists, add one, such as a data-report-ready attribute set after data binding, image decoding, and chart rendering finish.
Free tools Windows power users keep installed
One-click scans. No signup required.
CLI timing controls
Use --timeout when you need a maximum real-time wait. Use --virtual-time-budget for pages whose JavaScript depends on timers and you want Chrome to fast-forward virtual time. These options solve different problems: a virtual-time budget is not an application-readiness check, and a real-time timeout does not force timer-driven code to complete. Validate the resulting DOM or PDF rather than assuming either flag worked.
4. Check print media CSS
Puppeteer’s page.pdf() generates a PDF with the print CSS media type. A stylesheet can therefore hide navigation, change layout, remove backgrounds, or resize content even though the screen looks correct.
Inspect print-only rules
- Search for
@media print,display: none, visibility changes, absolute positioning, page-break rules, and print-specific width or height declarations. - Check whether the main content is hidden while a print-only template is expected but never populated.
- Look for fixed-position headers, overflow containers, transforms, and viewport units that behave differently on paper-sized pages.
- Temporarily disable the print stylesheet in DevTools or remove individual rules to find the declaration that eliminates or clips content.
Compare screen and print intentionally
If the PDF should match the screen, request screen media before printing:
await page.emulateMediaType('screen');
await page.pdf({path: 'screen-media.pdf', printBackground: true});
Use this only when screen styling is the intended output. For invoices, reports, and formal documents, fixing the print stylesheet is usually more predictable than forcing screen media.
Backgrounds and color differences
PDF colors are modified for printing by default. In CSS, -webkit-print-color-adjust: exact; requests exact colors for elements where that matters, and Puppeteer’s printBackground: true includes CSS backgrounds. Test both the page’s print rules and the PDF option; enabling one does not repair a print stylesheet that deliberately uses different colors.
Rank #3
@media print {
.report {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
5. Investigate fonts and assets
Missing fonts can change line wrapping, element heights, and page breaks even when all text is present. Puppeteer says PDF generation waits for web fonts by default, but a font request can still fail because of a wrong URL, CORS policy, authentication, blocked resource, or missing system fallback.
- Inspect font requests in request-failure logs and the browser’s network output.
- Confirm every
@font-faceURL is reachable from the headless process, not just from your desktop browser. - Check that the declared font weight and style match files actually served.
- Compare a run with web fonts disabled or replaced by a known system font. A dramatic layout change confirms a font dependency.
- Wait for
document.fonts.readywhen your own rendering code changes the DOM after fonts load.
await page.evaluate(async () => {
await document.fonts.ready;
});
Images and canvases need the same treatment. A successful navigation does not mean every lazy image is decoded. Wait for the application’s image-ready signal or evaluate image completion before printing.
6. Make layout and pagination observable
When content is clipped or unexpectedly split, inspect the computed geometry immediately before page.pdf(). Record the viewport, device scale factor, paper format, margins, and orientation. A page that fits on a wide desktop viewport may overflow an A4 or Letter content box after margins are applied.
const metrics = await page.evaluate(() => ({
viewport: {width: innerWidth, height: innerHeight},
documentHeight: document.documentElement.scrollHeight,
bodyHeight: document.body.scrollHeight,
readyState: document.readyState,
title: document.title
}));
console.log(metrics);
Use CSS page-break controls deliberately. Avoid placing critical content inside a fixed-height or overflow: hidden container. For a full-page capture, ensure the document—not only a nested panel—contains the complete content.
Rank #4
7. Reduce the failure to a reproducible case
- Create a minimal local HTML file containing the smallest failing structure: one font, one image, the relevant print rule, and the suspected asynchronous update.
- Run that file with the same Chrome build, flags, viewport, and PDF options.
- Add features back one at a time: external assets, framework code, authentication, animations, and third-party widgets.
- Save the HTML, console output, failed-request list, command, versions, and generated PDF together.
This method tells you whether the cause is browser-specific, environment-specific, or part of the application. Official documentation does not provide a universal error-to-fix catalogue, so exact reproduction details are essential when escalating a Chromium or Puppeteer issue.
8. Common symptoms and targeted fixes
| Symptom | Likely branch | Action |
|---|---|---|
| No file or immediate exit | Startup, flag, permission, or sandbox | Capture stderr, verify the binary/version, use an absolute writable path, and resolve sandbox requirements. |
| Blank PDF | Navigation or readiness | Log the final URL and response status, wait for the application-ready signal, and inspect page errors. |
| Only a loading shell | Asynchronous rendering | Wait for the data-loaded selector or event instead of increasing a blind delay. |
| Screen and PDF layouts differ | Print media CSS | Inspect @media print; use emulateMediaType('screen') only when screen styling is intended. |
| Colors or backgrounds missing | Print color handling | Enable printBackground and review -webkit-print-color-adjust. |
| Text wraps or pagination changes | Font or paper geometry | Check font requests, wait for fonts, and verify format, margins, orientation, and viewport. |
| Timer content is absent | Virtual-time misunderstanding | Use a controlled virtual-time budget, then verify the DOM state; do not treat the budget as readiness proof. |
9. Performance, reliability, and cost considerations
- Reuse a browser process for multiple jobs when isolation requirements permit, but create a fresh page and clean cookies between documents.
- Set explicit navigation and readiness timeouts so a stuck request cannot consume a worker indefinitely.
- Capture console, page-error, request-failure, final URL, and output metadata for every failed job.
- Keep browser and Puppeteer versions pinned in CI. A flag or rendering behavior that works on one release may change on another.
- Do not equate a successful HTTP response or nonzero PDF size with correctness. Validate a required heading, page count, or sentinel element when the document is business-critical.
Or skip the browser setup
If you need a clean web capture rather than maintaining Chrome launch code, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers.
One request is enough to test a URL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
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)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo documentation for request options. It also offers PDF capture, full-page and element capture, device and retina settings, custom CSS and JavaScript, click and wait controls, request blocking, headers and cookies, timezone and geolocation, caching, signed links, asynchronous webhooks, bulk capture, usage reporting, and an MCP server with take_screenshot, get_page_info, and capture_pdf for AI clients.
The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan. Create a free ScreenshotNeo account.
10. A compact debugging checklist
- Record Chrome, Chromium, Puppeteer, OS, flags, URL, and output path.
- Confirm the process starts, sandbox requirements are met, and stderr is clean.
- Verify the final URL, response status, authentication state, and failed requests.
- Wait for the application-ready signal, not only a fixed delay or network idle.
- Inspect print media rules, backgrounds, colors, fonts, images, and overflow.
- Check paper size, margins, orientation, viewport, and device scale.
- Use virtual time only for timer diagnostics and validate the resulting DOM.
- Reduce the page to a minimal reproducible case and preserve all artifacts.
Frequently Asked Questions
Does a larger --timeout guarantee complete PDF content?
No. It only sets a maximum real-time wait. Application code can still finish after that point or remain blocked; wait for a page-specific ready signal and validate the output.
Best Value
Should I always launch Puppeteer with --no-sandbox?
No. It is a security-sensitive workaround for hosts without a usable sandbox. Fix the environment or isolate trusted content instead of making it a default flag.
Why does page.pdf() look different from my browser window?
Puppeteer uses the print CSS media type by default. Print rules, print color adjustment, paper geometry, and font loading can all change the result.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →What is the difference between --timeout and --virtual-time-budget?
--timeout waits in real time before capture; --virtual-time-budget fast-forwards timer-dependent JavaScript. Neither proves that application-specific rendering is complete.
Quick Recap
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.

