How to Use Custom Fonts with the NReco wkhtmltopdf Wrapper on Azure Web Apps
Custom fonts usually fail with NReco wkhtmltopdf on Azure App Service. Learn why, which hosting options work, and how to verify deployed PDFs.
Direct answer: custom fonts generally do not render with NReco.PdfGenerator/wkhtmltopdf in the standard Windows Azure App Service sandbox. The sandbox restricts GDI APIs used by wkhtmltopdf and falls back to installed system fonts. This limitation also applies to VM-based Basic, Standard and Premium App Service plans. CSS @font-face rules and font files stored in your application directory do not remove it.
Use a supported system font if you remain on built-in Windows App Service. For a custom typeface, move the renderer to a Windows custom container, a Linux container using NReco.PdfGenerator.LT with a separately deployed wkhtmltopdf binary, or a normal Windows/Linux VM where you control OS font installation.
Why Azure substitutes Arial
NReco.PdfGenerator wraps wkhtmltopdf, a QtWebKit-based renderer. Its output depends on both the .NET wrapper and the operating system where wkhtmltopdf runs. Azure App Service’s sandbox blocks the font-rendering path required by wkhtmltopdf and PhantomJS PDF generators; Microsoft documents the result as custom fonts not being rendered and the system-installed font being used instead. NReco also documents that the restriction remains on VM-based Azure Apps plans at Basic or higher.
A typical symptom is a PDF that looks correct on a developer workstation but uses Arial or Times New Roman after deployment. An issue report described the same result with @font-face rules pointing to TTF files in a Fonts directory; that report is useful for recognizing the symptom, but the platform documentation is the authoritative explanation.
Choose a hosting route
| Route | Custom fonts | What you must operate |
|---|---|---|
| Built-in Windows App Service | No for wkhtmltopdf | Use standard system fonts; Basic or higher may satisfy generator plan requirements but does not remove the font restriction. |
| Windows custom container | Yes | Install licensed fonts in the image and deploy that image to App Service. |
| Linux custom container | Yes | Use NReco.PdfGenerator.LT, include a compatible wkhtmltopdf executable and Linux dependencies, then install fonts. |
| Normal Windows/Linux VM | Yes | Install and patch the OS, renderer and fonts yourself. |
| Shared Azure Apps plan | Not supported by NReco | Choose another hosting model. |
Step 1: identify the exact environment
- Record whether the process runs on built-in Windows App Service, a Windows custom container, a Linux container, Azure Functions, or a self-managed VM.
- Record the NReco package and wkhtmltopdf versions. The wrapper cannot override restrictions imposed by the host.
- Confirm whether the font license permits redistribution inside an image or VM.
Step 2: keep built-in Windows App Service (system fonts only)
If changing hosting is not possible, select a Windows font that is available to the renderer, such as Arial or Times New Roman, and document that custom font loading is unsupported in this environment. Moving from Free or Shared to Basic, Standard or Premium can address minimum plan requirements, but it does not enable custom-font rendering.
var pdf = new NReco.PdfGenerator.HtmlToPdfConverter();
pdf.Quiet = false;
pdf.GeneratePdf("<html><body style='font-family: Arial'>Invoice</body></html>", "invoice.pdf");
Step 3: use a Windows custom container
Microsoft’s custom-container guidance explains that an app requiring an installed font cannot run in the App Service sandbox, but can run in a Windows container where the font is installed. Build the image with the font, the application and its renderer dependencies; deploy the image to App Service.
- Copy the licensed font into the image during the build.
- Install it using the Windows container’s supported font-installation method.
- Run the same NReco code inside the container.
- Generate a PDF after deployment and inspect actual glyphs and fallback.
# Illustrative Dockerfile steps; use the installation commands required by your base image
COPY AcmeSans-Regular.ttf C:\\Windows\\Fonts\\AcmeSans-Regular.ttf
COPY AcmeSans-Bold.ttf C:\\Windows\\Fonts\\AcmeSans-Bold.ttf
# Install/register the fonts using your Windows container image's documented method
COPY . /app
Do not treat copying a file into your project directory as installation. The renderer must be able to discover the font through the container’s Windows font system.
Step 4: use a Linux container with NReco.PdfGenerator.LT
NReco directs Linux container deployments to NReco.PdfGenerator.LT. The LT package does not include wkhtmltopdf binaries, so the image must contain a compatible executable separately. NReco’s current examples list dependencies such as fontconfig and runtime libraries for specific Debian and Ubuntu base images. Match package commands to the exact base image and current vendor instructions.
FROM ubuntu:24.04
# Install the packages required by your chosen wkhtmltopdf build and fontconfig.
# Copy the licensed fonts into a font directory recognized by the image.
COPY fonts/AcmeSans-Regular.ttf /usr/local/share/fonts/acme/AcmeSans-Regular.ttf
COPY fonts/AcmeSans-Bold.ttf /usr/local/share/fonts/acme/AcmeSans-Bold.ttf
RUN fc-cache -f -v
# Copy your application and the separately obtained compatible wkhtmltopdf binary.
COPY publish/ /app/
The exact binary and dependency set is image-specific. Avoid copying an old or unsuitable wkhtmltopdf package from a generic repository without checking NReco’s instructions.
Step 5: make CSS and resource paths resolvable
NReco states that GeneratePdf cannot handle relative resource locations reliably. Use absolute URLs or filesystem paths for stylesheets, images and fonts, or use GeneratePdfFromFile where appropriate.
@font-face {
font-family: 'Acme Sans';
src: url('https://your-domain.example/assets/fonts/AcmeSans-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}
body { font-family: 'Acme Sans', Arial, sans-serif; }
Ensure the renderer process can reach the URL without authentication failures, TLS problems or network policies. For local container files, use an absolute path and verify case sensitivity on Linux.
Step 6: enable diagnostics and verify the deployed PDF
Set Quiet = false and capture NReco’s LogReceived output while investigating missing resources. Look for font URL errors, denied requests and renderer warnings.
var converter = new NReco.PdfGenerator.HtmlToPdfConverter {
Quiet = false
};
converter.LogReceived += (sender, args) => Console.WriteLine(args.Data);
converter.GeneratePdfFromFile("/app/templates/invoice.html", null, "/app/output/invoice.pdf");
Verification must happen in the deployed environment. Check:
- The font file exists inside the image or VM.
- The font URL or path resolves from the wkhtmltopdf process.
- Font configuration has been refreshed where required.
- The PDF uses the intended family and weight, not a fallback.
- Glyphs outside ASCII, ligatures and shaping render correctly.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Arial appears only after Azure deployment | Windows App Service sandbox GDI restriction | Use a system font or move to a custom container or VM. |
| Font file is in the repository but ignored | A project file is not an installed OS font | Install/register it in the container or VM font system. |
| Local CSS works, deployed PDF does not | Relative path, blocked URL, wrong case or missing file | Use absolute paths/URLs and inspect renderer logs. |
| Linux container starts but PDF generation fails | Missing wkhtmltopdf binary or native dependency | Deploy a compatible binary and all dependencies required by the selected base image. |
| Font loads but layout is wrong | QtWebKit lacks modern CSS features such as flexbox and grid | Simplify layout for wkhtmltopdf or use a renderer that supports the required CSS. |
| Only some characters are wrong | Missing glyph coverage or incorrect font weight | Install the required font variants and test representative Unicode text. |
Performance, reliability and cost considerations
- Image size: baking fonts and native binaries into a container increases image size and deployment time.
- Cold starts: container or VM startup and font-cache initialization add work before the first PDF.
- Repeatability: pin the base image, wkhtmltopdf binary and font files so environments do not drift.
- Security: keep font files and templates controlled; do not fetch arbitrary remote CSS or fonts unless required.
- Licensing: confirm redistribution rights before putting a commercial font in an image or artifact.
- Capacity: wkhtmltopdf is an external renderer process; size and monitor the host for concurrent jobs and memory use.
- Validation: retain representative PDFs in deployment checks and compare embedded or rendered glyphs after upgrades.
Or skip the browser setup
If your requirement is a clean image or PDF capture of a rendered webpage rather than server-side NReco PDF generation, ScreenshotNeo provides a single HTTP request. Its capture flow accepts cookie and consent banners, removes more than 60 known consent platforms plus newsletter popups and chat widgets, and lets each cleanup step be disabled. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed; response headers identify the page verdict and billing result. It also offers an MCP server for AI agents through take_screenshot, get_page_info and capture_pdf.
See the ScreenshotNeo API documentation for all options. 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)
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}`);
const data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);
Every feature is included on every plan. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
FAQ
Will upgrading to Premium App Service enable my font?
No. NReco and Microsoft’s sandbox documentation describe the custom-font restriction across VM-based Basic, Standard and Premium Azure Apps plans.
Can @font-face bypass the sandbox?
No. The restriction is in the renderer’s hosting environment, so a URL or local CSS declaration cannot provide the blocked GDI capability.
Is a custom container the same as a self-managed VM?
No. A container gives you an image-level font environment; a VM gives you full OS control. Both avoid the built-in sandbox limitation but have different operational responsibilities.
Does NReco include wkhtmltopdf for Linux?
NReco.PdfGenerator.LT requires you to deploy a compatible wkhtmltopdf executable separately.
Why can a modern CSS layout still fail after the font works?
wkhtmltopdf uses older QtWebKit and lacks several modern CSS features, including flexbox and grid. Font availability and layout compatibility are separate checks.
Primary sources
- NReco.PdfGenerator documentation — Azure limitations, package selection, paths, logging and renderer constraints.
- Azure Web App sandbox documentation — GDI and custom-font restriction.
- Microsoft Learn: custom containers for Azure App Service — installing a font in a Windows container.
- wkhtmltopdf issue report — example of the Arial fallback symptom.


