If pdfkit raises FileNotFoundError: [Errno 2] No such file or directory: 'which', Python has not yet failed to find wkhtmltopdf. It failed earlier while trying to launch the Unix discovery utility named which. Restore that utility and its PATH, or bypass discovery by passing pdfkit the absolute path to the wkhtmltopdf executable.
The deterministic fix is pdfkit.configuration(wkhtmltopdf='/absolute/path/to/wkhtmltopdf'). The path must be valid in the same user, service, container, virtual environment, or CI job that runs your Python process.
What the traceback is actually telling you
python-pdfkit is a wrapper around the wkhtmltopdf command-line program. On Unix-like systems, pdfkit normally discovers that program by launching which wkhtmltopdf. On Windows, it uses where wkhtmltopdf.
That makes the filename in the exception significant:
#1 Best Overall
'which'is missing: the operating system could not start the discovery utility. This is an environment problem, not proof that wkhtmltopdf is absent.wkhtmltopdfis missing: discovery ran, but it did not find an executable in the processPATH.- wkhtmltopdf returns a command error: discovery and executable lookup succeeded; the failure is now in HTML loading, options, resources, permissions, or wkhtmltopdf itself.
A wkhtmltopdf installation that works in your interactive terminal does not guarantee that a web server, queue worker, container, virtual environment, or CI runner can launch which or see the same PATH.
Check the exact runtime that runs Python
Run these checks as the same operating-system user and inside the same service, container, or CI job that executes the application. Testing in a separate terminal can hide the real problem.
Unix-like systems
which wkhtmltopdf
python -c "import os; print(os.environ.get('PATH', ''))"
python -c "import shutil; print(shutil.which('which')); print(shutil.which('wkhtmltopdf'))"
A healthy result is an absolute path for both commands, such as /usr/bin/which and /usr/local/bin/wkhtmltopdf. If the first command itself reports that which cannot be found, install or restore the operating-system package that supplies it, then make sure its directory is in PATH.
Windows
where wkhtmltopdf
py -c "import os; print(os.environ.get('PATH', ''))"
py -c "import shutil; print(shutil.which('where')); print(shutil.which('wkhtmltopdf'))"
Windows discovery uses where, not which. If a Windows traceback names which, inspect how the application was installed or whether a Unix-oriented environment is being used; native Windows pdfkit discovery should use where.
Restore the discovery utility and PATH
If which is unavailable on a Unix-like machine, use that operating system’s package manager to install or restore the package that provides the command. Then open a new shell or restart the service so it receives the updated PATH. Verify with which wkhtmltopdf before returning to Python.
Do not assume that activating a virtual environment changes system commands. A virtual environment changes Python packages; the worker still needs access to the operating-system utility and executable.
Rank #2
Service and container environments
Interactive shell startup files often add directories to PATH that are not loaded by systemd, Supervisor, Docker, a task queue, or a hosted CI runner. Set PATH in the service definition or container image, or use an absolute executable path in pdfkit. Restart the process after changing its environment; a long-running worker keeps the old value.
Install wkhtmltopdf and verify it independently
Once discovery works, install wkhtmltopdf if the executable is not present. The project documents these package-manager commands:
Free tools Windows power users keep installed
One-click scans. No signup required.
# Debian or Ubuntu
sudo apt-get install wkhtmltopdf
# macOS
brew install homebrew/cask/wkhtmltopdf
On Windows, install a platform-appropriate build from the official wkhtmltopdf project downloads. Whichever method you use, verify that the command returns a real path:
which wkhtmltopdf # Unix-like systems
where wkhtmltopdf # Windows
Run that returned path directly with a minimal input before involving pdfkit. For example, create a small test.html containing one heading and invoke the executable against it. If the executable itself emits an error, fix that issue first; changing Python code will not repair a broken binary, missing permission, or inaccessible input.
Bypass discovery with an absolute path
When PATH differs between environments, explicit configuration is the most reproducible solution. Pass the full path to wkhtmltopdf when creating pdfkit’s configuration object:
import pdfkit
config = pdfkit.configuration(
wkhtmltopdf='/absolute/path/to/wkhtmltopdf'
)
pdfkit.from_string(
'<h1>Hello</h1>',
'out.pdf',
configuration=config
)
Use the same configuration=config argument with URL and file conversions:
Recommended Free Tools
import pdfkit
config = pdfkit.configuration(
wkhtmltopdf='/absolute/path/to/wkhtmltopdf'
)
pdfkit.from_url('https://example.com', 'page.pdf', configuration=config)
pdfkit.from_file('input.html', 'file.pdf', configuration=config)
Replace the example path with the path returned by the runtime check. In deployment, keep it in application configuration rather than hard-coding a developer laptop’s location:
import os
import pdfkit
wkhtmltopdf = os.environ['WKHTMLTOPDF_PATH']
config = pdfkit.configuration(wkhtmltopdf=wkhtmltopdf)
pdfkit.from_string(html, output_path, configuration=config)
This approach also makes a bad deployment obvious: the process fails at startup when WKHTMLTOPDF_PATH is absent instead of failing later with an ambiguous discovery traceback.
Separate discovery failures from conversion failures
Debug in stages so each error has one likely cause:
- Can the process launch the discovery command? Check
whichon Unix-like systems orwhereon Windows. - Does discovery return wkhtmltopdf? Confirm the result is an executable file, not a stale symlink or a directory.
- Can the binary run by itself? Convert a minimal local HTML file and inspect its standard error output.
- Can pdfkit invoke it? Pass the absolute path and run the smallest
from_stringexample. - Does the real document load? Only after the preceding checks should you investigate remote assets, JavaScript, CSS, authentication, options, or output permissions.
After the which problem is fixed, a new error is progress: it means execution reached a later stage. Capture the generated command and wkhtmltopdf’s stderr when diagnosing that next failure.
Choose the remedy for your deployment
| Remedy | Best fit | Reproducibility | Trade-off |
|---|---|---|---|
Install or restore which and repair PATH |
A developer machine or a managed host where you control the operating-system packages | Depends on every runtime receiving the same environment | Simple locally, but service and shell environments can drift |
| Install wkhtmltopdf through the documented package or official build | A new machine that has no converter installed | Good when the image or host is rebuilt from a documented setup | Still requires discovery or explicit configuration |
Pass an absolute wkhtmltopdf path |
Containers, CI, web workers, and locked-down services | Highest, provided the path is stable in the image or host | You must update configuration when the binary location changes |
For a reproducible deployment, install the binary in the image or provisioning step, set a known path, and pass that path explicitly. For local development, repairing the missing utility and verifying which wkhtmltopdf is usually the quickest route.
Common errors and targeted fixes
[Errno 2] No such file or directory: 'which'
Cause: pdfkit tried to execute the Unix discovery utility and the process could not find it. Fix: install or restore the operating-system package that supplies which, expose its directory through PATH, or configure an absolute wkhtmltopdf path.
No wkhtmltopdf executable found
Cause: discovery ran, but no usable wkhtmltopdf was found in the current process environment. Fix: install wkhtmltopdf, correct PATH, remove stale links, or pass wkhtmltopdf='/absolute/path/to/wkhtmltopdf'.
It works in a shell but fails in a web request or worker
Cause: the service has a different user, working directory, permissions, or PATH. Fix: print the environment from the failing process, set the service’s environment explicitly, restart it, and prefer an absolute path.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →The error changes to a wkhtmltopdf command failure
Cause: executable discovery is fixed; conversion has failed later. Fix: run the returned binary directly, inspect stderr, check output-directory permissions, and reduce the input to a minimal HTML document before adding remote resources and options.
Conversion hangs or produces a blank document
Cause: the page or its resources are not completing in the wkhtmltopdf process. Fix: test a local static file, check network access from the service, verify that required assets are reachable without interactive authentication, and then add resources one at a time. This is separate from a missing which utility.
Version and security considerations
The official wkhtmltopdf project lists the stable series as 0.12.6 (project page dated 2020). Match the binary to your operating system and test the exact build in the environment where PDFs are generated; package availability and defaults can vary by platform.
The project warns: “Do not use wkhtmltopdf with any untrusted HTML.” Treat user-supplied HTML, CSS, JavaScript, and URLs as untrusted. Sanitize or isolate input before conversion, restrict what the process can access, and avoid allowing arbitrary file or network reads. An explicit path fixes command discovery; it does not make unsafe HTML safe.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Best Value
Performance, reliability, and cost notes
Every conversion starts an external program, so repeated calls can add process-start overhead. Keep HTML small while diagnosing failures, avoid unnecessary remote assets, and make sure the worker has enough CPU, memory, temporary storage, and network access for the documents it renders.
Reliability comes from controlling the complete runtime: pin the wkhtmltopdf installation in your machine image or provisioning script, set the path explicitly, run a startup health check, and log the executable path plus stderr for failed jobs. Do not count a successful interactive test as a service health check.
pdfkit and wkhtmltopdf are separate components. Installing the Python package does not install the operating-system converter, and installing the converter does not provide the which utility. Treat each dependency and each environment’s PATH as an independently verifiable requirement.
Or skip the browser setup
If your actual goal is a clean image or PDF of a page that is already available at a URL, ScreenshotNeo provides a hosted alternative instead of maintaining a browser-and-renderer stack. It is not a drop-in replacement for converting private local HTML with wkhtmltopdf, but it can capture a reachable website with one request.
For example, this cURL request returns a WebP screenshot:
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 API documentation for parameters and response headers. The equivalent Python and Node.js calls are:
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)
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}`);
const data = Buffer.from(await res.arrayBuffer());
await fs.promises.writeFile('shot.webp', data);
ScreenshotNeo accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; 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 status in X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Plans include 1,000 screenshots per month free with no card, then Starter at $5 for 3,000, Growth at $15 for 15,000, Pro at $39 for 60,000, Scale at $99 for 250,000, and Business at $249 for 1,000,000; yearly billing gives two months free and every feature is included on every plan. Start with 1,000 free ScreenshotNeo screenshots a month with no card.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →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.




