Puppeteer Screenshot on an Indian VPS: Install Chrome on Debian
Install a compatible Chrome for Testing build, add Debian’s browser dependencies, and diagnose Puppeteer launch failures on an Indian VPS.
Direct answer: On a Debian VPS, install Node.js and the project package puppeteer. Its install process ordinarily downloads a compatible Chrome for Testing browser. Install Chrome’s required shared libraries, then run Puppeteer as the same deployment user that can access the browser cache. The server being in India does not require a special Chrome package or different documented dependencies.
This guide uses a Node.js script to capture a screenshot, covers the managed-browser and self-managed-browser installation paths, and gives a diagnostic sequence for common Linux launch failures. Puppeteer’s current system requirements list Node.js 22.12+ and Debian/Ubuntu x64 and arm64 for Chrome for Testing; verify requirements and browser compatibility for the Puppeteer version you deploy. Puppeteer system requirements · Supported browsers and version mapping.
1. Check the Debian VPS before installing
Run these commands over SSH to record the operating system, CPU architecture, and Node.js version:
cat /etc/os-release
uname -m
node --version
npm --version
The system requirements page lists Debian/Ubuntu on x64 and arm64, and Node.js 22.12 or newer for the current guide. If your Node.js version or architecture differs, check the requirements for the specific Puppeteer release you intend to use before proceeding.
Also decide which Unix account will run the screenshot job. Puppeteer downloads the browser into a cache associated with the install environment. If you install as one user and run as another, the runtime account may not be able to find or execute that browser. Keep the installation and runtime user consistent, or deliberately configure a shared browser cache.
2. Install Puppeteer and its compatible Chrome
For most projects, use puppeteer. It ordinarily downloads a compatible Chrome for Testing browser during installation, so you do not have to select an arbitrary system Chrome version. The Linux browser download is approximately 282 MB, so allow for download time and disk space during deployment. Puppeteer installation guide.
mkdir -p ~/puppeteer-shot
cd ~/puppeteer-shot
npm init -y
npm install puppeteer
If your package manager or deployment policy disables install scripts, the package can install without downloading its browser. Install it explicitly from the project directory:
npx puppeteer browsers install
On Debian or Ubuntu, Puppeteer’s browser CLI also provides a route that installs Chrome and attempts to install the operating system dependencies:
sudo npx puppeteer browsers install chrome --install-deps
The --install-deps option requires root privileges because it installs OS packages. If you do not want the CLI to manage packages, install the required shared libraries through Debian’s package manager instead, using Puppeteer’s dependency list and the diagnostics in section 5.
3. Capture a screenshot with Puppeteer
Create screenshot.js in the project directory. This runnable example launches the downloaded browser, waits for the page to load, captures the full page, and closes the browser even if navigation or capture fails.
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({
headless: true,
// Keep Puppeteer's default sandbox settings unless your environment
// has a specifically diagnosed sandbox problem.
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 1000, deviceScaleFactor: 1 });
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 60000,
});
await page.screenshot({ path: 'screenshot.png', fullPage: true });
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run it as the same account that installed Puppeteer:
node screenshot.js
For a page that keeps long-lived network connections open, networkidle2 may never be a useful readiness signal. Wait for a page-specific selector instead:
await page.goto('https://example.com', { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.waitForSelector('main', { timeout: 15000 });
await page.screenshot({ path: 'screenshot.png', fullPage: true });
Replace main with an element that appears when the content you need is ready. A fixed delay can be used for a known animation or delayed widget, but selector-based waits are usually more reliable than guessing a delay.
4. Choose between puppeteer and puppeteer-core
| Package | Browser management | Use it when |
|---|---|---|
puppeteer |
Ordinarily downloads a compatible Chrome for Testing browser. | You want Puppeteer to manage its matching browser for a project. |
puppeteer-core |
Does not download Chrome. | You manage the browser yourself or connect to a separately managed browser. |
With puppeteer-core, install a browser that matches the Puppeteer release’s supported-browser mapping, then provide its executable path when launching:
npm install puppeteer-core
# Set CHROME_PATH to the actual Chrome executable path on this VPS.
CHROME_PATH=/path/to/chrome node core-screenshot.js
const puppeteer = require('puppeteer-core');
async function main() {
const executablePath = process.env.CHROME_PATH;
if (!executablePath) {
throw new Error('Set CHROME_PATH to the Chrome executable');
}
const browser = await puppeteer.launch({
executablePath,
headless: true,
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
await page.screenshot({ path: 'screenshot.png', fullPage: true });
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Do not assume that any installed Chrome will work with any Puppeteer release. Check the supported-browser mapping when you choose or update either component. The installation guide documents executable-path and browser-channel configuration for cases where you manage Chrome yourself.
5. Install and diagnose Debian’s shared libraries
A browser can be downloaded correctly and still fail to start because Debian is missing a shared library. Puppeteer’s troubleshooting guide lists common Debian/Ubuntu dependencies and recommends checking unresolved libraries with ldd. Puppeteer troubleshooting guide.
Find the Chrome executable used by Puppeteer, then check its dependencies. The following command asks Puppeteer’s browser CLI for its installed Chrome path:
npx puppeteer browsers list
Use the listed executable path in the diagnostic command:
ldd /path/to/chrome | grep not
If the command reports missing libraries, install the corresponding Debian packages using the current Puppeteer troubleshooting dependency list or the documented --install-deps route. Then rerun ldd; no output from the filtered command means it found no unresolved shared libraries.
Check file and cache access as the runtime account, not only as an administrator:
whoami
ls -ld ~/.cache/puppeteer
find ~/.cache/puppeteer -type f -name chrome -print
The precise cache location can vary with configuration and Puppeteer version. If you set a custom cache directory during installation, make the same configuration available to the screenshot process and ensure that account can read and execute the browser files.
6. Troubleshoot common failures
| Symptom | Likely cause | What to check or do |
|---|---|---|
| “Could not find Chrome” or browser executable missing | Install scripts were blocked, the browser was not downloaded, or runtime and install users have different caches. | From the project directory run npx puppeteer browsers install. Confirm the runtime account can access the browser cache; see the installation guide. |
| “error while loading shared libraries” | A Debian shared-library dependency is absent. | Run ldd /path/to/chrome | grep not, then install the missing Debian packages from the troubleshooting dependency list. |
| Chrome exits immediately or reports a launch error | Missing dependencies, inaccessible executable or cache, incompatible browser pairing, or an environment-specific sandbox restriction. | Check the executable path, file permissions, cache access, and Puppeteer/browser mapping. Read the full launch error before changing sandbox settings. |
| Works over SSH but fails under a service manager | The service may use another account, environment, working directory, or cache location. | Run the process as the intended service account and explicitly configure its working directory and any browser-cache environment settings. Confirm that account can execute Chrome. |
| Navigation times out on a particular site | The page may continue making requests, be slow to respond from the VPS, or wait on resources unrelated to the screenshot. | Try domcontentloaded followed by a meaningful selector wait; set an appropriate navigation timeout and inspect the destination’s response and page behavior. |
| Screenshot is blank or missing lazy-loaded content | The page was captured before its content rendered or before below-the-fold images loaded. | Wait for a page-specific selector or application-ready condition, and scroll through the page before capture if the target lazy-loads content as it enters the viewport. |
| “No usable sandbox” or sandbox-related startup failure | The VPS environment may impose a particular Linux sandbox or AppArmor restriction. | Diagnose the specific environment and consult Puppeteer’s Linux troubleshooting notes. Changing sandbox settings reduces browser isolation; treat it as a narrowly scoped workaround, not a standard install step. |
Do not add --no-sandbox as a routine fix. First establish that the error is specifically caused by the environment’s sandbox configuration and review the security implications for the pages the process will visit.
7. Make screenshots more reliable and efficient
- Pin and review dependencies: deploy a lockfile and update Puppeteer deliberately. When Puppeteer changes, verify the browser it expects rather than relying on an unrelated system Chrome.
- Reuse a browser for a small batch: launching Chrome for every URL adds startup work. Reuse a browser process where appropriate, create a fresh page per task, and close pages when finished. Restart the browser periodically in long-running workers to limit resource accumulation.
- Set timeouts and close resources: use navigation and selector timeouts suited to the pages you capture, and put
browser.close()in afinallyblock so failed captures do not leave browser processes behind. - Wait for the content you need: a page load event does not guarantee that a single-page app has finished rendering. Prefer an application-specific selector or ready condition over a long fixed sleep.
- Budget disk and memory: the Linux browser download is about 282 MB, in addition to the operating system, Node project, and captured files. Concurrent browser pages also use memory; start with a small worker concurrency and observe the VPS under the real workload.
- Separate setup from runtime: install the browser and dependencies during image or server provisioning, then have the service verify that its runtime user can find and execute Chrome. This avoids a browser download on every job.
- Do not infer VPS sizing or latency from location: the research does not establish a provider, instance size, price, or India-specific latency recommendation. Measure your own workload and destination sites.
8. Or skip the browser setup
If you only need a screenshot and do not want to provision Chrome or maintain browser dependencies on a VPS, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a screenshot or PDF. See the ScreenshotNeo API documentation for request options and response details.
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,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
f.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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then((fs) => fs.writeFile('shot.webp', image));
ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed; response headers indicate 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 screenshots a month with no card, and paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
9. Frequently asked questions
Does an Indian VPS need a different Chrome build?
The documented setup is based on Debian or Ubuntu, CPU architecture, Node.js, and Puppeteer/browser compatibility. The VPS’s Indian location does not change the documented OS dependencies.
Can I install Chrome with apt and point Puppeteer at it?
You can manage a browser yourself with puppeteer-core and an explicit executable path. Check that the browser version is supported by your Puppeteer release before using that pairing.
Is networkidle2 required for screenshots?
No. Choose a readiness condition that matches the target page. For apps with persistent network activity, a selector or application-ready condition may be more suitable.
Can I run the screenshot script without a display server?
The example launches Chrome in headless mode, so it does not ask for a visible browser window. Chrome still needs its runtime dependencies and a working launch environment on the VPS.


