ScreenshotNeo

BlogHTML to image & PDF

HTML to PDF with Puppeteer on an Indian VPS: install Chromium and fonts

Install Puppeteer’s compatible browser, Linux libraries, and script fonts on an Indian VPS, then generate PDFs and fix common launch failures.

By the ScreenshotNeo team4 October 202610 min read

To generate PDFs with Puppeteer on an Indian VPS, first check the VPS distribution, CPU architecture, Node.js version, and deployment user. Install Puppeteer so it can download its compatible Chrome for Testing browser, install the required Linux libraries and fonts for your document’s scripts, then call page.pdf(). “Chromium” is often used loosely here: current Puppeteer normally manages Chrome for Testing. The VPS being in India does not change these installation steps; the provider’s image and repositories do.

This guide uses Debian or Ubuntu commands. Confirm your OS before running them, since package names and browser support vary. Puppeteer’s current system requirements list Node.js 22.12 or newer and Chrome for Testing on Debian/Ubuntu Linux for x64 and arm64. Check the [system requirements](https://pptr.dev/guides/system-requirements) and [supported browser mapping](https://pptr.dev/chromium-support/) when pinning versions.

1. Check the VPS before installing

Log in as the user that will run the PDF service and inspect the image and architecture:

cat /etc/os-release
uname -m
node --version
npm --version
whoami

x86_64 is the usual Linux name for x64; aarch64 is arm64. Check that the installed Node.js version meets Puppeteer’s current requirement. If you use containers or a process manager, remember that the runtime user may differ from the user who installed the packages. That difference affects both browser cache visibility and permissions.

2. Install Puppeteer and its browser

For the standard setup, use the puppeteer package. Its installation normally downloads a compatible Chrome for Testing browser into Puppeteer’s browser cache.

mkdir puppeteer-pdf
cd puppeteer-pdf
npm init -y
npm install puppeteer

Make a minimal script that creates a PDF from HTML. Save this as render.mjs:

import puppeteer from 'puppeteer';

const html = `
  <!doctype html>
  <html>
    <head>
      <meta charset="utf-8">
      <style>
        body { font: 16px sans-serif; margin: 24px; }
        h1 { color: #173b66; }
        @page { size: A4; margin: 18mm; }
      </style>
    </head>
    <body>
      <h1>PDF from Puppeteer</h1>
      <p>Rendered on the VPS.</p>
    </body>
  </html>`;

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: 'networkidle0' });
  await page.pdf({
    path: 'output.pdf',
    format: 'A4',
    printBackground: true,
    waitForFonts: true,
  });
} finally {
  await browser.close();
}

Run it with node render.mjs. Puppeteer’s waitUntil: 'networkidle0' waits for network activity to settle; for a page that keeps analytics or streaming connections open, use a more suitable readiness condition such as domcontentloaded plus a wait for a document-specific selector.

If npm or your deployment system suppressed package install scripts, the browser may not have been downloaded. From the project directory, run:

npx puppeteer browsers install

The browser cache defaults to $HOME/.cache/puppeteer in the current installation documentation. Ensure the same runtime user can read that cache. See the official [installation guide](https://pptr.dev/guides/installation).

3. Install Chrome’s Linux dependencies

A downloaded browser still needs shared system libraries. On Debian or Ubuntu, Puppeteer’s browser CLI can install Chrome dependencies using system package privileges:

sudo npx puppeteer browsers install --install-deps

This option is documented for Chrome on Debian or Ubuntu and invokes the system package manager. If you cannot use it, follow the up-to-date dependency list in [Puppeteer troubleshooting](https://pptr.dev/troubleshooting), rather than copying an old package list from another VPS image. For a manual diagnosis, identify unresolved shared libraries after installing the browser:

ldd /path/to/chrome | grep 'not found'

The exact executable path depends on Puppeteer’s browser cache. You can ask Puppeteer for its cache path with a short script, or find the downloaded browser under the installing user’s Puppeteer cache. The Linux dependency list includes packages such as fonts-liberation, GTK, NSS, fontconfig, and Pango; package names can differ across OS releases.

4. Install fonts for the document’s scripts

Missing fonts commonly show up as blank squares, substituted glyphs, or broken line wrapping. Install fonts that cover the scripts your PDFs actually contain. The browser’s general Linux dependencies do not guarantee coverage for every Indian language. Hindi in Devanagari, Bengali, Tamil, Telugu, Malayalam, Kannada, Gujarati, Punjabi, and other scripts require suitable font coverage; test the exact languages and symbols in your documents.

On Ubuntu or Debian, inspect available font packages and install the packages appropriate for the image and required script. For example, the package fonts-liberation is among Puppeteer’s listed Linux dependencies, but it is not a universal Indian-language font solution:

sudo apt-get update
sudo apt-get install -y fonts-liberation fontconfig
fc-list : family | sort -u | head -80
fc-match 'sans-serif'

Choose a font with the needed glyphs and make it available to the runtime image. Validate coverage rather than assuming that a font family name means every glyph is present. Ensure your HTML declares an appropriate fallback stack and that web fonts have finished loading before capture. page.pdf() currently defaults waitForFonts to true, but the font files must still exist and load successfully on the server.

await page.setContent(html, { waitUntil: 'networkidle0' });
await page.evaluate(() => document.fonts.ready);
await page.pdf({ path: 'output.pdf', format: 'A4', waitForFonts: true });

5. Configure the PDF output

Page.pdf() renders using print CSS. Its options let you set paper size, custom dimensions, margins, page ranges, landscape orientation, backgrounds, and whether CSS page size takes priority. Read the current [PDFOptions reference](https://pptr.dev/api/puppeteer.pdfoptions) because option details can change.

await page.pdf({
  path: 'report.pdf',
  format: 'A4',                 // e.g. A4, Letter, Legal
  landscape: false,
  printBackground: true,        // include CSS background colors/images
  preferCSSPageSize: true,      // honor @page size when present
  displayHeaderFooter: false,
  margin: {
    top: '18mm',
    right: '16mm',
    bottom: '18mm',
    left: '16mm',
  },
  pageRanges: '1-3',
  waitForFonts: true,
});
  • Paper: set format for a standard size, or provide dimensions with width and height. When CSS @page dimensions should win, set preferCSSPageSize: true.
  • Margins: use CSS lengths such as mm, in, or px. Coordinate CSS margins and API margins to avoid surprising whitespace.
  • Backgrounds: set printBackground: true if colors or background images carry meaning. Print behavior may also be styled with @media print.
  • Page selection: use pageRanges for a subset, for example '1-2, 5'. Invalid or out-of-range pages can produce errors or no useful output; validate ranges against document length.
  • Header and footer: enable displayHeaderFooter and supply templates when page numbers or labels are needed. Templates have restrictions; consult the PDF options documentation.

For a remote URL, navigate to it and wait for the application to reach a PDF-ready state. A successful navigation response does not guarantee that client-side rendering, images, or fonts are complete. Wait for a stable selector or app-specific event where needed. Avoid waiting indefinitely for network idle on sites with persistent connections.

6. Choose the right browser installation strategy

Setup Use when Configuration Tradeoff
puppeteer You want Puppeteer to manage its compatible browser. Usually launch without an executable path. Browser download must run and cache must persist or be recreated in deployment.
puppeteer-core Your application supplies a browser separately. Set executablePath to the installed browser. You own version compatibility, package dependencies, and updates.
Distro Chromium Your OS provides a browser package and you deliberately use it. Set the explicit executable path and check Puppeteer compatibility. The distro browser may not match Puppeteer’s expected browser version.

From Puppeteer v20 onward, its managed browser is Chrome for Testing. The words “install Chromium” in a VPS checklist may refer to a separately packaged Chromium; do not assume that browser is interchangeable without checking compatibility. The [supported browsers page](https://pptr.dev/chromium-support/) maps Puppeteer versions to browser versions.

import puppeteer from 'puppeteer-core';

const browser = await puppeteer.launch({
  executablePath: '/usr/bin/chromium', // verify this path on the image
  headless: true,
});

Use the executable path provided by the actual image. Do not assume every distribution names the binary chromium or installs it at the same path.

7. Keep the browser sandbox enabled

Do not make --no-sandbox a default deployment flag. Puppeteer recommends Chrome’s sandbox. If Chrome exits during launch, diagnose the actual permissions, container policy, and operating system. Puppeteer documents a user-namespace/AppArmor issue on Ubuntu 23.10 and newer that can affect Chrome for Testing. Its [troubleshooting guide](https://pptr.dev/troubleshooting) points to the upstream Chromium guidance; the remedy depends on the VPS image and security policy.

Run the service under a dedicated non-root user where your deployment allows it, ensure that user can access the browser cache and temporary directories, and inspect browser stderr when launch fails. If a provider’s image imposes restrictions, resolve those specifically rather than disabling sandbox protections without understanding the consequence.

8. Deployment, performance, and reliability

  • Provision browser artifacts deliberately. A clean deploy must either install the browser during build or run Puppeteer’s browser install command. Do not rely on a developer laptop’s browser cache being present on the VPS.
  • Keep runtime and cache users aligned. Installing as root but running as another account can leave the browser absent from the runtime user’s cache or inaccessible.
  • Limit concurrency. Every browser and page consumes memory and CPU. Start with bounded parallel work and measure the VPS under representative documents before increasing workers.
  • Reuse carefully. Reusing a browser process can reduce startup overhead, but isolate pages and close them after each job. Restart a browser process after crashes or sustained resource growth.
  • Set timeouts and clean up. Bound navigation and job duration, close pages and browsers in finally paths, and remove temporary files according to your retention needs.
  • Use representative readiness checks. Wait for the document’s content and fonts, not just a fixed sleep. Remote APIs and slow assets can make a fixed delay unreliable.
  • Budget disk and memory. Browser downloads take substantial disk space, and parallel PDF rendering needs memory headroom. The precise needs depend on document complexity and concurrency; no single VPS size is right for all workloads.
  • Pin and update deliberately. Lock npm dependencies and control browser changes in deployment. When upgrading Puppeteer or changing the browser image, regenerate representative PDFs and inspect script glyphs, page breaks, and background output.

9. Troubleshooting

Symptom Likely cause Fix
Could not find Chrome Install scripts were blocked, browser download failed, or runtime user has a different cache. Run npx puppeteer browsers install as the deployment user, then verify cache path and access.
error while loading shared libraries or browser exits immediately Missing OS libraries or incompatible image packages. On Debian/Ubuntu use the browser CLI --install-deps with privileges, or diagnose the current dependency list; inspect unresolved libraries with ldd.
Browser launch fails only on Ubuntu 23.10+ AppArmor/user namespace policy can affect Chrome for Testing sandbox startup. Follow the current Puppeteer troubleshooting guidance for the exact image. Avoid reflexively adding --no-sandbox.
PDF has squares or missing characters Installed fonts lack glyph coverage for the document’s script, or a web font did not load. Install a font with the required glyphs, check fc-match, wait for document.fonts.ready, and inspect browser/network errors.
Colors or background images are absent PDF printing omits backgrounds by default. Set printBackground: true and review print CSS.
Wrong paper size or unexpected whitespace API format, CSS @page, and margins conflict. Choose which page-size source controls output; configure preferCSSPageSize, paper dimensions, and margins together.
Content is cut off or missing Capture happened before client rendering/assets completed, or page range excluded content. Wait for a document-specific ready selector and fonts; verify page range and print styles.
Works locally but not on VPS Different architecture, Node version, runtime user, cache, OS libraries, or fonts. Compare /etc/os-release, uname -m, Node version, executable path, cache ownership, and font inventory in the deployed environment.

10. Or skip the browser setup

If you need a PDF from a URL without maintaining a Puppeteer browser on your VPS, ScreenshotNeo provides a website screenshot API and MCP server. Its PDF capture options include paper size, margins, landscape, and page ranges. Use the API base with an access key:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.pdf

See the ScreenshotNeo API documentation for request options. Cookie banners, newsletter popups, and chat widgets are removed before the shot; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card required.

FAQ

Does the VPS need to be located in India?

No. The setup depends on the operating system, architecture, package access, and network access. This guide does not assume a particular Indian provider or region.

Should I install Chromium with apt or let Puppeteer download Chrome?

Use Puppeteer’s managed browser for the standard puppeteer setup. Use a separately installed browser only when you intentionally manage its path and compatibility.

Will one font package cover every Indian language?

No. Install fonts based on the scripts and glyphs your documents need, then inspect actual PDF output.

Can I use this setup to print any website?

Many pages can be rendered, but authenticated pages, bot checks, dynamic content, and site-specific print styles may require additional handling and permission to access.

References