PhantomJS Website Screenshot Script Hangs: Debugging Steps
Trace a PhantomJS screenshot hang through the binary, page errors, network requests, timeouts, and capture lifecycle, with runnable diagnostics for legacy scripts.
Start by checking the exact PhantomJS executable and version, then instrument page errors and network requests. Next, bound individual resource loads, verify that the page-open callback reaches the render step, and make sure the script exits. These checks distinguish a wrong binary, page exception, stalled request, proxy or TLS issue, and lifecycle wait without assuming one cause explains every hang.
PhantomJS is a legacy headless browser based on QtWebKit. Its official site says development is suspended until further notice. The steps below are for investigating an existing installation; they do not establish compatibility with any particular present-day website or operating system. No hang has been reproduced for this guide.
1. Confirm which PhantomJS is running
Run the version check in the same shell, container, service account, or package script that runs the capture. Multiple installations can mean the interactive shell and application invoke different binaries.
phantomjs --version
command -v phantomjs
The documented latest release in the CLI reference is 2.1.1. If the version is unexpected, inspect PATH and the command configured in your package script or service. When available, add --debug=true for additional warnings and debug messages.
phantomjs --debug=true capture.js https://example.com
Use the absolute path to the expected executable when comparing environments. Record the version and operating system alongside the output; this narrows later TLS, proxy, and display investigations.
2. Add page and resource diagnostics
page.onError reports exceptions raised by page code. page.onResourceRequested shows which URLs the page requests. Logging both helps separate a script exception from a request that stops progressing.
var page = require('webpage').create();
var system = require('system');
var address = system.args[1] || 'https://example.com';
page.onError = function (message, trace) {
console.error('[page error] ' + message);
(trace || []).forEach(function (frame) {
console.error(' at ' + (frame.file || '(unknown file)') + ':' + (frame.line || '?'));
});
};
page.onConsoleMessage = function (message) {
console.log('[page console] ' + message);
};
page.onResourceRequested = function (requestData, networkRequest) {
console.log('[request] ' + requestData.url);
};
page.onResourceTimeout = function (request) {
console.error('[resource timeout] ' + request.url + ' (id ' + request.id + ')');
};
page.onResourceReceived = function (response) {
if (response.stage === 'end') {
console.log('[response] ' + response.status + ' ' + response.url);
}
};
page.settings.resourceTimeout = 20000; // milliseconds; set before page.open
page.viewportSize = { width: 1365, height: 900 };
page.open(address, function (status) {
console.log('[page open callback] status=' + status);
if (status !== 'success') {
console.error('Page did not open successfully: ' + address);
phantom.exit(2);
return;
}
var output = 'capture.png';
if (page.render(output)) {
console.log('[rendered] ' + output);
phantom.exit(0);
} else {
console.error('[render failed] ' + output);
phantom.exit(3);
}
});
Save this as capture.js, then run phantomjs capture.js https://example.com. The output records requested URLs, completed responses, page exceptions and the open/render lifecycle. If the last log line is a request, investigate that resource and the network path. If the open callback runs but rendering does not, inspect the callback logic and any readiness wait added by your application.
The resource timeout handler reports the individual resource that exceeded its limit. It does not guarantee that a script-level timer, polling loop, callback, or page JavaScript will finish.
3. Bound slow resources before opening the page
page.settings.resourceTimeout is in milliseconds and stops an individual resource request after its limit. Set it before the initial page.open, because changing it after that call does not affect the in-flight load.
page.settings.resourceTimeout = 15000;
page.onResourceTimeout = function (request) {
console.error('Timed out resource: ' + request.url);
};
page.open(address, onOpen);
Pick a limit that fits the resources your target page needs. A short limit may report slow but essential assets; a long limit delays diagnosis. Keep an overall watchdog separate from this per-resource limit if the script has its own waits. Treat both as bounds with explicit failure handling, not as proof that every cause of a hang is covered.
4. Check HTTPS and proxy behavior
If the same target works over HTTP but HTTPS stalls or fails, inspect the SSL libraries available to the PhantomJS binary and compare the environment in which it runs. The legacy documentation specifically calls out SSL libraries such as OpenSSL as a diagnostic area.
On Windows, the documentation notes that a default proxy can cause substantial latency. If that applies to the machine and network, compare a run with proxy use disabled:
phantomjs --proxy-type=none capture.js https://example.com
This is a diagnostic comparison, not a general proxy configuration. If disabling the proxy changes behavior, check the intended proxy settings and network policy rather than assuming direct access is appropriate for every environment.
5. Verify capture timing and process exit
PhantomJS’s basic screenshot example calls page.render() from the page.open callback and then calls phantom.exit(). A script can appear hung if it never reaches its capture point, starts an unbounded wait for dynamic content, or leaves the process alive after rendering.
- Log entry into and exit from
page.open‘s callback. - Check the callback status before rendering.
- For a page with asynchronous updates, choose a readiness condition that is meaningful for that particular page.
- Give that readiness wait a separate overall deadline and report which condition was not met.
- Render only after readiness succeeds, then call
phantom.exit()on both success and failure paths.
There is no universal readiness signal for dynamic pages. A fixed delay may be too short on a slow run and waste time on a fast one; a page-specific condition is more informative. Keep the deadline distinct from resourceTimeout, which applies to individual resource requests.
6. Use this symptom-to-evidence order
| Observed symptom | Check | Next step |
|---|---|---|
| Unexpected behavior before any useful logs | Version output, PATH, package script, service configuration | Invoke the intended binary by its full path and compare versions. |
| Page callback reports success, but content is wrong or incomplete | page.onError, console messages, and readiness logic |
Fix the page exception or wait for a site-specific completion condition. |
| Last output is a resource request | Request URL, response completion, and timeout log | Investigate that URL, its network route, and whether the resource is required. |
| HTTP works while HTTPS fails | SSL libraries and runtime environment for this binary | Compare the TLS dependencies and configuration of the working and failing environments. |
| Windows run has unusually high latency | Default proxy behavior | Where appropriate, compare with --proxy-type=none. |
| Page opens but process never ends | Render branch, pending timers, polling loops, and exit calls | Bound application waits and exit explicitly on every terminal path. |
| Actual error mentions an X server | PhantomJS version | Versions 1.4 and earlier required an X server; versions 1.5 and later are pure headless and do not need X11/Xvfb. |
If SELinux may be blocking execution, investigate it as an environment-specific possibility. The available legacy guidance flags SELinux but does not establish a generally valid policy fix.
7. Inspect the page with the remote debugger
When available, PhantomJS documents a WebKit inspector workflow using --remote-debugger-port. Start it on a diagnostic port:
phantomjs --remote-debugger-port=9000 capture.js https://example.com
Use the documented inspector workflow to examine the script and page state while diagnosing. Treat the remote debugging endpoint as a local diagnostic interface; bind and access it only as appropriate for the environment.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF. For the API options and examples, see the ScreenshotNeo documentation.
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)
open("shot.webp", "wb").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}`);
- Cookie banners are accepted and removed before the shot, along with known newsletter popups and chat widgets; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing status in headers.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
Performance, reliability, and cost considerations
For a legacy PhantomJS script, the useful performance evidence is the timeline in its own logs: time to open callback, the last requested resource, resource timeout events, time to render, and time to process exit. This identifies where elapsed time is being spent without relying on an unsupported universal benchmark. An individual resource timeout limits one request; an application watchdog limits your own readiness logic. Neither makes an obsolete browser compatible with a current site.
Reliability depends on the installed binary, its TLS and proxy environment, target behavior, and the script’s lifecycle. Keep the version and environment recorded with failures so that an upgrade, deployment change, or network difference can be compared. PhantomJS development is suspended, so treat its official API pages as documentation for legacy installations, not a promise of present-day site support.
PhantomJS’s supplied documentation does not establish a cost per screenshot. ScreenshotNeo’s listed plans are Free: 1,000 shots per month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Compare expected successful captures and required features against those stated limits; do not infer a reliability or speed advantage from price alone.
Frequently asked questions
Does increasing the resource timeout fix a hung script?
No. It changes the limit for an individual resource request. A page script, polling loop, or callback can still wait indefinitely, so add a separate bounded deadline for application-level waits.
Should every dynamic page use a fixed sleep before capture?
No single delay suits every page or environment. Define a page-specific readiness signal where possible and bound the wait with an overall deadline.
Does a missing screenshot always mean PhantomJS needs Xvfb?
No. The documented X-server requirement applies to PhantomJS 1.4 and earlier. Versions 1.5 and later are pure headless; first verify the version and the actual error.
Does this guide confirm support for current websites?
No. It describes diagnostics for an existing legacy runtime. Compatibility depends on the specific binary, environment, and target page.
Sources and scope
- PhantomJS official site (development status).
- PhantomJS troubleshooting (version, diagnostics, SSL/proxy, inspector, and environment notes).
- WebPage settings (resource timeout semantics).
- PhantomJS FAQ (X server version boundary).
- Screen capture example (rendering and process exit).
- Command-line reference (debug flag and documented release).
The research basis is official documentation only. It contains no hang-frequency statistic, reproduction, or confirmed compatibility result for a present-day site.


