If Browsershot broke after reinstalling Node.js with nvm, first test Node, npm, Puppeteer, and Chrome as the same operating-system user that runs Laravel. nvm changes a per-user, per-shell PATH; PHP-FPM, queue workers, cron, and containers often do not load your interactive shell profile. Use the resulting absolute binary paths in Browsershot, then fix Puppeteer’s dependency location, Chrome discovery, and sandbox policy as separate problems.
Why a successful terminal test can still fail
nvm is “a version manager for node.js, designed to be installed per-user, and invoked per-shell,” according to the nvm-sh project README. Your login shell may therefore find the newly installed Node.js while a PHP-FPM pool or queue worker cannot. Spatie’s requirements documentation notes that “Depending on your setup, node or npm might be not directly available to Browsershot,” and that Browsershot uses node and npm by default.
There are four independent layers to check:
- Runtime and PATH: the service user must be able to execute the intended Node and npm.
- Dependency context: the Puppeteer package must be installed where Browsershot’s browser script resolves it.
- Browser binary: Puppeteer-managed Chrome or a system Chrome/Chromium must exist and be readable.
- OS policy: sandbox and AppArmor failures are not PATH failures.
Do not prescribe one universal Node, Puppeteer, or Chrome version: the correct combination depends on your Browsershot release, operating system, and deployment model.
1. Identify the process user and execution context
Find the account that actually renders the PDF or image. It may be the PHP-FPM pool user, a queue account, a cron account, or a container user rather than your SSH account.
#1 Best Overall
Typical contexts
- PHP-FPM: inspect the pool configuration for its
userandgroup. - Queue workers: inspect the systemd unit, Supervisor program, or container user.
- Cron: remember that cron starts a non-interactive shell with a minimal environment.
- Containers: identify the image user and whether bash is interactive.
Run all diagnostics as that account. A command that works as your login user proves only that your login environment is configured.
2. Verify nvm, Node, and npm in the affected context
In the target shell, run:
nvm current
nvm which current
node -v
npm -v
command -v node
command -v npm
nvm which current gives the Node executable selected by nvm. Record the complete path, Node version, npm version, and OS user. If nvm is unavailable, the service did not load nvm’s initialization script. For a controlled service, sourcing an nvm script during startup can work, but explicit executable paths are usually easier to reason about after an nvm upgrade.
Interactive versus non-interactive shells
Shell startup files differ by mode. A profile loaded by an interactive login shell may never run for PHP-FPM or a worker. The nvm README documents BASH_ENV for loading nvm in non-interactive bash, including container workflows. Configure that deliberately if you choose profile-based PATH inheritance; otherwise use absolute paths in Browsershot.
3. Configure Browsershot with deterministic binary paths
Spatie documents setNodeBinary, setNpmBinary, and setIncludePath. Replace the examples below with paths returned in the target context; do not copy the version string blindly.
Rank #2
use SpatieBrowsershotBrowsershot;
Browsershot::html($html)
->setNodeBinary('/home/app/.nvm/versions/node/v20.x/bin/node')
->setNpmBinary('/home/app/.nvm/versions/node/v20.x/bin/npm')
->save('/var/www/app/storage/app/output.pdf');
For a system-wide Node installation, use its actual path, such as the result of command -v node. If npm is a symlink, verify that its target is also executable by the service user. setIncludePath can supply a controlled PATH when supporting binaries are needed, but it does not install Node or repair file permissions.
PATH inheritance or absolute paths?
| Approach | Best when | Risk |
|---|---|---|
| Inherited PATH | Service startup is fully controlled and loads the same profile every time | An nvm switch or different shell mode silently changes the executable |
Absolute setNodeBinary/setNpmBinary |
PHP-FPM, workers, cron, and deployments need deterministic behavior | You must update paths after changing Node versions |
After an nvm upgrade, update the application configuration and restart long-running PHP-FPM or queue processes; an already-running worker retains its old environment.
4. Fix “Cannot find module puppeteer”
Browsershot’s PHP package invokes a Node browser script. The puppeteer dependency must be installed in the dependency context that script resolves, not merely in your personal home directory.
- Change to the Laravel application directory.
- Switch to the runtime user and the intended nvm version.
- Inspect the project’s declared dependencies and lockfile.
- Run the project’s normal install command, such as
npm install, under that user. - Retry a minimal Browsershot render before the full production job.
A community compatibility report describes removing node_modules and rerunning npm install as a fix in one environment. Treat that as version-specific, not a universal first step: deleting dependencies can remove reproducibility and may change transitive versions if the lockfile is not honored. Prefer the package manager and lockfile policy used by your deployment.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Rank #3
Per-user caches and permissions
nvm installs and Puppeteer caches commonly live below one user’s home directory. A web-service account may not be able to traverse that directory or execute the cached browser. Check directory traversal, file readability, and execute bits at every parent directory. Avoid copying a cache between incompatible Node or Puppeteer setups without verifying ownership and version alignment.
5. Fix “Could not find Chrome”
Chrome discovery is separate from Node discovery. Choose one browser strategy and configure it consistently.
Puppeteer-managed browser
Use the installation procedure for the exact Puppeteer dependency version in your project. Managed downloads keep the browser and Puppeteer versions aligned, but the download must complete for the runtime user and the cache must remain available to that user after deployment.
System Chrome or Chromium
If your operating system supplies the browser, locate the actual executable and set it explicitly:
Recommended Free Tools
Rank #4
Browsershot::html($html)
->setNodeBinary('/absolute/path/to/node')
->setNpmBinary('/absolute/path/to/npm')
->setChromePath('/absolute/path/to/chrome-or-chromium')
->save('/var/www/app/storage/app/output.pdf');
Use the real path for the target host. Confirm the service account can read and execute it, and that required OS libraries are installed. A Browsershot discussion reports that changing the cache directory and setting an explicit Chrome path resolved a launch failure in one deployment; that is a deployment-specific remedy, not proof that every Chrome error has the same cause.
| Browser choice | Advantage | What you must maintain |
|---|---|---|
| Puppeteer-managed | Browser and Puppeteer versions are intended to align | Download location, cache ownership, disk space, and runtime-user access |
| System Chrome/Chromium | Central OS patching and shared installation | Executable path, OS libraries, upgrades, and compatibility with Puppeteer |
6. Separate sandbox failures from PATH failures
An error such as No usable sandbox! means Chromium cannot establish its OS sandbox under the current policy. It does not mean Node or npm is missing. On affected Ubuntu/AppArmor configurations, consult Spatie’s documented sysctl settings and apply them only after confirming the platform and the exact error. Do not weaken sandboxing as a generic workaround for a missing binary.
7. Retest from smallest to largest
- As the runtime user, print Node and npm versions and executable paths.
- Run a short HTML or simple URL render.
- Render a small PDF or image to a known writable directory.
- Test the real application job, including its queue or PHP-FPM path.
- Record the runtime user, Node version, Puppeteer version, Chrome path, cache path, and Browsershot version.
This record makes the next nvm change diagnosable instead of relying on an interactive shell that may no longer match production.
Common errors and targeted fixes
| Symptom | Likely cause | Fix |
|---|---|---|
node: command not found or npm not found |
nvm profile not loaded, or wrong service user | Test as the runtime user; source nvm deliberately or set absolute binaries |
Cannot find module puppeteer |
Dependencies installed in another directory or user context | Install the declared project dependencies under the runtime user and verify resolution |
Could not find Chrome |
Missing Puppeteer download, inaccessible cache, or wrong system path | Install the matching browser or use setChromePath; fix ownership and permissions |
| Works in SSH, fails in queue | Different environment, user, or stale worker | Inspect the worker unit and restart it after configuration changes |
No usable sandbox! |
OS sandbox/AppArmor policy | Follow platform-specific sysctl guidance only after confirming the exact error |
| Blank output or timeout | Page load, network, browser, or application failure | Reproduce with a minimal URL/HTML render and inspect service logs separately from PATH checks |
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF without maintaining a local Puppeteer installation.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for request options. Before capture it accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools named take_screenshot, get_page_info, and capture_pdf.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account to try it.
Performance, reliability, and cost decisions
- Reuse workers carefully: long-lived workers need a restart after changing nvm paths or environment variables.
- Control caches: a shared, readable browser cache avoids repeated downloads, but only when ownership and version compatibility are explicit.
- Prefer minimal reproductions: a short HTML render isolates runtime problems from page JavaScript, network, and asset issues.
- Choose deterministic deployment: pin declared dependencies and record browser paths rather than relying on whichever nvm version a login shell currently selects.
- Consider an API: ScreenshotNeo’s cache TTL, async jobs, signed webhooks, bulk capture of up to 100 URLs per call, and usage API can reduce browser maintenance when your requirement is a clean remote capture rather than local Laravel rendering.
Frequently Asked Questions
Does reinstalling Node.js automatically reinstall Chrome?
No. Node and the browser are separate layers. Verify the Puppeteer browser cache or configure an explicit system Chrome/Chromium path.
Should I install Puppeteer globally?
Usually no. Install it in the project dependency context that Browsershot’s Node script resolves, under the runtime user.
Why does changing my shell profile not fix PHP-FPM?
PHP-FPM may not load that profile and may run as another user. Test its actual environment or configure absolute binary paths.
Can I treat every Chrome launch error as a sandbox problem?
No. First distinguish missing/inaccessible Chrome from an explicit sandbox or AppArmor error; they require different fixes.
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.

