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

A zero-byte PDF means that no PDF bytes reached the file your PHP code is checking or serving. The reliable fix is to make wkhtmltopdf’s output mode explicit, capture stdout and stderr separately, obtain the process exit code, and validate the exact file path and its %PDF- signature before returning it. Progress text alone does not prove that a PDF was written.

What a zero-byte result actually tells you

The symptom does not identify one universal cause. It can come from a mismatch between a named output file and stdout, an incorrectly built command, a failed child process, permissions or environment differences, or PHP inspecting a different path from the one wkhtmltopdf used.

wkhtmltopdf converts HTML pages or document objects into PDF. Its documented command-line form places the input object(s) before the output target: wkhtmltopdf input.html output.pdf. The project also documents an output mode that writes PDF bytes to stdout. Read the wkhtmltopdf documentation and the command-line usage reference for the build installed on your server.

Choose one destination and keep every layer consistent:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
PDF Converter Ultimate - Convert PDF files into Word, Excel, PowerPoint and others - PDF converter software with OCR recognition compatible with Windows 11 / 10 / 8.1 / 8 / 7
  • Convert your PDF files into Word, Excel & Co. the easy way
  • Convert scanned documents thanks to our new 2022 OCR technology
  • Adjustable conversion settings
  • No subscription! Lifetime license!
  • Compatible with Windows 11, 10, 8.1, 7 - Internet connection required
  • Named file: wkhtmltopdf creates /absolute/path/report.pdf; PHP validates and serves that file.
  • Stdout: wkhtmltopdf emits binary PDF data on descriptor 1; PHP must capture descriptor 1 as the PDF and keep descriptor 2 for diagnostics.

Do not ask wkhtmltopdf to write a named file while PHP waits for PDF bytes on stdout, and do not merge diagnostic output into a stream that is supposed to contain binary PDF data.

Establish a known-good command first

  1. Create a minimal local file such as /tmp/probe.html containing a heading and plain text. This removes remote assets and application templates from the first test.
  2. Run the exact executable with an absolute output path from a shell account that represents the deployment:
wkhtmltopdf /tmp/probe.html /tmp/probe.pdf
printf 'exit=%s size=' "$?"
wc -c < /tmp/probe.pdf
head -c 5 /tmp/probe.pdf

A successful artifact should exist, have a nonzero size, and begin with %PDF-. Then run the same executable, input, arguments and destination from PHP. If the shell succeeds and PHP fails, compare the runtime identity, working directory, environment, temporary directory, permissions and process restrictions instead of changing renderer options at random.

For stdout mode, make the destination unambiguous at the shell:

Rank #2
Doxillion Free Document Converter – Converts DOCX, DOC, PDF, WPS and Many More Files Quickly [Download]
  • Convert over 50 document file formats.
  • Preview your files from Doxillion before converting them.
  • Use batch conversion to convert thousands of files at once.
  • Enjoy an easy-to-use, intuitive interface with a Drag and Drop file option.
  • Burn your converted or original files directly to disc.
wkhtmltopdf /tmp/probe.html - > /tmp/probe.pdf 2> /tmp/probe.err

Here stdout is the PDF and stderr is the diagnostic log. The two streams must never be treated as one file.

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

A PHP implementation that records the evidence

PHP’s proc_open() can connect a child process’s stdin, stdout and stderr to pipes or files. The following example deliberately uses absolute paths and separate files for stdout and stderr, obtains the exit status with proc_close(), and validates the resulting PDF. It uses the array command form available in modern PHP; confirm the details for your PHP version and operating system.

<?php
$binary = '/usr/local/bin/wkhtmltopdf';
$input  = '/var/www/app/probe.html';
$output = '/var/www/app/var/report.pdf';
$stdoutLog = tempnam(sys_get_temp_dir(), 'wkhtml-stdout-');
$stderrLog = tempnam(sys_get_temp_dir(), 'wkhtml-stderr-');

if (!is_string($stdoutLog) || !is_string($stderrLog)) {
    throw new RuntimeException('Unable to create diagnostic files');
}

$command = [$binary, '--quiet', $input, $output];
$descriptors = [
    0 => ['file', '/dev/null', 'r'],
    1 => ['file', $stdoutLog, 'ab'],
    2 => ['file', $stderrLog, 'ab'],
];

$process = proc_open($command, $descriptors, $pipes, '/var/www/app');
if (!is_resource($process)) {
    throw new RuntimeException('proc_open() could not start wkhtmltopdf');
}

// The descriptors use files, so there are no open pipes to drain.
$exitCode = proc_close($process);
$stdout = file_get_contents($stdoutLog) ?: '';
$stderr = file_get_contents($stderrLog) ?: '';

$size = is_file($output) ? filesize($output) : false;
$signature = '';
if (is_file($output)) {
    $handle = fopen($output, 'rb');
    if ($handle !== false) {
        $signature = fread($handle, 5) ?: '';
        fclose($handle);
    }
}

if ($exitCode !== 0 || $size === false || $size === 0 || $signature !== '%PDF-') {
    error_log(json_encode([
        'command' => $command,
        'exit_code' => $exitCode,
        'output' => $output,
        'size' => $size,
        'signature' => $signature,
        'stdout' => $stdout,
        'stderr' => $stderr,
    ], JSON_UNESCAPED_SLASHES));
    throw new RuntimeException('wkhtmltopdf did not produce a valid PDF');
}

// Only now is it safe to stream $output to the caller.
header('Content-Type: application/pdf');
header('Content-Length: ' . (string) $size);
readfile($output);

When you configure descriptors as pipes instead of files, read both output streams without allowing one full pipe to block the child, close every pipe, and only then call proc_close(). The PHP manual warns that failing to close pipes can cause a deadlock. Keep stderr separate from binary stdout; it is where renderer warnings and errors belong.

Rank #3
PDF Extra 2024| Complete PDF Reader and Editor | Create, Edit, Convert, Combine, Comment, Fill & Sign PDFs | Lifetime License | 1 Windows PC | 1 User [PC Online code]
  • EDIT text, images & designs in PDF documents. ORGANIZE PDFs. Convert PDFs to Word, Excel & ePub.
  • READ and Comment PDFs – Intuitive reading modes & document commenting and mark up.
  • CREATE, COMBINE, SCAN and COMPRESS PDFs
  • FILL forms & Digitally Sign PDFs. PROTECT and Encrypt PDFs
  • LIFETIME License for 1 Windows PC or Laptop. 5GB MobiDrive Cloud Storage Included.

If you intentionally use stdout mode, change the command to end in '-', connect descriptor 1 directly to the intended PDF file (or read it as binary), and keep descriptor 2 in a text log. Do not also pass a named PDF output path.

Use an exit code, not empty command output

shell_exec() returns command output, but PHP documents that it cannot distinguish execution failure from a command that simply produced no output. A null or empty return therefore says nothing reliable about PDF generation. Use exec() when you need an exit status, or use proc_open() when you need controlled stdin, stdout, stderr and working-directory handling.

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

A reported PHP invocation displayed renderer progress while its result file remained zero bytes. That is why progress output is diagnostic information, not proof of a valid destination file; see the dated wkhtmltopdf issue report from 2015 for an example.

Rank #4
MobiPDF Lifetime - Professional PDF Editor for Windows | Edit, Sign & Convert PDFs | Best Adobe Acrobat Pro Alternative | Lifetime License
  • Edit PDFs with Ease. Modify text, images, and layouts directly within your PDF documents.
  • Convert & Organize. Export PDFs to Word, Excel, or ePub, and organize files with ease.
  • Read & Annotate. Enjoy intuitive reading modes and powerful tools to comment, highlight, and mark up PDFs.
  • Create & Manage PDFs. Create new PDFs, combine multiple files, scan documents, and compress for easy sharing.
  • Fill & Sign Forms. Complete forms and digitally sign documents with secure e-signature tools.

Compare the CLI and PHP runs systematically

Comparison What to record Why it matters
Executable Absolute path and reported version PHP may resolve a different binary from the one in an interactive shell.
Arguments Input, output mode, flags and destination A named file and stdout are different contracts.
Input Absolute HTML path or URL and readable assets The service account may not see the same files or network resources.
Identity Operating-system user and group Write and execute permissions can differ from your login account.
Working directory Explicit directory passed to proc_open() Relative paths can resolve to different locations.
Environment PATH, temporary directory and relevant policy settings Web workers often have a smaller environment or restricted process execution.
Result Exit code, stderr, existence, size and first five bytes These checks distinguish a renderer failure from a file-path or validation mistake.

Log the command as an argument array where possible, but redact credentials, cookies and authorization headers. Record enough information to reproduce the failure without exposing sensitive page data.

Filesystem and hosting checks

  • Use an output directory that already exists and is writable by the PHP worker. Check the directory entry, not only the parent directory’s apparent permissions.
  • Use absolute paths while diagnosing. A relative path can cause wkhtmltopdf to write one file while PHP checks another.
  • Verify that the worker can execute the selected binary and read the HTML and referenced local assets.
  • Check whether PHP configuration or hosting policy disables process creation. A failed proc_open() call is different from wkhtmltopdf starting and returning a renderer error.
  • Capture stderr even when the exit code is zero. Warnings can explain an incomplete or unexpected document.
  • Do not stream a file merely because it exists. Require a regular file, nonzero size and a leading %PDF- signature.

A valid signature is necessary, not a complete PDF integrity test. If a later consumer reports a damaged document, preserve stderr and inspect the complete file with a PDF validator appropriate to your environment.

Common failure symptoms and fixes

Symptom Likely branch to investigate Fix
proc_open() returns no usable process PHP process execution is unavailable, the binary path is wrong, or the working directory is invalid. Log the creation failure, use an absolute executable path, verify hosting policy and directory access.
Exit status indicates failure and stderr has text wkhtmltopdf rejected an argument, could not load input, or failed during rendering. Run the identical command manually, read stderr, and reduce it to the minimal HTML probe.
Exit status is successful but the expected path is absent The command wrote to another path or emitted bytes to stdout. Print the exact output argument, working directory and output mode; validate that same absolute path.
File exists but is zero bytes PHP opened or truncated the wrong file, output was redirected incorrectly, or the process did not deliver PDF bytes. Check descriptor mapping, named-file versus stdout usage, exit status, stderr and the file’s owner and permissions.
PHP run fails while shell run succeeds Different user, PATH, temporary directory, current directory, environment or process restrictions. Run both with the same absolute paths and compare every axis in the table above.
Browser receives a blank or corrupt response The response was sent before validation, or logs were mixed into binary output. Validate first, set Content-Type: application/pdf, send the exact byte length, and keep diagnostics out of the response.
A wrapper reports only “send” or “save” failure The wrapper has hidden the underlying process details. Use its error accessor, preserve the generated command and stderr, then perform the same file and signature checks.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When a PHP wrapper is involved

The mikehaertl/phpwkhtmltopdf project documents checking the return value from send(), saveAs() or toString(), then reading getError() when an operation fails. Those methods are specific to that wrapper; another package may expose different names. Regardless of the library, retain the underlying stderr and exit status when available, and validate the resulting file rather than trusting a wrapper return value alone.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
PDF Pro 3 - PDF editor to create, edit, convert and merge PDFs - 100% Compatible with Adobe Acrobat - for Windows 11, 10, 8.1, 7
  • ALL-IN-ONE SOLUTION – read, edit, convert, merge and protect your PDF files
  • MAXIMUM FUNCIONALITY – create interactive forms, compare PDFs, bates numbering, find and replace text or colors, convert documents, OCR engine, comment, highlight, fill out and print forms, document protection and others
  • EASY TO INSTALL AND USE – well-structured user-interface, in-program instructions, free tech support whenever you need it
  • GREAT VALUE FOR MONEY - why spend a fortune if you can have maximum functionality at a reasonable price - this also fits the requirements of companies very well

Performance and reliability practices

  • Keep a minimal local probe available so you can separate renderer startup and permissions from application HTML complexity.
  • Use one explicit working directory and absolute paths in production jobs.
  • Give each job a unique temporary output and log path, then move a validated PDF into its final location. This prevents a reader from seeing a partially written file.
  • Set an application-level timeout and terminate a stuck child according to your process policy; always preserve stderr before cleanup.
  • For repeated jobs, record the executable version, exit code, output size and signature result so an environment change is visible.
  • Never expose command arguments containing cookies, authorization headers or private URLs in public error messages.

Or skip the browser setup

If your actual requirement is a clean capture of a public webpage rather than a local wkhtmltopdf rendering pipeline, ScreenshotNeo is a managed alternative. It accepts a URL and can return PNG, JPEG, WebP or PDF. Before capture it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

One request looks like this; the complete parameter reference is in 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

The same call from PHP is:

<?php
$q = http_build_query([
    'access_key' => 'YOUR_API_KEY',
    'url' => 'https://stripe.com',
]);
$r = file_get_contents('https://api.screenshotneo.com/v1/shot?' . $q);
file_put_contents('shot.webp', $r);

Python:

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(`ScreenshotNeo returned ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. Every feature is on every plan: 1,000 shots per month are free with no card, and paid plans start at $5 for 3,000 shots. If that workflow fits your page-capture use case, create a free ScreenshotNeo account.

Final validation checklist

  1. Confirm the exact executable, version, input and output mode.
  2. Run the minimal command directly, then under the PHP service identity.
  3. Keep stdout (possible PDF bytes) separate from stderr (diagnostics).
  4. Close pipes before proc_close() when pipes are used.
  5. Record the exit code and stderr.
  6. Validate the exact absolute output path, regular-file status, nonzero size and %PDF- signature.
  7. Only after all checks pass, send the PDF response or publish the file.

Frequently Asked Questions

Does the 2015 wkhtmltopdf issue identify the cause on my server?

No. It demonstrates that progress output can coexist with a zero-byte result, but your executable, arguments, account and filesystem still need to be checked independently.

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

Can a wrapper library eliminate the need to inspect stderr?

No. A wrapper may expose an error method, but stderr, the process status and the final file checks remain the authoritative troubleshooting evidence.

Quick Recap

Bestseller No. 1
PDF Converter Ultimate - Convert PDF files into Word, Excel, PowerPoint and others - PDF converter software with OCR recognition compatible with Windows 11 / 10 / 8.1 / 8 / 7
PDF Converter Ultimate - Convert PDF files into Word, Excel, PowerPoint and others - PDF converter software with OCR recognition compatible with Windows 11 / 10 / 8.1 / 8 / 7
Convert your PDF files into Word, Excel & Co. the easy way; Convert scanned documents thanks to our new 2022 OCR technology
$29.99
Bestseller No. 2
Doxillion Free Document Converter – Converts DOCX, DOC, PDF, WPS and Many More Files Quickly [Download]
Doxillion Free Document Converter – Converts DOCX, DOC, PDF, WPS and Many More Files Quickly [Download]
Convert over 50 document file formats.; Preview your files from Doxillion before converting them.
Bestseller No. 3
PDF Extra 2024| Complete PDF Reader and Editor | Create, Edit, Convert, Combine, Comment, Fill & Sign PDFs | Lifetime License | 1 Windows PC | 1 User [PC Online code]
PDF Extra 2024| Complete PDF Reader and Editor | Create, Edit, Convert, Combine, Comment, Fill & Sign PDFs | Lifetime License | 1 Windows PC | 1 User [PC Online code]
READ and Comment PDFs – Intuitive reading modes & document commenting and mark up.; CREATE, COMBINE, SCAN and COMPRESS PDFs
$99.99
Bestseller No. 4
MobiPDF Lifetime - Professional PDF Editor for Windows | Edit, Sign & Convert PDFs | Best Adobe Acrobat Pro Alternative | Lifetime License
MobiPDF Lifetime - Professional PDF Editor for Windows | Edit, Sign & Convert PDFs | Best Adobe Acrobat Pro Alternative | Lifetime License
Edit PDFs with Ease. Modify text, images, and layouts directly within your PDF documents.; Convert & Organize. Export PDFs to Word, Excel, or ePub, and organize files with ease.
$99.99
Bestseller No. 5
PDF Pro 3 - PDF editor to create, edit, convert and merge PDFs - 100% Compatible with Adobe Acrobat - for Windows 11, 10, 8.1, 7
PDF Pro 3 - PDF editor to create, edit, convert and merge PDFs - 100% Compatible with Adobe Acrobat - for Windows 11, 10, 8.1, 7
ALL-IN-ONE SOLUTION – read, edit, convert, merge and protect your PDF files
$29.99

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.