What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Most wkhtmltopdf hangs in PHP come from one of four causes: a stdout/stderr pipe that fills, a different PATH or environment under Apache/PHP-FPM, a page that never satisfies a JavaScript wait, or a Linux display/Xvfb problem. Replace the one-line shell_exec() call with supervised proc_open(), drain both output streams, enforce a deadline, and run the command as the same user and environment as the web worker.

Why shell_exec() appears to hang

PHP’s shell_exec() waits for the command to finish and returns the command’s complete output. It does not provide an exit code, so an empty or null result cannot tell you whether wkhtmltopdf succeeded, failed, or was still blocked. A child process can also deadlock when it writes enough diagnostics to stdout or stderr to fill a pipe while the parent waits without draining that stream.

Background-process advice applies here too: if output is not redirected, PHP can remain blocked. For a quick diagnosis use exec() (which can collect an exit status) or, for production, proc_open(), which gives you separate pipes and process control.

Follow this diagnostic sequence

  1. Run as the web-worker user. Find the Unix account used by Apache or PHP-FPM, then run wkhtmltopdf --version and the exact conversion command as that account. A command that works in your shell may fail under a restricted service account.
  2. Use an absolute binary path. Replace wkhtmltopdf with a path such as /usr/local/bin/wkhtmltopdf. Log the working directory and the worker’s PATH, HOME, and DISPLAY.
  3. Expose diagnostics. Temporarily append 2>&1, or redirect stderr to a file, so loading, DNS, TLS, font, JavaScript, and X11 messages are visible. Do not interpret shell_exec() returning null as an exit status.
  4. Set an outer deadline. During investigation wrap the command with an operating-system timeout, for example timeout 60s /usr/local/bin/wkhtmltopdf .... Log whether the timeout terminated the child; 60 seconds is an operational safeguard, not a universal wkhtmltopdf requirement.
  5. Reduce the input. Convert a local, minimal HTML file. If that succeeds, add remote assets, JavaScript, headers and footers, custom waits, and authentication one at a time.

Eliminate pipe and shell problems first

Quick test with exec()

This test captures output and an exit code, but it still leaves quoting and timeout responsibilities to you:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Epson EcoTank ET-2800 Wireless Color All-in-One Supertank Printer - Black
  • INNOVATIVE CARTRIDGE-FREE PRINTING — No more dealing with lots of tiny ink cartridges; With this wireless document and photo printer each ink bottle set is equivalent to about 90 individual cartridges²
  • LESS FREQUENT INK REPLACEMENT — Replacement ink bottles don't have to be changed nearly as often as ink cartridges¹; When you choose this combination printer, scanner and copier you can print up to 4,500 pages black/7,500 color³
  • COLOR PRINTING — Up to 2 years of ink in the box4 (and with every replacement ink set) for fewer out-of-ink frustrations
  • ZERO CARTRIDGE WASTE — By using an Epson EcoTank printer you can help reduce the amount of cartridge waste ending up in landfills
  • HOME PRINTER DESIGNED FOR RELIABILITY — The Epson EcoTank ET-2800 All-in-One Supertank Color Printer creates vivid, detailed prints and documents thanks to Micro Piezo Heat-Free Technology; Fire off 10 ISO pages per minute1 to easily finish large jobs
$lines = [];
$exitCode = 0;
exec('/usr/local/bin/wkhtmltopdf --quiet ' . escapeshellarg($input) . ' ' . escapeshellarg($output) . ' 2>&1', $lines, $exitCode);
error_log('wkhtmltopdf exit=' . $exitCode . ' output=' . implode("n", $lines));
if ($exitCode !== 0) {
    throw new RuntimeException('wkhtmltopdf failed: ' . implode("n", $lines));
}

Use escapeshellarg() for every variable that crosses a shell boundary. User-controlled paths, URLs, or options can otherwise become shell syntax. Direct process launching avoids most shell interpretation.

Capture stderr without blocking

When debugging, redirect stderr to a file outside the web root:

/usr/local/bin/wkhtmltopdf --quiet input.html output.pdf 2>/var/log/myapp/wkhtmltopdf.err

Inspect the file while the request is running. Messages about an unreachable resource, certificate, missing font, display, or JavaScript wait usually identify the real blocker.

Use proc_open() for a supervised conversion

proc_open() lets you close stdin, drain stdout and stderr independently, set the working directory and environment, and obtain the child status with proc_close(). Descriptor 0 is stdin, 1 is stdout, and 2 is stderr.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
$command = [
    '/usr/local/bin/wkhtmltopdf',
    '--quiet',
    $input,
    $output,
];
$spec = [
    0 => ['pipe', 'r'],
    1 => ['pipe', 'w'],
    2 => ['pipe', 'w'],
];
$environment = ['DISPLAY' => ':99'];
$proc = proc_open($command, $spec, $pipes, $workDir, $environment);
if (!is_resource($proc)) {
    throw new RuntimeException('Could not start wkhtmltopdf');
}
fclose($pipes[0]);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$stdout = '';
$stderr = '';
$deadline = microtime(true) + 60;
$timedOut = false;
while (true) {
    $read = [];
    if (!feof($pipes[1])) {
        $read[] = $pipes[1];
    }
    if (!feof($pipes[2])) {
        $read[] = $pipes[2];
    }
    if ($read) {
        $write = null;
        $except = null;
        @stream_select($read, $write, $except, 0, 200000);
        foreach ($read as $stream) {
            $chunk = stream_get_contents($stream);
            if ($stream === $pipes[1]) {
                $stdout .= $chunk;
            } else {
                $stderr .= $chunk;
            }
        }
    }
    $status = proc_get_status($proc);
    if (!$status['running']) {
        break;
    }
    if (microtime(true) >= $deadline) {
        $timedOut = true;
        proc_terminate($proc);
        break;
    }
}
$stdout .= stream_get_contents($pipes[1]);
$stderr .= stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exitCode = proc_close($proc);
if ($timedOut) {
    throw new RuntimeException('wkhtmltopdf exceeded the application deadline');
}
if ($exitCode !== 0) {
    throw new RuntimeException('wkhtmltopdf failed (' . $exitCode . '): ' . $stderr);
}

This is a structure to adapt, not a tested drop-in. Keep draining both pipes until the process exits, and make the termination policy explicit. If you need a kill-after-grace-period policy, send a terminate signal first, wait briefly, then use the platform’s stronger termination mechanism.

Rank #2
Sale
Epson EcoTank Photo ET-8550 Wireless Wide-Format All-in-One Tank Printer
  • CARTRIDGE-FREE PRINTING — Print lab-quality photos, graphics and creative projects; Get vibrant colors and sharp text with Epson's high-accuracy printhead and Claria ET Premium 6-color inks
  • INK BOTTLES — Save on photos1 and creative projects with affordable in-house printing; All-in-one printer allows you to print 4" x 6" photos for about 4 cents each vs. 40 cents with traditional ink cartridges1
  • LESS FREQUENT INK REPLACEMENT — Replacement ink bottles don't have to be changed nearly as often as ink cartridges¹; Printer, scanner and copier lets you print up to 6,200 color pages³
  • PRINT FOR LONGER — Up to 2 years of ink in the box² (and with every replacement ink set) for fewer out-of-ink frustrations with this wireless printer
  • ZERO CARTRIDGE WASTE — Epson EcoTank printer helps reduce the amount of cartridge waste ending up in landfills; Cartridge-free printer uses high-yield ink bottles; Each replacement ink bottle set is equivalent to about 100 individual ink cartridges⁴

Check headless Linux and Xvfb

Some Linux wkhtmltopdf builds require an X server. The phpwkhtmltopdf guidance recommends xvfb-run for low-frequency sites, or one persistent Xvfb process reused by requests. Starting Xvfb for every PDF adds CPU overhead.

Xvfb :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset >/var/log/xvfb.log 2>&1 &
export DISPLAY=:99
/usr/local/bin/wkhtmltopdf --quiet input.html output.pdf

Set DISPLAY=:99 in the PHP-FPM or Apache worker environment, not only in your interactive shell. Check wkhtmltopdf --version first: a patched-Qt build may not need Xvfb. A missing display often fails immediately; a badly managed persistent display can instead leave workers waiting or accumulate defunct processes. Monitor the Xvfb log and process table.

Bound JavaScript and resource waits

The usage reference lists a 200 ms default for --javascript-delay, --stop-slow-scripts enabled by default, and --load-error-handling abort by default. These settings do not make an unbounded page safe: network requests, iframes, DNS, proxies, TLS negotiation, or a busy WebKit event loop can still prevent completion.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Understand the wait flags

Option What it does Safe diagnostic approach
--javascript-delay <msec> Waits a bounded number of milliseconds for client-side rendering; default is 200 ms. Use only the delay your page needs, then reduce it after measuring.
--window-status <value> Waits until the page sets the exact window status value. Remove it while diagnosing, or assign the value on every success and error path.
--stop-slow-scripts Stops scripts considered slow; enabled by default. Do not assume it handles scripts that keep the event loop busy in other ways.
--load-error-handling abort|skip|ignore Controls behavior when a resource load fails; default is abort. skip or ignore can finish a PDF but may hide missing content.

Fix a never-reached window status

--window-status ready is a synchronization contract. The page must execute window.status = 'ready'; if an exception, rejected promise, alternate route, or failed request prevents that assignment, wkhtmltopdf can wait indefinitely. Remove the flag, add a bounded delay, and inspect browser-side logs. If you keep the flag, set it from every code path that can produce a valid document and add an application-level deadline anyway.

Environment, files, and deployment checks

  • Permissions: give the worker read access to HTML, CSS, fonts, images, and certificates, and write access only to the temporary and destination directories it needs.
  • Temporary files: store inputs and outputs outside the public web root; use unpredictable names and delete them after a successful response or a logged failure.
  • Working directory: use an explicit directory so relative asset paths behave the same in a web request and a shell.
  • Sessions: close the PHP session before long rendering work if other requests from the same user must remain responsive.
  • Network policy: verify DNS, proxy variables, outbound firewall rules, certificate stores, and authentication headers from the service account.
  • Concurrency: cap simultaneous conversions. Each process consumes memory, file descriptors, and possibly an X display; an unbounded queue can look like a hang.
  • Observability: log the command arguments after redaction, start and finish times, timeout state, exit code, stderr, output size, and the worker identity.

Common symptoms and fixes

Symptom Likely cause Fix
Works in a terminal, hangs under Apache or PHP-FPM Different PATH, HOME, permissions, certificates, proxy, or DISPLAY Run as the service user, use absolute paths, and set environment values explicitly.
No useful output from shell_exec() Output was not captured, or null was mistaken for a status Redirect stderr, use exec() for a code, or supervise with proc_open().
Process stops when a page is verbose stdout/stderr pipe filled Drain both nonblocking pipes or redirect stderr to a file.
Only pages with charts or client rendering hang Never-ending script, remote request, or incorrect wait flag Test locally, remove --window-status, bound delay, and inspect failed requests.
Immediate “cannot connect to display” error Required X server is unavailable Configure persistent Xvfb and DISPLAY, or use a verified patched-Qt build without X.
PDF completes but content is missing Resources failed and load errors were skipped or ignored Restore abort while debugging and fix the underlying URL, TLS, or permission problem.
Zombie wkhtmltopdf processes accumulate Children are not reaped after termination Always close pipes and call proc_close(), including timeout paths.

Security boundaries for HTML-to-PDF workers

Treat HTML, URLs, headers, cookies, and wkhtmltopdf options as untrusted input. Prefer the array form of proc_open() so arguments are passed directly. If a shell is unavoidable, escape every variable with escapeshellarg() and never concatenate user text into option names. Run the worker with the least filesystem and network access practical; an HTML renderer can be exposed to local-file and server-side request risks if you allow arbitrary URLs.

Rank #3
HP Smart Tank 5000 Wireless All-in-One Ink Tank Printer, Scanner, Copier with 2 Years of Ink Included, Best-for-Home, Cartridge-Free, Refillable and AI-Enabled. (5D1B6A)
  • SET IT UP ONCE AND PRINT WITH CONFIDENCE. No complicated maintenance. Just easy, reliable printing you can count on.
  • INK FOR YEARS. NOT MONTHS. Up to 2 years of ink included. Get thousands of pages of cartridge-free printing. More pages, less hassle
  • KEEPS PRINTING WELL AFTER COMPETITORS HAVE QUIT. No complex maintenance. Sharper text, richer colors.[2] Only with HP Smart Tank
  • PREMIUM SUPPORT - Strong technical expertise to solve issues faster
  • THE LAST PRINTER YOU'LL EVER NEED. Enjoy years of refillable, cartridge-free printing.

When to migrate away from wkhtmltopdf

The upstream wkhtmltopdf repository is archived and read-only, with repository metadata showing an archive date of January 2, 2023. Existing installations can remain serviceable, but old WebKit behavior increases the cost of platform-specific fixes and modern CSS or JavaScript compatibility. Stabilize the worker first, then evaluate a maintained Chromium renderer or managed PDF API against the pages you actually generate. Compare JavaScript and CSS fidelity, isolation, latency, observability, and total operating cost rather than assuming a migration is automatically faster.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a reliable hosted capture rather than maintaining a wkhtmltopdf process, ScreenshotNeo accepts one GET request and returns PNG, JPEG, WebP, or PDF. Before capture it accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing result in X-Page-Verdict and X-Billed headers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The API also supports full-page captures with lazy images, CSS-selector element captures, dark mode, 12 device presets or custom viewports, retina scale, PDF paper size/margins/orientation/page ranges, HTML/CSS input, custom JavaScript and CSS, pre-capture clicks, hidden selectors, selector/delay/network-idle waits, ad/tracker/request/resource blocking, headers, cookies, user-agent and Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, up to 100 URLs per bulk call, a usage API, an OpenAPI specification, and familiar parameter names for easier switching. Every feature is included on every plan.

Use the ScreenshotNeo API documentation for authentication and option details. A minimal call is:

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

Equivalent clients:

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}`);

Plans are Free (1,000 shots per month, no card), Starter ($5 for 3,000), Growth ($15 for 15,000), Pro ($39 for 60,000), Scale ($99 for 250,000), and Business ($249 for 1,000,000); yearly billing gives two months free. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools to Claude, Cursor, and other MCP clients. Create a free ScreenshotNeo account to get 1,000 screenshots each month without adding a card.

FAQ

Can I fix the issue by adding --quiet?

--quiet reduces diagnostics but does not solve a blocked network request, an unreached window status, a missing display, or a pipe that is still not drained correctly. Keep stderr available until the cause is known.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
NDYIN Portable Printers Wireless for Travel, N80 Bluetooth Thermal Printer
  • Wireless Bluetooth Printer: Portable thermal printer compatible with iPhone, Android phones, iPad and tablet computers via Bluetooth. For smartphones, please download the "Nada Print" App. You can also connect to laptops and computers for printing using a USB-C cable. (Note: Laptops and computers can only be connected via USB and require the installation of a driver first. Bluetooth connection is not supported.)
  • No-ink printing: Only supports US Letter and A4 size thermal paper.(Doesn't support regular paper) The no-ink portable thermal printer uses direct thermal technology, requiring no ink, toner or ribbons, making it environmentally friendly, cost-effective and time-saving. The thermal printer package comes with a roll of US Letter thermal printing paper. Note: When installing the paper, remember to switch the paper size switch on APP
  • Clear Print: NDYIN N80 portable thermal printer adopts high-definition printing technology, with a 203DPI resolution to provide you with clear printing results. This mobile printer is compatible with roll paper, folded paper and tattoo transfer paper, supporting printing from your mobile phone PDF, Word, pictures and web pages anytime and anywhere. It is recommended to use our NDYIN thermal paper to achieve good printing quality
  • Portable wireless printer for travel: The thermal printer is equipped with a built-in 1500mAh rechargeable battery, which can print 160 sheets of 8.5" x 11" thermal paper after being fully charged. It weighs only 1.5 pounds and is compact in size. This ink-free portable printer can be easily carried in a backpack or briefcase! It is perfect for business travel, cars, small offices, construction sites, schools and homes. You can print documents, contracts, invoices and boarding passes anytime and anywhere
  • The N80 thermal printer has a wide range of uses. The package includes the N80 printer, a roll of US Letter paper(7m/roll), a user manual, a guide card, a type-C soft cable and a type C adapter. Note: The charging adapter is not included. Special thermal paper is required for use; ordinary paper cannot be used. This ink-free portable thermal printer is suitable for various scenarios such as home, school, travel, office, and outdoor, meeting the printing needs of different groups of people. This tattoo template printer is also compatible with tattoo transfer paper, making it an ideal choice for tattoo art

Should I increase --javascript-delay indefinitely?

No. A larger delay only waits longer; it cannot make a failed request or never-assigned status succeed. Use the smallest bounded delay that covers the page’s rendering work and retain an outer process deadline.

Why does a successful PDF have a non-empty stderr stream?

wkhtmltopdf can write warnings and resource messages while still returning exit code zero. Judge success primarily by the exit code and output validation, while retaining stderr for diagnosis.

Is Xvfb required for every wkhtmltopdf binary?

No. Determine whether your specific build uses patched Qt and test it under the service account. Add Xvfb only when the binary and environment require an X display.

Frequently Asked Questions

Can a PHP request be made asynchronous instead?

Yes. Queue the conversion in a worker and return a job identifier, but the worker still needs the same stream draining, environment, timeout, and child-reaping controls.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What should I validate after wkhtmltopdf exits successfully?

Check that the output file exists, is readable by the response process, has a nonzero size, and begins with the expected PDF signature before sending it.

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.