Recommended Free Tools
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
If Browsershot broke after reinstalling Node.js with nvm, the most common cause is that Laravel is running under a different user or non-interactive shell than the one where nvm works. Verify Node, npm, Puppeteer, and Chrome as the actual PHP-FPM, queue, cron, or container user, then give Browsershot deterministic binary and browser paths.
Start with the process that is failing
A successful node -v in your terminal does not prove that PHP-FPM or a queue worker can run Node. nvm is installed per user and invoked per shell; it modifies PATH only when its initialization script is loaded. Web servers, supervisors, cron, and containers often start without your interactive profile.
Identify the runtime user
- For PHP-FPM, inspect the pool configuration and find its
userandgroup. - For a queue, check the account configured in Supervisor, systemd, or your container image.
- For cron, remember that its environment is minimal and usually does not load your login files.
- In a container, verify the user in the image and the shell used by the entrypoint.
Run every diagnostic below as that account, not merely as your own login user. If you administer the server, an equivalent test is to switch to the service account with its real home directory and shell before running the commands.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Confirm nvm, Node, and npm in that context
Use the following commands from the application’s normal working directory:
#1 Best Overall
command -v nvm || true
nvm current
nvm which current
node -v
npm -v
command -v node
command -v npm
node -p "process.execPath"
npm prefix
nvm current shows the selected version, while nvm which current prints the absolute Node executable. process.execPath is a useful final check because it reports the binary Node actually launched. Record these values, along with the runtime user and application directory.
When nvm is “command not found”
The service shell has not loaded nvm. For a non-interactive Bash process, nvm documents using BASH_ENV so Bash reads a file that sources nvm. You can instead source the nvm initialization script in the service’s startup command. In PHP-FPM and queue deployments, explicit executable paths are usually less fragile than depending on profile inheritance.
Do not solve this by installing another Node version as root and assuming the web user will see it. nvm installations and their caches are normally per-user.
Give Browsershot absolute Node and npm paths
Browsershot uses node and npm by default, but its requirements documentation notes that either may not be directly available depending on your setup. Spatie provides setters for deterministic paths:
Rank #2
<?php
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.png');
Replace both example paths with the output from nvm which current and the matching npm location for the same installation. Do not copy v20.x literally. Verify that the service user can traverse every parent directory and execute both files.
PATH inheritance versus explicit paths
| Approach | Best when | Risk |
|---|---|---|
Inherited PATH |
You control service startup and deliberately load the same nvm profile everywhere | A Node upgrade or a different shell silently selects another version or no Node at all |
setNodeBinary and setNpmBinary |
PHP-FPM, queues, cron, or deployments where reproducibility matters | The path must be updated when nvm removes an old version |
After changing a Node version, update the configured paths and restart long-running PHP-FPM and queue processes. A worker that was started before the change retains its old environment.
Fix “Cannot find module puppeteer”
Browsershot launches a browser script from the dependency context it resolves. Installing Puppeteer globally, or installing it under your personal home directory, does not guarantee that the Laravel process can resolve it.
- Change to the Laravel application directory.
- Become the same OS user that runs the failing job.
- Check the project declaration and installation:
cat package.json,npm ls puppeteer, andnode -p "require.resolve('puppeteer')". - If resolution fails, install the dependency declared for this application with that user and the project’s chosen package manager.
- Restart PHP-FPM or queue workers and rerun a minimal render.
A community compatibility report describes deleting node_modules and running npm install as a fix in one environment. Treat that as a version-specific recovery step, not a universal first command: deleting dependencies can remove a working lockfile state. Preserve package-lock.json (or the project’s lockfile), use the project’s documented Node version, and reinstall only when the dependency tree is actually missing or inconsistent.
Rank #3
Per-user dependency and cache checks
- Confirm
node_modules/puppeteerbelongs to the application release the worker is executing. - Ensure the runtime user can read the project, package files, and Puppeteer cache.
- Do not mix a root-owned install with a web-user process unless permissions are intentionally configured.
- If releases use separate directories, make sure the worker’s current release contains the dependency.
Fix “Could not find Chrome” separately
Node discovery and browser discovery are different problems. A working Node binary does not mean a usable Chrome or Chromium executable exists.
Choose one browser strategy
- Puppeteer-managed browser: follow the installation procedure for the exact Puppeteer version used by the project. This keeps the browser download aligned with that dependency.
- System Chrome or Chromium: install and maintain the OS package, then provide its absolute executable path to Browsershot.
For an explicit system browser, configure the path in your Browsershot call:
Browsershot::url('https://example.com')
->setChromePath('/usr/bin/chromium')
->save('/var/www/app/storage/app/example.png');
Use the real path on your host; it may be named google-chrome, chromium, or chromium-browser. Test readability and execution as the service user. For a Puppeteer-managed browser, check its cache directory and executable permissions under that same account. A Browsershot deployment discussion reports that correcting the cache directory or setting an explicit Chrome path resolved a launch failure; the exact location is deployment-specific.
Managed versus system browser
| Choice | Advantage | Responsibility |
|---|---|---|
| Puppeteer download | Browser and Puppeteer versions are kept together | Download the browser for the exact dependency and make its cache available to the service user |
| System Chrome/Chromium | Central OS package and familiar security updates | Install compatible OS libraries, maintain updates, and set an explicit path |
Do not confuse sandbox failures with PATH failures
An error such as No usable sandbox! is an operating-system policy problem, not evidence that Node or npm is missing. On affected Ubuntu/AppArmor configurations, consult Spatie’s documented sysctl settings for your exact platform and apply them only after confirming the error matches. Do not weaken browser isolation merely to silence a generic launch failure.
Rank #4
Retest with a minimal render
- Restart the process that runs the job.
- Render a short HTML string or a simple URL.
- Confirm the output file is created and can be opened by the service and your application.
- Only then test the production PDF, image, CSS, JavaScript, authentication, and long-page content.
Keep a small deployment record containing the runtime user, Node version, npm version, Puppeteer version, Chrome path, Puppeteer cache path, and Browsershot package version. When nvm changes, compare the new record with the last working one instead of guessing.
Common errors and targeted fixes
| Symptom | Likely cause | Fix |
|---|---|---|
node: command not found |
Service shell did not load nvm | Load nvm deliberately or configure setNodeBinary() with an absolute path; restart the service |
npm: command not found |
npm is outside the worker’s PATH or mismatched with Node |
Use the npm next to the selected Node installation via setNpmBinary() |
Cannot find module puppeteer |
Dependency installed in another directory or for another user | Run the project install and require.resolve test as the runtime user |
Could not find Chrome |
No managed browser, inaccessible cache, or missing system path | Install for the exact Puppeteer version or set and test setChromePath() |
Permission denied launching Node or Chrome |
Parent directory, executable, cache, or output permissions | Grant least-privilege traversal/read/execute access to the service user |
No usable sandbox! |
Kernel or AppArmor policy | Follow platform-specific sandbox guidance only after confirming the exact error |
| Works in SSH, fails in queue | Different user, working directory, environment, or stale worker | Reproduce under the queue account, set absolute paths, and restart workers |
Performance, reliability, and deployment notes
- Launching a browser is separate from resolving Node; a correct path does not eliminate browser startup cost.
- Long-running workers keep their original environment, so restart them after nvm, Puppeteer, or Chrome changes.
- Use one deliberate browser strategy per deployment rather than allowing some jobs to use a user cache and others a system binary.
- Keep cache and output directories on storage readable by the service account, and avoid placing them in a developer-only home directory.
- Pin and record the project’s dependency versions. The available evidence does not establish one universal Node, Puppeteer, Chrome, or Browsershot combination.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server when you do not need Laravel-specific browser control. It accepts a URL and returns PNG, JPEG, WebP, or PDF. Before capture it accepts cookie or consent banners 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 reports the result through X-Page-Verdict and X-Billed headers. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients.
One GET request is enough:
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 all options, including full-page lazy-image loading, CSS-selector element capture, device presets, retina scale, PDF paper and page settings, custom CSS or JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage data, and the OpenAPI specification. Parameter names used by other screenshot APIs also work, easing migration.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallPython
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()
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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo’s Free plan includes 1,000 shots per month without a card. Paid plans start at $5 for 3,000 shots; every feature is on every plan, and annual billing provides two months free. Sign up for the free plan.
FAQ
Should I install Node globally after reinstalling nvm?
No. Keep the project’s intended nvm installation and make the service context use it deliberately. A second global installation can hide which binary a worker is executing.
Why does changing my shell profile not fix PHP-FPM?
PHP-FPM may start without an interactive login shell and therefore never read that profile. Configure its startup environment or use Browsershot’s absolute binary setters.
Can I use any Chrome version with Puppeteer?
Do not assume that. Use the browser procedure for the exact Puppeteer dependency, or manage a system browser with a tested explicit path; the available evidence does not define a universal version pairing.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsWhat should I capture for a support request?
Provide the runtime user, execution context, Node and npm paths, versions, Puppeteer version, Chrome path or cache, exact Browsershot error, and whether the minimal render succeeds.
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.

