ScreenshotNeo

BlogHow-to

How to Run a Scheduled Playwright Screenshot Job on an Indian VPS

Install Playwright on a Linux VPS, capture screenshots on a schedule, and keep the job reliable with explicit paths, logging, and troubleshooting.

By the ScreenshotNeo team4 October 20269 min read

To run a recurring Playwright screenshot job on an Indian VPS, install Node.js and the project dependencies, install the matching Chromium browser and Linux libraries, save screenshots to an absolute path, verify the script as the job’s service account, then schedule it with cron or a systemd timer. Playwright runs headlessly by default, so a desktop session is not required. [Playwright CI documentation]

The same setup works on a Linux VPS in India as elsewhere; choose the host, region, operating system, and schedule based on your requirements. Confirm the VPS timezone before setting a calendar schedule. This guide does not assume a particular provider, India region, server price, or distribution.

1. Prepare the VPS and project

Use a supported Node.js installation method for your Linux distribution. Put the screenshot job in a dedicated project directory and commit a lockfile so package versions are repeatable. The commands below assume an npm project with package-lock.json.

mkdir -p /opt/site-capture /var/lib/site-capture /var/log/site-capture
cd /opt/site-capture
# Put package.json, package-lock.json, and the script here
npm ci
npx playwright install --with-deps chromium

--with-deps asks Playwright to install the browser’s Linux system dependencies along with Chromium. Playwright browser binaries are tied to Playwright releases, so rerun the browser installation command after updating the Playwright package. See the official browser installation documentation.

If the job only needs Chromium’s headless shell, Playwright documents this install variant:

npx playwright install --with-deps --only-shell

Choose the installation that matches your use of Chromium. Keep your package lockfile under version control and deploy with npm ci so the installed Playwright version is predictable.

2. Write a screenshot script with explicit behavior

Create /opt/site-capture/capture.js. This runnable example takes a full-page PNG, uses a fixed viewport, applies a finite navigation timeout, and always closes the browser. Replace the target URL with the page you are allowed to capture.

const { chromium } = require('playwright');

(async () => {
  let browser;
  try {
    browser = await chromium.launch();
    const page = await browser.newPage({
      viewport: { width: 1440, height: 900 },
      deviceScaleFactor: 1,
    });

    const response = await page.goto('https://example.com', {
      waitUntil: 'domcontentloaded',
      timeout: 45_000,
    });

    if (!response || !response.ok()) {
      const status = response ? response.status() : 'no response';
      throw new Error(`Navigation failed: ${status}`);
    }

    await page.screenshot({
      path: '/var/lib/site-capture/latest.png',
      fullPage: true,
      animations: 'disabled',
    });
  } catch (error) {
    console.error(error);
    process.exitCode = 1;
  } finally {
    if (browser) await browser.close();
  }
})();

domcontentloaded waits for the document to be parsed without requiring all network activity to stop. For a page whose visible content is rendered later, wait for a meaningful selector before capturing:

await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.locator('[data-ready="true"]').waitFor({ state: 'visible', timeout: 30_000 });
await page.screenshot({ path: '/var/lib/site-capture/latest.png', fullPage: true });

Choose a readiness condition that reflects the page. networkidle can be convenient for pages that settle, but analytics, polling, and persistent connections may keep network activity alive. A specific selector is often more reliable for an application page. Add a deliberate short delay only when the page has a known delayed effect such as an animation or client-side update.

Viewport or full page

By default, the screenshot covers the visible viewport. Set fullPage: true to capture the page’s full scrollable height. Playwright’s screenshot API also accepts image format, quality for JPEG, scale, and other screenshot options; consult the Page screenshot API.

Choice Use it when Trade-off
Viewport You need a consistent above-the-fold image. Content below the viewport is omitted.
Full page You need a complete page image for review or archiving. Very long pages can produce large images and use more memory.
PNG Text and sharp edges should remain lossless. Files can be larger than lossy formats.
JPEG or WebP Smaller output matters and the consumer supports the format. Compression or downstream compatibility may matter.

Use deviceScaleFactor: 1 for a one-to-one CSS pixel capture. A higher device scale factor produces more image pixels and can increase output size and memory use. Set viewport and scale explicitly so runs are comparable.

3. Verify as the scheduled job account

Run the script manually as the same Linux user that will own the scheduled job. Avoid running the browser as root for convenience. Give that user write access to the project, screenshot directory, and log location it needs, and no broader access than necessary.

cd /opt/site-capture
node /opt/site-capture/capture.js
ls -lh /var/lib/site-capture/latest.png
file /var/lib/site-capture/latest.png

Confirm the file exists, can be read by the intended consumer, and is written to the expected absolute path. Also confirm that rerunning the script replaces the intended output rather than creating files in an accidental working directory.

4. Schedule the job with cron or systemd

Choose one scheduler. Cron is a compact choice for a simple recurring command. A systemd service and timer are useful when you want the job represented as managed units and need service lifecycle behavior. Distribution configuration varies; check the documentation for the Linux distribution on the VPS.

Option A: cron

Find the absolute Node executable path with command -v node while using the same Node installation intended for the job. Create /opt/site-capture/run.sh:

#!/bin/sh
set -eu
cd /opt/site-capture
exec /usr/bin/node /opt/site-capture/capture.js

Replace /usr/bin/node with the path returned on your VPS. Make the wrapper executable and create the log file with suitable ownership:

chmod 750 /opt/site-capture/run.sh
touch /var/log/site-capture/capture.log
chmod 640 /var/log/site-capture/capture.log

Edit the crontab for the job account with crontab -e. This example runs every day at 06:30 according to the cron daemon’s timezone:

30 6 * * * /opt/site-capture/run.sh >> /var/log/site-capture/capture.log 2>&1

Check the host timezone and cron implementation before relying on that time. The VPS may use UTC or another timezone. Use log rotation appropriate to the distribution so a recurring job does not fill the disk.

Option B: systemd service and timer

Create /etc/systemd/system/site-capture.service, substituting the account and group that own the job:

[Unit]
Description=Capture a website screenshot

[Service]
Type=oneshot
User=sitecapture
Group=sitecapture
WorkingDirectory=/opt/site-capture
ExecStart=/usr/bin/node /opt/site-capture/capture.js
TimeoutStartSec=90
StandardOutput=journal
StandardError=journal

Create /etc/systemd/system/site-capture.timer for a daily run at 06:30. The calendar timezone should be verified on the host; use an explicit supported timezone if the intended schedule must follow a named local timezone.

[Unit]
Description=Run the website screenshot job daily

[Timer]
OnCalendar=*-*-* 06:30:00
Persistent=true

[Install]
WantedBy=timers.target

Load and enable the timer, then inspect its status and logs:

sudo systemctl daemon-reload
sudo systemctl enable --now site-capture.timer
systemctl list-timers site-capture.timer
sudo systemctl start site-capture.service
sudo journalctl -u site-capture.service

Persistent=true requests a catch-up activation after downtime for a calendar timer. Confirm whether that behavior is appropriate for your capture job. Set the service timeout to suit the page and VPS; a timeout that is too short can kill valid captures, while no external limit can leave a stuck job consuming resources.

5. Keep recurring captures reliable

  • Run one browser job at a time on a small VPS. Measure resource use before adding concurrency. Playwright’s CI guidance recommends one worker for stability and reproducibility in CI; that is useful operational guidance for a constrained recurring job too. [Playwright CI guidance]
  • Use stable inputs. Pin dependencies with a lockfile, specify viewport and output path, and use a page-specific readiness condition.
  • Close browsers on every path. The finally block in the example closes Chromium after success or failure.
  • Make failure visible. Log errors and return a non-zero process status so an operator or monitoring system can distinguish a failed run.
  • Plan retention. Decide how many screenshots and logs to keep, whether to upload images elsewhere, and how old files are pruned. Check available disk space.
  • Protect captured data. Screenshots can expose personal, account, or customer information. Restrict filesystem access and avoid capturing authenticated or sensitive pages unless access and retention are appropriate.
  • Reinstall browsers after Playwright upgrades. A package and browser mismatch can prevent Chromium from launching. [Playwright browser documentation]

6. Troubleshooting

Symptom Likely cause Fix
Chromium fails to launch with missing library errors Required Linux browser dependencies are absent. Run npx playwright install --with-deps chromium for the project’s Playwright version, then retry. Check distribution package support if dependency installation fails.
Browser executable is missing after an upgrade The Playwright package changed but its matching browser was not installed. Run the Playwright browser install command again after updating the package.
Works in a shell but fails in cron Cron has a different working directory, PATH, environment, user, or permissions. Use absolute paths, set the working directory in a wrapper, use the same account as manual verification, and redirect output to a writable log.
Navigation times out The site is slow, unreachable from the VPS, or waiting for an unsuitable load condition. Check network access and the target’s response. Prefer domcontentloaded plus a meaningful selector when network activity does not settle. Increase the timeout only if the page legitimately needs more time.
Screenshot is blank or incomplete The page renders after the selected readiness event, requires authentication, or content loads only after scrolling. Wait for a page-specific visible selector, confirm the expected access state, and use fullPage: true if below-viewport content is required.
Permission denied writing the image or log The scheduler account cannot write to the destination. Create the directories first and grant the job account appropriate ownership and permissions. Keep permissions narrow.
Job succeeds manually but produces no scheduled output Wrong crontab account, schedule timezone assumption, or a missed/disabled timer. Inspect that user’s crontab or systemctl list-timers, confirm host timezone, then inspect cron or journal logs.
Browser launch fails and the error is unclear Launch configuration, dependencies, or environment may be involved. Use Playwright’s documented DEBUG=pw:browser diagnostic setting to capture browser launch details. [Playwright CI documentation]
Disk usage grows continuously Old screenshots or logs are retained indefinitely. Define retention and log rotation, and monitor the output filesystem.

7. Performance, reliability, and cost

A self-hosted job’s main resource costs are the VPS, disk, network transfer, and operator time. This research does not establish current India-region VPS availability or prices, so compare providers’ current region, CPU, memory, disk, bandwidth, and billing details directly. Full-page screenshots and larger device scales can use more memory and disk than viewport captures. Keep concurrency low until measured capacity supports more jobs.

Reliability depends on the target website as well as the VPS. A page can change its markup, require new access, load slowly, or fail independently. Log each run, retain enough output to diagnose failures, and alert on missing or stale captures if the image feeds another system. A successful process alone does not prove the screenshot contains the expected page; validate the captured artifact where that matters.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. Instead of maintaining Chromium and a scheduler-side browser installation, make a GET request for a screenshot. Read the ScreenshotNeo API documentation for parameters and output options.

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

The API also accepts the parameter names used by other screenshot APIs, which can make switching easier. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed; and an MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. [Docs and options]

Sign up free for 1,000 screenshots a month, no card required.

FAQ

Can I schedule a screenshot every day from an Indian VPS?

Yes. The scheduler runs on the VPS and invokes your Node script at the configured time. Verify the host timezone and network access to the target site.

Does Playwright need a desktop session on Linux?

No. Playwright browsers launch headlessly by default, which is suitable for a server without a desktop session. [Playwright CI documentation]

Should I use cron or a systemd timer?

Either can schedule the job. Choose based on the distribution, service lifecycle and logging needs, and operator familiarity. Use one scheduler for a given job.

Why does the screenshot differ between runs?

Page content, viewport, device scale, timing, fonts, and remote resources can change. Fix the viewport and scale, wait for a meaningful readiness condition, and account for content that is inherently dynamic.