Why Are Web Fonts Missing in wkhtmltoimage Screenshots?
Diagnose missing or substituted fonts in wkhtmltoimage by checking font requests, local-file access, runtime fonts, glyph coverage, and load timing.
Web fonts are missing in a wkhtmltoimage screenshot when the renderer cannot retrieve the font stylesheet or font file, cannot access a local font, cannot find a usable font or glyph in its runtime, or captures the page before delayed font loading finishes. A page can load while its font file fails: hosted fonts commonly involve a stylesheet request followed by a separate font-file request. Diagnose those stages separately before changing renderer settings. The exact cause depends on your binary, operating system, page, and command.
Use this order: reproduce with a minimal page, verify the stylesheet and font-file URLs, check local-file permissions, confirm fonts in the renderer’s runtime, then investigate timing and glyph coverage. Treat a renderer-specific defect as a later hypothesis, not the starting assumption.
1. Make a minimal reproduction
Reduce the page to one font declaration and a short line containing the characters that fail. Save the exact input URL or local file, command, operating system, and output. Record the renderer version:
wkhtmltoimage --version
Compare the result with a browser if useful, but treat that comparison as a clue only. The browser and the screenshot process can differ in resource loading and installed fonts. There is no single compatibility matrix for every wkhtmltoimage build and operating system.
For a local diagnostic page, use an explicit font declaration and sample the affected glyphs. For example, replace the example URL and font family with the actual values from your page:
<!doctype html>
<meta charset="utf-8">
<style>
@font-face {
font-family: "ExampleWebFont";
src: url("https://example.com/fonts/example.woff2") format("woff2");
font-weight: 400;
font-style: normal;
}
.sample { font: 400 24px "ExampleWebFont", sans-serif; }
</style>
<p class="sample">Latin text — and the exact missing glyphs</p>
Keep the same font URL, family, weight, style, and sample characters as the failing page. Changing several variables at once makes the result harder to interpret.
2. Verify both requests for a hosted font
When a page uses a hosted web font, check both the stylesheet request and the font-file request referenced by that CSS. A successful page response does not establish that either font request succeeded. Google Fonts describes this two-stage process: the client gets CSS, then downloads the font specified by the stylesheet. The returned stylesheet can also be tailored to the requesting user agent. See the [Google Fonts technical overview](https://developers.google.com/fonts/docs/technical_considerations).
- Find the font stylesheet URL in the page’s CSS or HTML.
- Confirm the renderer can reach that URL and that it returns CSS, not an error page or redirect that the renderer cannot use.
- Inspect the CSS for the actual font-file URL selected for the requested family, weight, and style.
- Confirm that the font-file URL is reachable from the same machine or container running wkhtmltoimage.
- Check whether authentication, a proxy, TLS configuration, or network policy blocks either request.
Use your environment’s network diagnostics or server logs to inspect the requests. Do not infer font success just because the main document loaded. If the font file is unavailable, changing the screenshot delay will not make that URL accessible.
3. Check local font paths and file access
For local HTML or a local @font-face file, check how the URL resolves relative to the HTML file and whether the process can read the resulting path. The command reference documents local-file controls, including --allow, --disable-local-file-access, and --enable-local-file-access. Prefer granting access to the needed directory rather than enabling broad local access.
# Allow a specific directory containing the page's required local resources
wkhtmltoimage --allow /absolute/path/to/assets input.html output.png
When access is deliberately disabled, the renderer cannot read a local font unless the necessary path is allowed under the behavior of your build. Avoid using broad access as a routine fix. Consult the [wkhtmltoimage command reference](https://manpages.debian.org/unstable/wkhtmltopdf/wkhtmltoimage.1.en.html) and the project’s [settings documentation](https://wkhtmltopdf.org/usage/wkhtmltopdf.txt) for the options supported by your version.
4. Check fonts installed in the renderer’s runtime
A font installed on a developer’s workstation may be missing in a server image, container, or the user account that starts the renderer. Confirm that the intended font is installed and discoverable in that exact runtime. Qt’s Linux font documentation says Qt normally uses fontconfig to access installed system fonts and describes a fallback font database when fontconfig is unavailable. This is useful general guidance, but the cited documentation is for Qt 6.8 and does not certify every wkhtmltoimage build. See [Qt’s Linux font documentation](https://doc.qt.io/qt-6/linux-fonts.html).
- Check the font inside the running container or server image, not just on the host.
- Confirm the process user can read the font files and any font configuration it needs.
- Restart or rebuild the relevant runtime if fonts were added after it started, according to your deployment setup.
- Check whether the font format is supported by the rendering stack in your particular build.
Qt documents font-format handling through FreeType and notes that a font need not contain every Unicode character. That supports checking format and coverage; it does not identify the cause in a specific screenshot.
5. Check family, weight, style, size, and glyph coverage
Inspect the computed CSS assumptions in your page. The font-family name must match the family declared by @font-face; the requested weight and style need corresponding font data or a usable browser-style synthesis path. Then check that the font contains the exact missing characters. A font can render Latin text but fall back for another script, symbol, or emoji.
If text is visible but merely too small, --minimum-font-size <int> may be relevant. It is not a remedy for missing glyphs or a font file that was never loaded. The Qt WebKit settings reference also describes font-family and minimum-size configuration; use the settings actually supported by your binary rather than assuming all builds expose identical behavior. See the [Qt WebKit settings reference](https://doc.qt.io/qt-5/qwebsettings.html).
6. Test delayed loading only when timing is plausible
If JavaScript adds the stylesheet, changes the page after initial load, or waits on asynchronous work, test a short post-load delay and inspect JavaScript diagnostics:
wkhtmltoimage --javascript-delay 1500 --debug-javascript input.html output.png
--javascript-delay waits after page load; --debug-javascript displays JavaScript debugging output. A delay can test whether relevant work was still pending. It cannot fix a blocked font URL, a denied local path, a missing runtime font, or absent glyphs. Avoid increasing the delay without evidence that timing is the problem; it adds capture time and does not address those other causes. These controls are documented in the [command reference](https://manpages.debian.org/unstable/wkhtmltopdf/wkhtmltoimage.1.en.html).
7. Interpret load errors and isolate build behavior
--load-error-handling <handler> controls behavior when a page load error occurs. It does not make a failed font request succeed. Capture warnings and request diagnostics where available, then compare a minimal page across the exact builds and environments you can reproduce.
Historical Qt WebKit font-loading issues exist, but an archived report does not show that an unspecified modern package has the same problem. See the [archived WebKit issue](https://bugs.webkit.org/show_bug.cgi?id=27684) as historical context only. Consider a renderer-specific limitation after resource access, runtime fonts, timing, CSS matching, and glyph coverage have been checked.
Option reference
| Option or check | Use it for | Limit |
|---|---|---|
--allow <path> |
Permitting access to a specified path, such as a directory of local assets. | Does not install a font or repair an incorrect path. |
--disable-local-file-access / --enable-local-file-access |
Controlling access to other local files during local conversion. | Use deliberately; a broad access change is not needed when a narrow allowed path works. |
--javascript-delay <msec> |
Testing whether JavaScript-driven font setup or page work is still pending. | Does not resolve blocked URLs or missing fonts. |
--debug-javascript |
Showing JavaScript diagnostics. | Does not report every network or font failure. |
--load-error-handling <handler> |
Choosing how page load errors are handled. | Does not make an unavailable resource load. |
--minimum-font-size <int> |
Increasing the minimum size when text exists but is too small. | Does not restore missing glyphs or substitute the intended face. |
Option availability and behavior can vary with the packaged binary. Check wkhtmltoimage --version and its matching documentation before relying on a flag.
Fast decision tree
- No text or a fallback face? Verify stylesheet and font-file retrieval first.
- Local HTML or font file? Resolve the path and check access for the renderer process; use a narrow
--allowpath where appropriate. - Works locally, fails in deployment? Check fonts and font discovery in the server/container runtime and under the process user.
- Only some characters fail? Check the font’s glyph coverage and any fallback font available in that runtime.
- Font is added after page load? Test a delay and JavaScript diagnostics to evaluate timing.
- Minimal reproduction still fails after those checks? Compare exact builds and inspect their warnings before concluding that the renderer has a limitation.
Or skip the browser setup
For a hosted page, ScreenshotNeo can return a screenshot with one API request. Replace YOUR_API_KEY with your key; the example saves the response as WebP. See the [ScreenshotNeo documentation](https://screenshotneo.com/docs/) for request options.
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}`);
ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Learn more at [ScreenshotNeo](https://screenshotneo.com).
Sign up for 1,000 free screenshots a month with no card.
Troubleshooting common symptoms
| Symptom | Likely cause to check | Next step |
|---|---|---|
| The page renders, but all text uses a fallback face. | The stylesheet or font-file request failed, or the runtime lacks the font. | Verify both remote requests, then check the renderer’s runtime fonts. |
| Local HTML renders, but its local font does not. | Incorrect relative path or local-file access restriction. | Resolve the path from the HTML location and allow only the required directory if needed. |
| It works on a workstation but fails in a container. | The container image or process user cannot discover or read the font. | Inspect fonts and font configuration inside the actual runtime. |
| Only certain scripts or symbols are wrong. | The selected face may not include those glyphs. | Check coverage and available fallback fonts for the affected characters. |
| A longer delay changes the output. | Font setup or related page work may be asynchronous. | Use the smallest delay that reliably addresses the demonstrated timing issue. |
| A longer delay has no effect. | The cause is likely unrelated to timing. | Return to URL access, file permissions, runtime fonts, and glyph coverage. |
| Text is present but unusually small. | A size rule or renderer minimum-size setting may be involved. | Inspect CSS and consider --minimum-font-size only for a size problem. |
Performance, reliability, and cost considerations
Font checks add little work compared with repeatedly capturing a full production page: use a minimal reproduction to keep each diagnostic run focused. A delay increases the time spent on each capture, so use it to test an asynchronous-load hypothesis rather than as a blanket setting. Local path allowances should be limited to needed resources to keep the rendering environment predictable.
For reliable output across deployments, record the renderer version and runtime image, keep required fonts available to the same process that captures pages, and verify the font URLs from that environment. No rendering experiment or cross-build guarantee is implied here; the correct fix depends on the binary, operating system, page, and invocation. With ScreenshotNeo, only clean shots are billed; its response identifies the page verdict and billing status in headers. Plan limits and current prices are listed on the product site.
FAQ
Does installing a font on my laptop install it for wkhtmltoimage on a server?
No. The renderer uses the fonts visible in its own runtime and under its process context. Check the server or container where the command runs.
Can a browser show the right font while wkhtmltoimage does not?
Yes. The two may have different resource access, font installations, or rendering behavior. Use the browser result as a comparison clue, then inspect the renderer’s requests and runtime.
Will a larger JavaScript delay always fix missing fonts?
No. A delay is useful only if relevant page work is still pending. It cannot supply a blocked font file, grant local access, or add missing glyphs.
Should I enable local file access globally?
Prefer allowing the specific directory required by the page when that is sufficient. Follow the documentation for the exact binary you run.


