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

Exit code 1 is not a diagnosis. It means wkhtmltopdf stopped because of a problem that is usually explained by the preceding stderr line: an unreachable URL, blocked local file, missing shared library or font, invalid X display, authentication failure, or a page-load error. Capture the complete command and stderr as the same Unix user that runs Django, then fix that specific failure before changing error handling.

What exit code 1 actually tells you

wkhtmltopdf reports a nonzero exit status when it cannot complete the conversion. Code 1 is therefore a symptom, not a universal “Django error.” The useful evidence is the first explicit Error: line and the final exit-code message. Save both; a shortened exception such as “wkhtmltopdf reported an error” removes the information needed to troubleshoot.

Common classes of failure include:

  • The executable cannot be found or started.
  • A required shared library or font configuration is missing.
  • A headless invocation cannot connect to its X server.
  • The renderer cannot resolve, connect to, authenticate to, or follow the target URL.
  • CSS, images, or fonts point to local files that wkhtmltopdf is not allowed to read.
  • A page-load error is handled with the default abort policy.

Do not begin by setting every load error to “ignore.” That can produce a PDF that looks successful but is missing page content.

1. Reproduce the failure as the Django service user

A command that works in your shell can fail under systemd, Supervisor, a container entrypoint, or a web-server account because PATH, permissions, certificates, DNS, proxy variables, and home directories differ.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Log the exact wkhtmltopdf command, its arguments, the working directory, the Unix user, all stderr, and the output path. Preserve the first Error: line and the final exit status.
  2. Switch to the account that runs Django (for example, the account named in your service unit) and verify the executable:
sudo -u YOUR_DJANGO_USER which wkhtmltopdf
sudo -u YOUR_DJANGO_USER /absolute/path/to/wkhtmltopdf --version

If which returns nothing, do not rely on the interactive shell’s PATH. Configure an absolute path in Django and confirm that the file is executable by that user.

  1. Run a minimal conversion as that same user, writing to a directory it can create:
sudo -u YOUR_DJANGO_USER /absolute/path/to/wkhtmltopdf 
  https://example.com /tmp/wkhtmltopdf-test.pdf

Use the real URL and output directory when narrowing the problem. If this direct command fails, Django is only reporting the underlying renderer failure.

2. Point Django at a real wkhtmltopdf binary

The Django integration package is a wrapper; it does not install the wkhtmltopdf executable for you. Set WKHTMLTOPDF_CMD to an absolute path when PATH lookup is unreliable.

# settings.py
WKHTMLTOPDF_CMD = "/usr/local/bin/wkhtmltopdf"
WKHTMLTOPDF_CMD_OPTIONS = {
    "encoding": "utf8",
    "load-error-handling": "abort",
    "load-media-error-handling": "ignore",
}

Replace the path with the result of the version check on your server. Check executable permissions and parent-directory traversal permissions for the service account. “No such file or directory” can mean either that the path is wrong or that the executable’s interpreter/shared libraries are unavailable; inspect the next startup error before changing application code.

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

3. Install libraries, fonts, and writable directories

Shared libraries

On Ubuntu, the django-wkhtmltopdf documentation specifically requires libfontconfig. Install the package appropriate for your distribution, then rerun wkhtmltopdf --version as the service user. A startup message such as error while loading shared libraries indicates a system dependency problem, not a template problem.

Fonts

Fonts must be installed on the server and readable by the account rendering the document. A missing font can appear as a startup failure or as a PDF with substituted glyphs. Verify the font files, the font configuration visible to the service user, and any application-specific font directory. If the PDF contains boxes or unexpected wrapping after the command succeeds, inspect fonts before changing CSS.

Temporary and output paths

wkhtmltopdf needs to create temporary files and write the final PDF. Use directories owned by, or explicitly writable to, the Django account. “Permission denied” on the executable, temporary directory, asset directory, or output file is fixed with filesystem ownership and mode—not with load-error-handling.

4. Configure headless X correctly

Some deployments invoke wkhtmltopdf with --use-xserver. In that mode a valid X server and display are required. “Could not connect to display” and similar X errors mean the display is absent, stopped, or different from the value inherited by Django.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
# settings.py — only when an X server is used
WKHTMLTOPDF_ENV = {"DISPLAY": ":2"}

Use the display supplied by your deployment instead of :2 if it differs. Confirm that the X server is running, that the service account is permitted to connect, and that the environment reaches the Django worker. If you are not using an X server, remove an accidental --use-xserver option rather than inventing a display value.

5. Test the URL from the renderer host

A laptop browser and a private server do not necessarily have the same DNS, routes, proxy, TLS trust, or credentials. Test the exact scheme and URL from the machine (or container) where wkhtmltopdf runs, using the same authentication and proxy configuration.

  • Redirects: follow the complete redirect chain and ensure the final host is reachable. A redirect to about:blank or an unexpected scheme can trigger a protocol error.
  • Authentication: supply the credentials, cookies, custom headers, or network path required by the protected page. A 401 or 403 is a reachability/authentication issue, not a PDF setting.
  • DNS and binding: verify that the hostname resolves inside the server or container. Django’s runserver binds to 127.0.0.1 by default, so a separate renderer cannot reach it through a different network namespace or host address.
  • TLS and proxy: check the server’s CA trust and proxy rules. A browser on a workstation may trust a certificate that the renderer host does not.
  • Timeouts and partial responses: make sure the application finishes rendering within the renderer’s load window and that intermediate proxies do not terminate the connection.

Errors such as ProtocolUnknownError, connection failure, timeout, 404, 401, or 403 all require testing the request path first. The exact exit-code mapping varies by failure, so use stderr rather than assuming every network problem is code 1.

6. Fix blocked CSS, images, and fonts

wkhtmltopdf disables local-file access unless it is explicitly allowed. A template that references file:///... or a filesystem path can therefore fail with Blocked access to file.

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

Preferred approach: serve assets over HTTP(S)

Give the renderer absolute, reachable URLs for stylesheets, images, and fonts. Ensure the server endpoint is available from the renderer host, not only from a developer’s browser. This also makes redirects, authentication, and TLS behavior visible in the same way as the page itself.

When local files are unavoidable

Allow only the specific asset directory, rather than opening the entire filesystem:

wkhtmltopdf --allow /srv/myapp/static/pdf-assets 
  https://internal.example/report /tmp/report.pdf

Apply the equivalent option through your Django wrapper’s command-options dictionary. Confirm that every parent directory is searchable by the service user. A narrowly scoped allow-list reduces accidental exposure while fixing the blocked-file error.

7. Choose load-error handling deliberately

The documented default for --load-error-handling is abort. The alternatives are ignore and skip:

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.
Handler Behavior Use it when
abort Stop when a page fails to load. The PDF must contain complete, trustworthy content.
ignore Continue despite a load error. A missing optional resource is acceptable and you have inspected the output.
skip Skip the failing resource/page according to wkhtmltopdf’s handling. You explicitly accept omitted content and have a validation step.

There is a separate media-resource setting. The following keeps navigation failures fatal while allowing a missing media resource:

WKHTMLTOPDF_CMD_OPTIONS = {
    "encoding": "utf8",
    "load-error-handling": "abort",
    "load-media-error-handling": "ignore",
}

Do not use ignore to hide a private URL that the renderer cannot reach. Fix DNS, routing, authentication, or application binding first, then decide whether an optional image or media file may be absent.

8. Remember Django’s production boundary

Django documents that runserver is for development and binds to 127.0.0.1 by default. A renderer on another host, container, or network therefore needs a production endpoint reachable through your deployment network, with proxy and HTTPS settings consistent with the request. Test the URL from the renderer host, not from the machine where you edit templates.

Keep the PDF endpoint protected. If it requires a session or authorization header, configure the request path used by wkhtmltopdf and avoid exposing a debug-only server merely to make conversion work.

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

9. If conversion succeeds but the layout is wrong

A zero exit status proves that a PDF was produced, not that it matches a modern browser. wkhtmltopdf 0.12.6 uses the Qt WebKit engine, whose documented limitations include missing flexbox, grid, and much CSS introduced during the last decade.

  • Replace flexbox and grid layout with simpler block, table, or positioned structures for the PDF template.
  • Use explicit widths, heights, margins, and line-heights where pagination matters.
  • Verify that every stylesheet and font loaded successfully before judging the layout.
  • If modern CSS is a hard requirement, evaluate a maintained rendering engine instead of treating a successful wkhtmltopdf exit as proof of compatibility.

Error-to-action map

Observed message Likely cause First corrective action
No such file or directory or permission denied Wrong command path, PATH, executable mode, or service-user access Set WKHTMLTOPDF_CMD absolutely and run --version as the Django user.
error while loading shared libraries or font startup failure Missing libraries or fonts Install required dependencies, including libfontconfig on Ubuntu, and verify readable fonts.
Could not connect to display Missing or inaccessible X server Start/connect to the configured server and correct WKHTMLTOPDF_ENV["DISPLAY"].
Blocked access to file Local-file access disabled Use absolute HTTP(S) assets or a narrowly scoped --allow directory.
ProtocolUnknownError, redirect, 401/403/404, timeout URL, DNS, routing, TLS, proxy, or authentication failure Request the exact URL from the renderer host with the same credentials and trust store.
Success but missing modern layout Qt WebKit CSS limitations Simplify the PDF CSS or choose a maintained rendering engine.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

A repeatable server checklist

  1. Capture the complete command, stderr, exit status, user, and output path.
  2. Run the configured binary and --version as that user.
  3. Resolve shared libraries, libfontconfig on Ubuntu, fonts, temporary directories, and output permissions.
  4. If applicable, verify the X server and DISPLAY.
  5. Request the exact URL from the renderer host and fix redirects, authentication, DNS, TLS, proxy, or binding issues.
  6. Replace local asset references with absolute HTTP(S) URLs, or allow only the required directory.
  7. Keep page-load handling at abort unless incomplete content is an intentional, validated outcome.
  8. After a successful conversion, inspect CSS compatibility and the actual PDF pages.

Or skip the browser setup

If your goal is a clean website capture or PDF rather than maintaining a wkhtmltopdf process, ScreenshotNeo provides a single HTTP endpoint and an MCP server for AI agents. Before capture it accepts cookie/consent banners 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 call can return PNG, JPEG, WebP, or a PDF. The API supports full-page capture with lazy images, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper/margins/landscape/page ranges, custom CSS and JavaScript, pre-capture clicks, hidden selectors, selector/delay/network-idle waits, request and resource blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs, which can simplify migration.

Use the ScreenshotNeo API documentation for authentication and options. The following examples use https://stripe.com; replace it with your target URL.

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

cURL

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

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

Every feature is included on every plan. The Free plan includes 1,000 shots per month with no card; paid plans are 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 exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Create a free ScreenshotNeo account to use 1,000 screenshots a month without a card.

Frequently Asked Questions

Does installing a newer wkhtmltopdf automatically add flexbox and grid support?

No. The Qt WebKit engine used by wkhtmltopdf 0.12.6 still has documented limits, so changing the binary alone does not make modern CSS layouts reliable.

Why does the same private URL work in my browser but not on the server?

The renderer may be on a different network, DNS scope, proxy path, certificate trust store, or authentication context. Test the exact URL from the renderer host under the Django service account.

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

Should I permanently set load-error-handling to ignore in production?

Only if omitted content is acceptable and you validate the resulting PDF. The documented default is abort because silently incomplete documents can be misleading.

Can I keep using Django runserver for a production PDF endpoint?

Django documents runserver as a development server. Use a production endpoint reachable by the renderer and configure its proxy and HTTPS behavior consistently.

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.