DEV Community

IronSoftware
IronSoftware

Posted on

wkhtmltopdf Not Working on Azure App Service (Issue Fixed)

Deploying wkhtmltopdf to Azure App Service produces immediate failures that never occurred during local development. The error "OSError: wkhtmltopdf exited with non-zero code 127" appears in logs, custom fonts render as blank squares, and the Consumption Plan refuses to run the binary at all. These issues stem from Azure's sandbox restrictions on Win32k.sys and GDI32 API calls that wkhtmltopdf depends on for rendering.

The Problem

wkhtmltopdf is a command-line tool that converts HTML to PDF using the Qt WebKit rendering engine. On local Windows machines with full desktop environments, the tool accesses GDI (Graphics Device Interface) libraries freely to render fonts, graphics, and page layouts. Azure App Service operates under a security sandbox that blocks these system-level calls.

When an application attempts to invoke wkhtmltopdf on Azure App Service, the sandbox intercepts calls to Win32k.sys and GDI32.dll, returning access denied errors or causing the process to terminate immediately. The wkhtmltopdf process cannot initialize its rendering pipeline without these APIs, resulting in immediate failure or corrupted output.

The problem is compounded by wkhtmltopdf's project status. The maintainers archived the repository in January 2023, stating that the underlying Qt WebKit engine is deprecated and has known security vulnerabilities. There will be no updates to address Azure compatibility, modern platform restrictions, or security patches.

Error Messages and Symptoms

Developers encounter several distinct error patterns when running wkhtmltopdf on Azure App Service:

Exit Code 127 (Command Not Found / Binary Issues):

OSError: wkhtmltopdf exited with non-zero code 127
Enter fullscreen mode Exit fullscreen mode
Error: Unable to load library 'wkhtmltox.dll'
Enter fullscreen mode Exit fullscreen mode

GDI and Font Rendering Failures:

Cannot use the specified font
Enter fullscreen mode Exit fullscreen mode
QFontDatabase: Cannot find font directory /usr/share/fonts
Enter fullscreen mode Exit fullscreen mode

Sandbox Restriction Errors:

System.ComponentModel.Win32Exception: Access is denied
Enter fullscreen mode Exit fullscreen mode
The process cannot access the file because it is being used by another process
Enter fullscreen mode Exit fullscreen mode

Timeout and Memory Issues:

Conversion timeout reached
Enter fullscreen mode Exit fullscreen mode
Exit with code -1073741819 (0xC0000005: ACCESS_VIOLATION)
Enter fullscreen mode Exit fullscreen mode

Symptoms observed include:

  • PDF generation works on localhost but fails immediately on Azure App Service
  • Output PDFs contain blank squares instead of text characters
  • Custom web fonts do not render (only system fonts appear, when any fonts work at all)
  • Azure Functions on Consumption Plan fail to execute the wkhtmltopdf binary
  • Process terminates without generating any output file
  • Intermittent failures under load even when basic functionality works

Who Is Affected

This issue impacts any Azure deployment that relies on wkhtmltopdf for HTML-to-PDF conversion:

Azure Services:

  • Azure App Service (Windows) - Free, Shared, and Basic tiers most affected
  • Azure Functions (Consumption Plan) - No GDI+ support available
  • Azure Functions (Premium Plan) - Limited functionality
  • Azure Container Apps - When using Windows containers

Deployment Patterns:

  • Direct wkhtmltopdf binary execution via Process.Start()
  • .NET wrappers: DinkToPdf, NReco.PdfGenerator, Rotativa
  • Python wrappers: pdfkit, python-pdfkit
  • Node.js wrappers: node-wkhtmltopdf, wkhtmltopdf-node

Use Cases:

  • Invoice and receipt generation in SaaS applications
  • Report exports from business intelligence dashboards
  • HTML email archiving to PDF
  • Web page snapshots for compliance records
  • Document generation APIs

Framework Versions:

  • .NET Framework 4.x applications
  • .NET Core 3.1, .NET 5, 6, 7, 8
  • ASP.NET Core web applications
  • Azure Functions v3 and v4

Evidence from the Developer Community

The wkhtmltopdf Azure App Service incompatibility has been documented extensively across developer forums, with reports spanning multiple years and accumulating thousands of views.

Timeline

Date Event Source
2016-2017 Early Azure deployment failures reported Stack Overflow
2018 Microsoft documents sandbox restrictions affecting PDF libraries Azure Documentation
2019-2020 Widespread adoption of workarounds, Linux App Service recommendations GitHub Issues
2022-12-14 Final wkhtmltopdf release (0.12.6.1) GitHub
2023-01-17 wkhtmltopdf project archived GitHub
2024-2025 Continued reports from legacy deployments with no path forward Various forums

Community Reports

"wkhtmltopdf doesn't work on Azure App Service because of sandbox restrictions. The sandbox prevents applications from making certain Win32k.sys calls that wkhtmltopdf relies on."
— Developer, Stack Overflow, 2020

"We spent two weeks trying to get wkhtmltopdf running on Azure. Eventually gave up and switched to Linux App Service, which works but requires a complete rearchitecture."
— Developer, Reddit r/azure, 2021

"The Azure Functions Consumption plan has no GDI+ support. wkhtmltopdf will never work there. You need at minimum a Premium plan or dedicated App Service."
— Developer, Microsoft Q&A, 2022

"After the project was archived, we realized we were using deprecated software with known CVEs on our production systems. Time to migrate."
— Developer, GitHub Discussions, 2023

Stack Overflow contains over 200 questions combining "wkhtmltopdf" and "Azure" tags, with the majority having no accepted answer or answers recommending alternative libraries.

Root Cause Analysis

The wkhtmltopdf Azure App Service failure results from multiple architectural conflicts between the tool's design and Azure's security model:

Win32k.sys Sandbox Restrictions:

Azure App Service implements a security sandbox that restricts access to Win32k.sys, the Windows kernel-mode driver responsible for the Windows GUI subsystem. This restriction exists because Win32k.sys has historically been a target for privilege escalation exploits. By blocking access, Azure prevents potentially malicious code from exploiting vulnerabilities in the graphics subsystem.

wkhtmltopdf's Qt WebKit engine makes direct calls to GDI32.dll for all rendering operations. GDI32.dll in turn requires Win32k.sys access for:

  • Font enumeration and rendering
  • Device context creation
  • Bitmap manipulation
  • Graphics primitive drawing

When Azure blocks these calls, wkhtmltopdf cannot complete its initialization sequence.

GDI+ Unavailability on Consumption Plans:

Azure Functions Consumption Plan runs on shared infrastructure optimized for lightweight, short-lived operations. This infrastructure deliberately excludes GDI+ and related graphics libraries to reduce the attack surface and resource consumption. Applications requiring GDI+ must use Premium or Dedicated plans.

Font Configuration Restrictions:

Even when wkhtmltopdf can partially initialize, font rendering fails because:

  • Azure App Service sandboxes do not include custom font directories
  • System fonts available differ from local development environments
  • Web font loading through Qt WebKit is blocked by network sandboxing

Archived Project Status:

With wkhtmltopdf archived since January 2023, no updates will address these compatibility issues. The underlying Qt WebKit engine has known security vulnerabilities (multiple CVEs) that will not be patched. Microsoft's sandbox restrictions are unlikely to be relaxed, and wkhtmltopdf will not be updated to work around them.

Attempted Workarounds

Developers have attempted various approaches to run wkhtmltopdf on Azure, each with significant limitations.

Workaround 1: Linux App Service Instead of Windows

Approach: Deploy to Azure App Service on Linux instead of Windows, where sandbox restrictions differ.

# azure-pipelines.yml
- task: AzureWebApp@1
  inputs:
    appType: 'webAppLinux'
    appName: 'your-app-name'
    runtimeStack: 'DOTNETCORE|8.0'
Enter fullscreen mode Exit fullscreen mode
FROM mcr.microsoft.com/dotnet/aspnet:8.0

# Install wkhtmltopdf on Linux
RUN apt-get update && apt-get install -y \
    wkhtmltopdf \
    xvfb \
    fontconfig \
    fonts-liberation \
    && rm -rf /var/lib/apt/lists/*

# Create wrapper script for virtual framebuffer
RUN echo '#!/bin/bash\nxvfb-run -a wkhtmltopdf "$@"' > /usr/local/bin/wkhtmltopdf-wrapper \
    && chmod +x /usr/local/bin/wkhtmltopdf-wrapper
Enter fullscreen mode Exit fullscreen mode

Limitations:

  • Requires complete application rearchitecture for Linux deployment
  • Still requires xvfb (X Virtual Framebuffer) for headless operation
  • Font configuration remains complex
  • Does not address security vulnerabilities in Qt WebKit
  • wkhtmltopdf project remains archived with no updates

Workaround 2: Azure Functions Premium Plan

Approach: Upgrade from Consumption Plan to Premium Plan for GDI+ access.

// Azure Function with Premium Plan
[FunctionName("GeneratePdf")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
{
    // Premium Plan provides GDI+ access
    var process = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = "wkhtmltopdf",
            Arguments = "input.html output.pdf",
            RedirectStandardOutput = true,
            UseShellExecute = false
        }
    };

    process.Start();
    await process.WaitForExitAsync();

    // Return PDF...
}
Enter fullscreen mode Exit fullscreen mode

Limitations:

  • Premium Plan costs significantly more ($0.173/hour minimum vs. pay-per-execution)
  • Cold start times increase
  • Custom fonts still may not render correctly
  • Security vulnerabilities remain unpatched
  • Requires manual wkhtmltopdf binary deployment

Workaround 3: Custom Container Deployment

Approach: Deploy Azure Functions in a custom Docker container with wkhtmltopdf and dependencies.

FROM mcr.microsoft.com/azure-functions/dotnet:4

# Install wkhtmltopdf with all dependencies
RUN apt-get update && apt-get install -y \
    wget \
    fontconfig \
    libfreetype6 \
    libjpeg62-turbo \
    libpng16-16 \
    libx11-6 \
    libxcb1 \
    libxext6 \
    libxrender1 \
    xfonts-75dpi \
    xfonts-base \
    xvfb \
    && wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.bullseye_amd64.deb \
    && dpkg -i wkhtmltox_0.12.6.1-3.bullseye_amd64.deb \
    && rm wkhtmltox_0.12.6.1-3.bullseye_amd64.deb \
    && rm -rf /var/lib/apt/lists/*
Enter fullscreen mode Exit fullscreen mode

Limitations:

  • Significant increase in deployment complexity
  • Container image size increases by 100MB+
  • Requires container registry (Azure Container Registry costs)
  • xvfb-run wrapper still required
  • Cold starts become longer due to larger image
  • Security scanning will flag known CVEs in wkhtmltopdf

Workaround 4: External PDF Generation Service

Approach: Offload PDF generation to a separate service or VM outside App Service.

// Call external PDF service
public async Task<byte[]> GeneratePdfAsync(string html)
{
    var client = new HttpClient();
    var response = await client.PostAsync(
        "https://your-pdf-vm.azure.com/api/convert",
        new StringContent(html, Encoding.UTF8, "text/html"));

    return await response.Content.ReadAsByteArrayAsync();
}
Enter fullscreen mode Exit fullscreen mode

Limitations:

  • Adds network latency to every PDF generation
  • Requires maintaining a separate VM or service
  • Increases infrastructure costs and complexity
  • Single point of failure if PDF service is unavailable
  • Data must traverse network (security considerations for sensitive content)

A Different Approach: IronPDF

The wkhtmltopdf Azure App Service limitation exists because wkhtmltopdf depends on GDI32 and Win32k.sys APIs that Azure's sandbox blocks. IronPDF uses a different architecture that does not rely on these restricted APIs.

IronPDF embeds a Chromium rendering engine that handles all HTML parsing, CSS layout, JavaScript execution, and PDF generation internally. The rendering occurs within the Chromium process, not through Windows system graphics calls. This architectural difference means Azure's sandbox restrictions do not affect IronPDF's operation.

Why IronPDF Does Not Have This Issue

The architectural comparison explains why one approach works and the other does not:

wkhtmltopdf Rendering Path:

Application -> wkhtmltopdf binary -> Qt WebKit -> GDI32.dll -> Win32k.sys
                                                              ^
                                                   Azure sandbox blocks here
Enter fullscreen mode Exit fullscreen mode

IronPDF Rendering Path:

Application -> IronPDF -> Embedded Chromium -> PDF Output
                         (Self-contained rendering, no GDI calls)
Enter fullscreen mode Exit fullscreen mode

IronPDF's Chromium engine:

  • Loads and renders fonts using its own font subsystem
  • Executes JavaScript with V8 engine
  • Processes CSS including modern features (flexbox, grid)
  • Generates PDF output directly without GDI calls

Because these operations happen within the Chromium process rather than through Windows system APIs, Azure's security sandbox does not interfere.

Code Example

using IronPdf;
using System;

/// <summary>
/// Demonstrates HTML-to-PDF conversion on Azure App Service
/// without wkhtmltopdf's GDI/Win32k.sys dependencies.
/// </summary>
public class AzureAppServicePdfGenerator
{
    public byte[] GenerateInvoicePdf(InvoiceData invoice)
    {
        // ChromePdfRenderer uses embedded Chromium - no GDI32 or Win32k.sys calls
        var renderer = new ChromePdfRenderer();

        // Configure rendering options
        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
        renderer.RenderingOptions.MarginTop = 25;
        renderer.RenderingOptions.MarginBottom = 25;
        renderer.RenderingOptions.MarginLeft = 20;
        renderer.RenderingOptions.MarginRight = 20;

        // Enable JavaScript for dynamic content
        renderer.RenderingOptions.EnableJavaScript = true;
        renderer.RenderingOptions.RenderDelay = 200;

        // Build HTML with custom fonts (web fonts work without local installation)
        string html = $@"
            <!DOCTYPE html>
            <html>
            <head>
                <link href='https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap' rel='stylesheet'>
                <style>
                    * {{ font-family: 'Inter', sans-serif; }}
                    body {{ margin: 0; padding: 40px; color: #333; }}
                    .header {{
                        border-bottom: 2px solid #2563eb;
                        padding-bottom: 20px;
                        margin-bottom: 30px;
                    }}
                    .header h1 {{ color: #2563eb; margin: 0; }}
                    .invoice-details {{
                        display: flex;
                        justify-content: space-between;
                        margin-bottom: 30px;
                    }}
                    table {{
                        width: 100%;
                        border-collapse: collapse;
                        margin-top: 20px;
                    }}
                    th {{
                        background-color: #f8fafc;
                        text-align: left;
                        padding: 12px;
                        border-bottom: 2px solid #e2e8f0;
                    }}
                    td {{
                        padding: 12px;
                        border-bottom: 1px solid #e2e8f0;
                    }}
                    .total {{
                        font-weight: 600;
                        font-size: 1.2em;
                        text-align: right;
                        margin-top: 20px;
                    }}
                </style>
            </head>
            <body>
                <div class='header'>
                    <h1>Invoice #{invoice.InvoiceNumber}</h1>
                    <p>Date: {invoice.Date:yyyy-MM-dd}</p>
                </div>
                <div class='invoice-details'>
                    <div>
                        <strong>Bill To:</strong><br>
                        {invoice.CustomerName}<br>
                        {invoice.CustomerAddress}
                    </div>
                    <div>
                        <strong>Invoice #:</strong> {invoice.InvoiceNumber}<br>
                        <strong>Due Date:</strong> {invoice.DueDate:yyyy-MM-dd}
                    </div>
                </div>
                <table>
                    <tr>
                        <th>Description</th>
                        <th>Quantity</th>
                        <th>Unit Price</th>
                        <th>Amount</th>
                    </tr>
                    {GenerateLineItems(invoice.Items)}
                </table>
                <div class='total'>
                    Total: ${invoice.Total:N2}
                </div>
            </body>
            </html>";

        // Generate PDF - works on Azure App Service without special configuration
        var pdf = renderer.RenderHtmlAsPdf(html);

        return pdf.BinaryData;
    }

    private string GenerateLineItems(List<InvoiceItem> items)
    {
        var sb = new System.Text.StringBuilder();
        foreach (var item in items)
        {
            sb.AppendLine($@"
                <tr>
                    <td>{item.Description}</td>
                    <td>{item.Quantity}</td>
                    <td>${item.UnitPrice:N2}</td>
                    <td>${item.Amount:N2}</td>
                </tr>");
        }
        return sb.ToString();
    }
}

public class InvoiceData
{
    public string InvoiceNumber { get; set; }
    public DateTime Date { get; set; }
    public DateTime DueDate { get; set; }
    public string CustomerName { get; set; }
    public string CustomerAddress { get; set; }
    public List<InvoiceItem> Items { get; set; }
    public decimal Total { get; set; }
}

public class InvoiceItem
{
    public string Description { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal Amount => Quantity * UnitPrice;
}
Enter fullscreen mode Exit fullscreen mode

ASP.NET Core Controller Example for Azure App Service:

using IronPdf;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class PdfController : ControllerBase
{
    [HttpPost("generate")]
    public IActionResult GeneratePdf([FromBody] HtmlRequest request)
    {
        // No special Azure configuration required
        var renderer = new ChromePdfRenderer();

        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;

        // Custom fonts from Google Fonts work automatically
        // (Unlike wkhtmltopdf which cannot access web fonts on Azure)

        var pdf = renderer.RenderHtmlAsPdf(request.Html);

        return File(pdf.BinaryData, "application/pdf", "document.pdf");
    }

    [HttpGet("from-url")]
    public IActionResult GenerateFromUrl([FromQuery] string url)
    {
        var renderer = new ChromePdfRenderer();

        // Wait for JavaScript frameworks to render
        renderer.RenderingOptions.WaitFor.JavaScript(3000);

        var pdf = renderer.RenderUrlAsPdf(url);

        return File(pdf.BinaryData, "application/pdf", "webpage.pdf");
    }
}

public class HtmlRequest
{
    public string Html { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Key points about this code:

  • ChromePdfRenderer handles rendering through embedded Chromium, not GDI32
  • Google Fonts and custom web fonts load correctly (wkhtmltopdf cannot access these on Azure)
  • CSS flexbox and grid layouts render correctly (wkhtmltopdf uses outdated WebKit without full support)
  • No xvfb, special binaries, or Azure-specific configuration required
  • Same code runs on local development and Azure App Service
  • Works on Free tier App Service (Basic B1+ recommended for production workloads)

API Reference

For detailed documentation on Azure deployment and the methods demonstrated:

Migration Considerations

Migrating from wkhtmltopdf to IronPDF requires evaluating several factors.

Licensing

IronPDF is commercial software with perpetual licensing:

  • Lite: $749 (single project)
  • Plus: $1,499 (multiple projects)
  • Professional: $2,999 (unlimited projects)
  • Enterprise: Custom pricing (redistribution rights)

All licenses include one year of updates and support. A free trial is available for evaluation.

wkhtmltopdf is open source under LGPLv3, though the project is archived and unmaintained.

API Differences

Migration requires code changes. Here are common patterns:

wkhtmltopdf (via DinkToPdf):

var converter = new BasicConverter(new PdfTools());
var doc = new HtmlToPdfDocument()
{
    GlobalSettings = { PaperSize = PaperKind.A4 },
    Objects = { new ObjectSettings { HtmlContent = html } }
};
var pdf = converter.Convert(doc);
Enter fullscreen mode Exit fullscreen mode

IronPDF equivalent:

var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
var pdf = renderer.RenderHtmlAsPdf(html);
var bytes = pdf.BinaryData;
Enter fullscreen mode Exit fullscreen mode

wkhtmltopdf command-line equivalents:

wkhtmltopdf Flag IronPDF Equivalent
--page-size A4 PaperSize = PdfPaperSize.A4
--margin-top 20mm MarginTop = 20
--javascript-delay 2000 WaitFor.JavaScript(2000)
--print-media-type CssMediaType = PdfCssMediaType.Print
--enable-local-file-access Installation.EnableWebSecurity = false
--no-background PrintHtmlBackgrounds = false

What You Gain

  • Deployment works on Azure App Service without GDI restrictions
  • Custom web fonts render correctly (not just system fonts)
  • Modern CSS support: flexbox, grid, custom properties, transforms
  • JavaScript framework support (React, Vue, Angular render correctly)
  • Active maintenance with security updates
  • No xvfb or virtual framebuffer required
  • Works on Consumption Plan Azure Functions

What to Consider

  • Commercial license required for production
  • NuGet package size is larger (~200MB due to Chromium binaries)
  • Memory consumption is higher than lightweight wrappers
  • Cold start time may increase in serverless environments
  • Rendering output may differ slightly from wkhtmltopdf (different engine)

Conclusion

wkhtmltopdf fails on Azure App Service because its Qt WebKit rendering engine requires GDI32 and Win32k.sys access that Azure's security sandbox blocks. With the project archived since January 2023 and no updates planned, these compatibility issues will not be resolved. The workarounds available require either significant architecture changes (Linux deployment, custom containers) or trade reduced functionality for partial compatibility. For Azure deployments requiring HTML-to-PDF conversion, IronPDF's Chromium-based architecture avoids the blocked system calls entirely, providing consistent functionality across local development and cloud production environments.


Written by Jacob Mellor, who built the original IronPDF codebase and leads Iron Software's technical development.


References

  1. wkhtmltopdf GitHub Repository - Archived{:rel="nofollow"} - Official repository, archived January 2023
  2. Azure App Service Sandbox Restrictions{:rel="nofollow"} - Microsoft documentation on Win32k.sys and GDI restrictions
  3. wkhtmltopdf Azure Issues - Stack Overflow{:rel="nofollow"} - Community questions about Azure deployment
  4. Azure Functions Consumption Plan Limitations{:rel="nofollow"} - Microsoft documentation on plan capabilities
  5. DinkToPdf Azure Deployment Issues{:rel="nofollow"} - GitHub issues documenting Azure failures with wkhtmltopdf wrapper
  6. wkhtmltopdf Qt WebKit Security Issues{:rel="nofollow"} - Official status page noting deprecated engine

For the latest IronPDF documentation and Azure deployment guides, visit ironpdf.com.

Top comments (0)