If Puppeteer works in your terminal but fails through PHP and Apache, the browser is usually running in a different environment: a different service user, HOME, PATH, cache, working directory, or security policy. Capture the exact Chrome stderr from the Apache-launched process, then fix the matching cause—browser discovery, permissions, missing libraries, sandboxing, or confinement. Do not run Apache or Chrome as root to make the error disappear.
Why Puppeteer works in a terminal but not through Apache
A successful shell test proves only that the browser can start as that shell user with that shell’s environment. PHP invoked by an Apache module inherits Apache’s service-user permissions; other PHP deployments, such as PHP-FPM, can run under a different account. Either way, the process may have a different HOME, PATH, current directory, temporary directory, Puppeteer cache, and access to browser files. Linux security controls such as AppArmor can also deny execution even when ordinary file permissions look correct.
Start with the first Chrome or Puppeteer stderr message, not just a blank screenshot or an error page. The wording often identifies the branch to investigate: Could not find Chrome points to installation or discovery; spawn ... ENOENT points to a missing executable or interpreter, and can also accompany missing runtime dependencies; No usable sandbox! points to Chrome’s sandbox setup.
1. Capture the environment Apache actually uses
Log the environment from the same PHP endpoint or job that fails. Record the effective user and groups, HOME, PATH, TMPDIR, working directory, Node version, Puppeteer version, resolved browser path, and complete stderr. Do not log API keys, cookies, authorization headers, or other secrets. PHP’s proc_open can run a child process with pipes for its output; its array command form, available from PHP 7.4, passes arguments directly rather than asking a shell to parse them.
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 errors#1 Best Overall
For a quick check, log values from PHP itself and run an equivalent diagnostic through the same process-launch path used in production. A terminal command such as whoami is not a substitute for identifying the PHP worker’s effective user. Check the browser file with ls -l and inspect permissions on every parent directory: the service account needs traversal access to the directories, not merely read permission on the final file.
2. Use a PHP launcher that passes arguments safely
This PHP 7.4+ example invokes a Node script with an argument array, an explicit working directory, and explicit environment values. It captures both output streams and the exit status. The example assumes the Node executable and script really are at the listed absolute paths; change them to match your installation.
<?php
$url = 'https://example.com';
$cmd = [
'/usr/bin/node',
'/var/www/app/render.js',
'--url', $url,
];
$spec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$env = [
'HOME' => '/var/lib/myapp',
'PATH' => '/usr/local/bin:/usr/bin:/bin',
'TMPDIR' => '/var/lib/myapp/tmp',
'PUPPETEER_CACHE_DIR' => '/var/lib/myapp/.cache/puppeteer',
];
$p = proc_open($cmd, $spec, $pipes, '/var/www/app', $env);
if (!is_resource($p)) {
error_log('Could not start Node process');
http_response_code(500);
exit('Screenshot process could not start');
}
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exitCode = proc_close($p);
if ($exitCode !== 0) {
error_log('render.js failed: ' . $stderr);
http_response_code(500);
exit('Screenshot failed');
}
echo $stdout;
In production, keep the diagnostic output modest or drain stdout and stderr concurrently: reading one pipe to completion while the other fills can block a child process that writes a lot of output. Log stderr and the exit code on failure, but return a generic error to an HTTP client. Do not concatenate the URL into a shell command; the array form avoids shell interpretation of user input. Create the cache, temporary, and profile directories for the service account before launch.
Rank #2
3. Make the Node script launch the intended browser
With the usual Puppeteer package, installation downloads a compatible browser. If deployment blocks package install scripts, that browser download may not have happened. Install Puppeteer in a way that permits its browser installation, or deliberately manage Chrome/Chromium yourself and set an absolute executable path. Keep the Puppeteer package and chosen browser version aligned rather than pointing at an arbitrary binary.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →const puppeteer = require('puppeteer');
async function main() {
const urlIndex = process.argv.indexOf('--url');
const url = urlIndex >= 0 ? process.argv[urlIndex + 1] : null;
if (!url || !/^https?:///i.test(url)) {
throw new Error('Pass an http(s) URL with --url');
}
const launchOptions = { headless: true };
if (process.env.PUPPETEER_EXECUTABLE_PATH) {
launchOptions.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH;
}
const browser = await puppeteer.launch(launchOptions);
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
const image = await page.screenshot({ type: 'png', fullPage: true });
process.stdout.write(image.toString('base64'));
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error.stack || error);
process.exitCode = 1;
});
The script uses the Puppeteer-managed browser unless PUPPETEER_EXECUTABLE_PATH is explicitly set. In a production application, write the screenshot to a controlled output location or stream the binary through a deliberate interface; base64 stdout keeps this small example easy to inspect, but increases payload size. If navigation regularly exceeds the timeout, diagnose the target page and its load behavior instead of raising the timeout without limit.
4. Fix browser-not-found and executable errors
Could not find Chrome
Puppeteer normally downloads Chrome for Testing and chrome-headless-shell. A package installation that skipped lifecycle scripts may leave the package present but the browser absent. Check the deployment log for the install step, then permit the download or manage a browser installation explicitly. The Puppeteer installation and configuration documentation describes browser installation and selection: Puppeteer installation and Puppeteer configuration.
Browser was not found at the configured executablePath or spawn ... ENOENT
Verify the configured absolute path from the Apache process context. Confirm the file exists, is executable, and every parent directory is traversable by the service account. If Chrome is supplied by the operating system, set executablePath in the launch options or PUPPETEER_EXECUTABLE_PATH in the process environment. Check that the binary is for the host OS and architecture; a path that exists in a build container may not exist in the runtime container.
5. Give the service account narrow, writable locations
Puppeteer’s default cache is under the invoking user’s home directory. Apache may use a different or unset HOME, and its account may not be able to create files in that location. Set PUPPETEER_CACHE_DIR or Puppeteer’s cacheDirectory configuration to a dedicated path. Choose a dedicated writable temporary directory and, when using one, a writable userDataDir for the browser profile. Ensure adequate disk space and ownership for those runtime locations.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →The service account must also be able to read and execute the browser and read its shared libraries. Keep application code and browser binaries non-writable by the web-serving account where practical; grant write access only to the cache, profile, temporary, and output paths that need it. Apache’s security guidance favors narrow, directory-specific write access rather than making served content broadly writable: Apache security tips.
Rank #4
6. Install missing Linux libraries and fonts
A browser executable can exist and still fail at startup if the system lacks a shared library, font, certificate, or runtime component. Puppeteer’s CI documentation lists commonly needed Debian/Ubuntu components, including libnss3, libgbm1, GTK/X11 libraries, fonts, certificates, and xdg-utils: Puppeteer system requirements. Package names vary by distribution and release, so install the equivalents for the target host rather than copying a package list blindly. Use the distribution’s package and shared-library inspection tools to identify the specific missing dependency.
If the error mentions a shared object, resolve that library first. If the page starts but text or emoji looks different, check fonts and font configuration. A missing GUI desktop is not by itself proof that headless Chrome cannot run; investigate the actual runtime error rather than installing a full desktop environment by reflex.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.7. Treat Chrome sandbox errors as a security issue
For No usable sandbox!, run Chrome under a non-root, non-privileged service account and configure a functioning Linux sandbox. Puppeteer documents the setuid sandbox helper setup and troubleshooting in its guide: Puppeteer troubleshooting. The project warns that running without a sandbox is strongly discouraged. Do not add --no-sandbox as a routine server fix; it removes an important isolation boundary. Consider it only as a tightly scoped exception when the content is fully trusted and the environment genuinely cannot provide a sandbox, with the resulting risk understood and documented.
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 & 11Best Value
- Used Book in Good Condition
Never solve a permission error by running Apache or Chrome as root. PHP’s security manual warns that escalating Apache privileges to root is extremely dangerous. The browser processes untrusted web content in many screenshot use cases, so weakening its isolation to mask a deployment mismatch is especially poor trade-off.
8. Check AppArmor or other execution policies
Unix mode bits are not the only access control. AppArmor profiles can separately restrict file reads, writes, and execution, including whether a service can start Node or Chrome. When permissions appear correct but the child process is still denied, inspect the system audit log for a policy denial. Adjust only the rule needed for the intended executable and its required files, or run browser capture in a separately supervised worker service. Do not disable the policy globally as a first response. AppArmor’s profile documentation explains these access controls: Ubuntu AppArmor documentation.
9. Decide whether Apache should launch the browser directly
Launching a browser inside an HTTP request is workable for short, bounded jobs, but it couples request latency and PHP worker availability to browser startup, page navigation, and cleanup. For sustained or long-running capture work, a queue and Node worker under a dedicated service account often make environment control, structured logging, restarts, and resource limits easier. Give the worker only the browser, library, cache, temp, and output access it requires. This is an architectural choice, not a prerequisite for fixing a single launch error.
Troubleshooting checklist
- The terminal works, Apache fails: compare effective user,
HOME,PATH, current directory, temp path, cache path, and security context from the failing PHP process. - Chrome cannot be found: confirm Puppeteer’s install step downloaded its browser, or set one verified absolute executable path.
- Executable exists but will not start: verify execute and directory-traversal permissions, architecture, shared libraries, fonts, certificates, and policy denials.
- Profile or cache errors: set dedicated cache, temporary, and profile paths owned by the service account; check disk space.
No usable sandbox!: use a non-root account and configure the sandbox; do not default to--no-sandbox.- It fails only under confinement: inspect AppArmor or container audit events and add the narrowest applicable permission.
- Requests hang or workers pile up: bound navigation time, ensure browser cleanup in a
finallyblock, and consider moving long captures to a queue worker.
Or skip the browser setup
If your actual goal is to get a website screenshot rather than control Puppeteer itself, ScreenshotNeo offers a screenshot API and MCP server. A single GET request can return a PNG, JPEG, WebP, or PDF. Its clean-shot flow accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing outcome. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
Recommended Free Tools
For example, save a WebP capture with cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
See the ScreenshotNeo API documentation for request options and response details. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo’s free plan to try it without a card.
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.

