푸터 콘텐츠로 바로가기
MIGRATION GUIDES

How to Migrate from wkhtmltopdf to IronPDF in C#

wkhtmltopdf has been a widely-used tool for converting HTML documents to PDF using Qt WebKit. Despite its popularity among developers for its command-line capabilities and free licensing, the project now presents critical security risks that can no longer be ignored. The library was officially abandoned in 2016-2017, and a CRITICAL severity vulnerability (CVE-2022-35583) remains permanently unpatched.

This guide provides a complete migration path from wkhtmltopdf to IronPDF, with step-by-step instructions, code comparisons, and practical examples for professional .NET developers who need to eliminate this security risk from their applications.

Critical Security Warning: CVE-2022-35583

wkhtmltopdf contains a critical security vulnerability that will never be fixed:

Issue Severity Status
CVE-2022-35583 CRITICAL (9.8/10) UNPATCHED
SSRF Vulnerability Infrastructure takeover risk UNPATCHED
Last Update 2016-2017 ABANDONED
WebKit Version 2015 (Qt WebKit) OBSOLETE
CSS Grid Support None Broken
Flexbox Support Partial Broken
ES6+ JavaScript None Broken

How the SSRF Attack Works

The Server-Side Request Forgery vulnerability allows attackers to access internal services, steal credentials, scan your internal network, and exfiltrate sensitive data through crafted HTML:


<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
<img src="http://internal-database:5432/admin"/>

<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
<img src="http://internal-database:5432/admin"/>
HTML

When wkhtmltopdf renders this HTML, it fetches these URLs from your server's network context, bypassing firewalls and security controls.

Affected Wrapper Libraries

All .NET wrappers for wkhtmltopdf inherit these vulnerabilities:

Wrapper Library Status Security Risk
DinkToPdf Abandoned ⚠️ CRITICAL
Rotativa Abandoned ⚠️ CRITICAL
TuesPechkin Abandoned ⚠️ CRITICAL
WkHtmlToPdf-DotNet Abandoned ⚠️ CRITICAL
NReco.PdfGenerator Uses wkhtmltopdf ⚠️ CRITICAL

If you use any of these libraries, you are vulnerable to CVE-2022-35583.

IronPDF vs wkhtmltopdf: Feature Comparison

Understanding the architectural differences helps technical decision-makers evaluate the migration investment:

Feature wkhtmltopdf IronPDF
Licensing LGPLv3 (Free) Commercial
Rendering Engine Qt WebKit (2015) Current Chromium Engine
Security Vulnerabilities CVE-2022-35583, major unpatched issues No known CVEs
Active Maintenance Abandoned, no meaningful updates since 2017 Actively maintained with regular releases
Support for Modern Web Standards Limited (Broken flexbox, no CSS Grid) Supported
Integration and Support Limited to community forums Extensive documentation and dedicated support
CSS Grid ❌ Not supported ✅ Supported
Flexbox ⚠️ Broken ✅ Supported
ES6+ JavaScript ❌ Not supported ✅ Supported
Async/Await ❌ Not supported ✅ Supported
PDF Manipulation ❌ Not supported ✅ Supported
Digital Signatures ❌ Not supported ✅ Supported
PDF/A Compliance ❌ Not supported ✅ Supported

Quick Start: wkhtmltopdf to IronPDF Migration

The migration can begin immediately with these foundational steps.

Step 1: Remove wkhtmltopdf Packages and Binaries

Remove all wkhtmltopdf wrapper packages:

# Remove wkhtmltopdf wrapper (whichever you're using)
dotnet remove package WkHtmlToPdf-DotNet
dotnet remove package DinkToPdf
dotnet remove package TuesPechkin
dotnet remove package Rotativa
dotnet remove package Rotativa.AspNetCore
dotnet remove package NReco.PdfGenerator

# Remove wkhtmltopdf binary from your deployment
# Delete wkhtmltopdf.exe, wkhtmltox.dll, etc.
# Remove wkhtmltopdf wrapper (whichever you're using)
dotnet remove package WkHtmlToPdf-DotNet
dotnet remove package DinkToPdf
dotnet remove package TuesPechkin
dotnet remove package Rotativa
dotnet remove package Rotativa.AspNetCore
dotnet remove package NReco.PdfGenerator

# Remove wkhtmltopdf binary from your deployment
# Delete wkhtmltopdf.exe, wkhtmltox.dll, etc.
SHELL

Step 2: Install IronPDF

# Add IronPDF (secure, modern alternative)
dotnet add package IronPdf
# Add IronPDF (secure, modern alternative)
dotnet add package IronPdf
SHELL

Step 3: Update Namespaces

Replace wkhtmltopdf namespaces with the IronPdf namespace:

// Before (wkhtmltopdf)
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;

// After (IronPDF)
using IronPdf;
// Before (wkhtmltopdf)
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;

// After (IronPDF)
using IronPdf;
$vbLabelText   $csharpLabel

Step 4: Initialize License

Add license initialization at application startup:

IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
$vbLabelText   $csharpLabel

Code Migration Examples

Converting HTML to PDF

The most fundamental operation reveals the complexity difference between these .NET PDF approaches.

wkhtmltopdf Approach:

// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;

class Program
{
    static void Main()
    {
        var converter = new SynchronizedConverter(new PdfTools());
        var doc = new HtmlToPdfDocument()
        {
            GlobalSettings = {
                ColorMode = ColorMode.Color,
                Orientation = Orientation.Portrait,
                PaperSize = PaperKind.A4
            },
            Objects = {
                new ObjectSettings()
                {
                    HtmlContent = "<h1>Hello World</h1><p>This is a PDF from HTML.</p>"
                }
            }
        };
        byte[] pdf = converter.Convert(doc);
        File.WriteAllBytes("output.pdf", pdf);
    }
}
// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;

class Program
{
    static void Main()
    {
        var converter = new SynchronizedConverter(new PdfTools());
        var doc = new HtmlToPdfDocument()
        {
            GlobalSettings = {
                ColorMode = ColorMode.Color,
                Orientation = Orientation.Portrait,
                PaperSize = PaperKind.A4
            },
            Objects = {
                new ObjectSettings()
                {
                    HtmlContent = "<h1>Hello World</h1><p>This is a PDF from HTML.</p>"
                }
            }
        };
        byte[] pdf = converter.Convert(doc);
        File.WriteAllBytes("output.pdf", pdf);
    }
}
$vbLabelText   $csharpLabel

IronPDF Approach:

// NuGet: Install-Package IronPdf
using IronPdf;
using System;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1><p>This is a PDF from HTML.</p>");
        pdf.SaveAs("output.pdf");
    }
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1><p>This is a PDF from HTML.</p>");
        pdf.SaveAs("output.pdf");
    }
}
$vbLabelText   $csharpLabel

wkhtmltopdf requires creating a SynchronizedConverter with PdfTools, constructing an HtmlToPdfDocument with GlobalSettings and Objects, setting properties like ColorMode, Orientation, and PaperSize, calling converter.Convert() to get raw bytes, and manually writing to a file with File.WriteAllBytes().

IronPDF eliminates this ceremony entirely—create a ChromePdfRenderer, call RenderHtmlAsPdf(), and use the built-in SaveAs() method.

For advanced HTML-to-PDF scenarios, see the HTML to PDF conversion guide.

Converting URLs to PDF

URL-to-PDF conversion shows similar complexity patterns.

wkhtmltopdf Approach:

// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;

class Program
{
    static void Main()
    {
        var converter = new SynchronizedConverter(new PdfTools());
        var doc = new HtmlToPdfDocument()
        {
            GlobalSettings = {
                ColorMode = ColorMode.Color,
                Orientation = Orientation.Portrait,
                PaperSize = PaperKind.A4
            },
            Objects = {
                new ObjectSettings()
                {
                    Page = "https://www.example.com"
                }
            }
        };
        byte[] pdf = converter.Convert(doc);
        File.WriteAllBytes("webpage.pdf", pdf);
    }
}
// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;

class Program
{
    static void Main()
    {
        var converter = new SynchronizedConverter(new PdfTools());
        var doc = new HtmlToPdfDocument()
        {
            GlobalSettings = {
                ColorMode = ColorMode.Color,
                Orientation = Orientation.Portrait,
                PaperSize = PaperKind.A4
            },
            Objects = {
                new ObjectSettings()
                {
                    Page = "https://www.example.com"
                }
            }
        };
        byte[] pdf = converter.Convert(doc);
        File.WriteAllBytes("webpage.pdf", pdf);
    }
}
$vbLabelText   $csharpLabel

IronPDF Approach:

// NuGet: Install-Package IronPdf
using IronPdf;
using System;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        var pdf = renderer.RenderUrlAsPdf("https://www.example.com");
        pdf.SaveAs("webpage.pdf");
    }
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        var pdf = renderer.RenderUrlAsPdf("https://www.example.com");
        pdf.SaveAs("webpage.pdf");
    }
}
$vbLabelText   $csharpLabel

wkhtmltopdf uses the Page property in ObjectSettings to specify a URL, requiring the same document construction pattern. IronPDF provides a dedicated RenderUrlAsPdf() method that clearly expresses intent.

Explore the URL to PDF documentation for authentication and custom header options.

Custom Settings: HTML Files with Page Configuration

Configuring orientation, margins, and paper size requires different approaches.

wkhtmltopdf Approach:

// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;

class Program
{
    static void Main()
    {
        var converter = new SynchronizedConverter(new PdfTools());
        var doc = new HtmlToPdfDocument()
        {
            GlobalSettings = {
                ColorMode = ColorMode.Color,
                Orientation = Orientation.Landscape,
                PaperSize = PaperKind.A4,
                Margins = new MarginSettings() { Top = 10, Bottom = 10, Left = 10, Right = 10 }
            },
            Objects = {
                new ObjectSettings()
                {
                    Page = "input.html",
                    WebSettings = { DefaultEncoding = "utf-8" }
                }
            }
        };
        byte[] pdf = converter.Convert(doc);
        File.WriteAllBytes("custom-output.pdf", pdf);
    }
}
// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;

class Program
{
    static void Main()
    {
        var converter = new SynchronizedConverter(new PdfTools());
        var doc = new HtmlToPdfDocument()
        {
            GlobalSettings = {
                ColorMode = ColorMode.Color,
                Orientation = Orientation.Landscape,
                PaperSize = PaperKind.A4,
                Margins = new MarginSettings() { Top = 10, Bottom = 10, Left = 10, Right = 10 }
            },
            Objects = {
                new ObjectSettings()
                {
                    Page = "input.html",
                    WebSettings = { DefaultEncoding = "utf-8" }
                }
            }
        };
        byte[] pdf = converter.Convert(doc);
        File.WriteAllBytes("custom-output.pdf", pdf);
    }
}
$vbLabelText   $csharpLabel

IronPDF Approach:

// NuGet: Install-Package IronPdf
using IronPdf;
using IronPdf.Rendering;
using System;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        renderer.RenderingOptions.PaperOrientation = PdfPaperOrientation.Landscape;
        renderer.RenderingOptions.MarginTop = 10;
        renderer.RenderingOptions.MarginBottom = 10;
        renderer.RenderingOptions.MarginLeft = 10;
        renderer.RenderingOptions.MarginRight = 10;
        renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;

        var pdf = renderer.RenderHtmlFileAsPdf("input.html");
        pdf.SaveAs("custom-output.pdf");
    }
}
// NuGet: Install-Package IronPdf
using IronPdf;
using IronPdf.Rendering;
using System;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        renderer.RenderingOptions.PaperOrientation = PdfPaperOrientation.Landscape;
        renderer.RenderingOptions.MarginTop = 10;
        renderer.RenderingOptions.MarginBottom = 10;
        renderer.RenderingOptions.MarginLeft = 10;
        renderer.RenderingOptions.MarginRight = 10;
        renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;

        var pdf = renderer.RenderHtmlFileAsPdf("input.html");
        pdf.SaveAs("custom-output.pdf");
    }
}
$vbLabelText   $csharpLabel

wkhtmltopdf nests settings inside GlobalSettings and Objects, with MarginSettings as a separate object. IronPDF provides direct RenderingOptions properties with clear names like PaperOrientation, MarginTop, and PaperSize.

wkhtmltopdf API to IronPDF Mapping Reference

This mapping accelerates migration by showing direct API equivalents:

CLI to IronPDF Mapping

wkhtmltopdf CLI Option IronPDF Equivalent
wkhtmltopdf input.html output.pdf renderer.RenderHtmlFileAsPdf()
wkhtmltopdf URL output.pdf renderer.RenderUrlAsPdf()
--page-size A4 RenderingOptions.PaperSize = PdfPaperSize.A4
--page-size Letter RenderingOptions.PaperSize = PdfPaperSize.Letter
--orientation Landscape RenderingOptions.PaperOrientation = Landscape
--margin-top 10mm RenderingOptions.MarginTop = 10
--margin-bottom 10mm RenderingOptions.MarginBottom = 10
--margin-left 10mm RenderingOptions.MarginLeft = 10
--margin-right 10mm RenderingOptions.MarginRight = 10
--header-html header.html RenderingOptions.HtmlHeader
--footer-center "[page]" {page} placeholder
--footer-center "[toPage]" {total-pages} placeholder
--enable-javascript Enabled by default
--javascript-delay 500 RenderingOptions.WaitFor.RenderDelay = 500
--dpi 300 RenderingOptions.Dpi = 300
--grayscale RenderingOptions.GrayScale = true

C# Wrapper API Mapping

wkhtmltopdf Wrapper IronPDF
SynchronizedConverter ChromePdfRenderer
HtmlToPdfDocument RenderingOptions
GlobalSettings.Out pdf.SaveAs()
GlobalSettings.PaperSize RenderingOptions.PaperSize
GlobalSettings.Orientation RenderingOptions.PaperOrientation
GlobalSettings.Margins RenderingOptions.Margin*
ObjectSettings.Page RenderHtmlFileAsPdf()
ObjectSettings.HtmlContent RenderHtmlAsPdf()
converter.Convert(doc) renderer.RenderHtmlAsPdf()

Placeholder Syntax Migration

wkhtmltopdf Placeholder IronPDF Placeholder
[page] {page}
[toPage] {total-pages}
[date] {date}
[time] {time}
[title] {html-title}
[url] {url}

Common Migration Issues and Solutions

wkhtmltopdf: Uses square bracket syntax like [page] and [toPage].

Solution: Update to IronPDF's curly brace placeholders:

// Before (wkhtmltopdf)
FooterSettings = { Left = "Page [page] of [toPage]" }

// After (IronPDF)
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
    HtmlFragment = "<div style='text-align:left;'>Page {page} of {total-pages}</div>",
    MaxHeight = 25
};
// Before (wkhtmltopdf)
FooterSettings = { Left = "Page [page] of [toPage]" }

// After (IronPDF)
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
    HtmlFragment = "<div style='text-align:left;'>Page {page} of {total-pages}</div>",
    MaxHeight = 25
};
$vbLabelText   $csharpLabel

Issue 2: JavaScript Delay Configuration

wkhtmltopdf: Uses JavascriptDelay property with limited reliability.

Solution: IronPDF provides multiple options:

renderer.RenderingOptions.EnableJavaScript = true;

// Option 1: Fixed delay
renderer.RenderingOptions.WaitFor.RenderDelay(500);

// Option 2: Wait for specific element (more reliable)
renderer.RenderingOptions.WaitFor.HtmlElementById("content-loaded");

// Option 3: Wait for JavaScript condition
renderer.RenderingOptions.WaitFor.JavaScript("window.renderComplete === true");
renderer.RenderingOptions.EnableJavaScript = true;

// Option 1: Fixed delay
renderer.RenderingOptions.WaitFor.RenderDelay(500);

// Option 2: Wait for specific element (more reliable)
renderer.RenderingOptions.WaitFor.HtmlElementById("content-loaded");

// Option 3: Wait for JavaScript condition
renderer.RenderingOptions.WaitFor.JavaScript("window.renderComplete === true");
$vbLabelText   $csharpLabel

Issue 3: Modern CSS Not Rendering

Symptom: CSS Grid and Flexbox layouts render incorrectly in wkhtmltopdf.

Solution: IronPDF's Chromium engine handles modern CSS correctly:

// This CSS now works with IronPDF
var html = @"
<style>
    .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
    .flex { display: flex; justify-content: space-between; align-items: center; }
</style>
<div class='grid'>
    <div>Column 1</div>
    <div>Column 2</div>
    <div>Column 3</div>
</div>";

var pdf = renderer.RenderHtmlAsPdf(html);
// This CSS now works with IronPDF
var html = @"
<style>
    .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
    .flex { display: flex; justify-content: space-between; align-items: center; }
</style>
<div class='grid'>
    <div>Column 1</div>
    <div>Column 2</div>
    <div>Column 3</div>
</div>";

var pdf = renderer.RenderHtmlAsPdf(html);
$vbLabelText   $csharpLabel

Issue 4: Synchronous vs Asynchronous Rendering

wkhtmltopdf: Wrappers are synchronous and block threads.

Solution: IronPDF supports async rendering:

public async Task<byte[]> GeneratePdfAsync(string html)
{
    var renderer = new ChromePdfRenderer();
    var pdf = await renderer.RenderHtmlAsPdfAsync(html);
    return pdf.BinaryData;
}
public async Task<byte[]> GeneratePdfAsync(string html)
{
    var renderer = new ChromePdfRenderer();
    var pdf = await renderer.RenderHtmlAsPdfAsync(html);
    return pdf.BinaryData;
}
$vbLabelText   $csharpLabel

wkhtmltopdf Migration Checklist

Pre-Migration Tasks

Audit your codebase to identify all wkhtmltopdf usage:

# Find all wkhtmltopdf references
grep -r "WkHtmlToPdfDotNet\|DinkToPdf\|TuesPechkin\|Rotativa" --include="*.cs" .
grep -r "wkhtmltopdf" --include="*.yml" --include="*.yaml" --include="Dockerfile" .
# Find all wkhtmltopdf references
grep -r "WkHtmlToPdfDotNet\|DinkToPdf\|TuesPechkin\|Rotativa" --include="*.cs" .
grep -r "wkhtmltopdf" --include="*.yml" --include="*.yaml" --include="Dockerfile" .
SHELL

Locate and document wkhtmltopdf binary files for removal. Document current settings (paper size, margins, headers/footers).

Code Update Tasks

  1. Remove all wkhtmltopdf wrapper NuGet packages
  2. Remove wkhtmltopdf binaries (wkhtmltopdf.exe, wkhtmltox.dll)
  3. Install IronPdf NuGet package
  4. Update namespace imports from WkHtmlToPdfDotNet to IronPdf
  5. Replace SynchronizedConverter with ChromePdfRenderer
  6. Convert HtmlToPdfDocument patterns to direct rendering methods
  7. Update GlobalSettings configurations to RenderingOptions
  8. Convert margin configurations from MarginSettings to individual properties
  9. Update placeholder syntax ([page]{page}, [toPage]{total-pages})
  10. Add IronPDF license initialization at startup

Post-Migration Testing

After migration, verify these aspects:

  • Visual comparison of PDF output (should improve with modern CSS support)
  • Verify modern CSS rendering (CSS Grid and Flexbox now work)
  • Test JavaScript-heavy pages
  • Security scan to confirm no wkhtmltopdf binaries remain
  • Load test for performance comparison

Security Verification

# Scan for any remaining wkhtmltopdf artifacts
find /var/www/ -name "*wkhtmlto*" 2>/dev/null
find /usr/local/bin/ -name "*wkhtmlto*" 2>/dev/null
docker images | grep wkhtmltopdf

# Check if any process is still using it
ps aux | grep wkhtmltopdf
# Scan for any remaining wkhtmltopdf artifacts
find /var/www/ -name "*wkhtmlto*" 2>/dev/null
find /usr/local/bin/ -name "*wkhtmlto*" 2>/dev/null
docker images | grep wkhtmltopdf

# Check if any process is still using it
ps aux | grep wkhtmltopdf
SHELL

Key Benefits of Migrating to IronPDF

Moving from wkhtmltopdf to IronPDF provides several critical advantages:

Security: CVE-2022-35583 and all wkhtmltopdf vulnerabilities are eliminated. IronPDF has no known CVEs and receives regular security updates.

Modern Rendering Engine: IronPDF uses the current Chromium engine, ensuring full CSS3, CSS Grid, Flexbox, and ES6+ JavaScript support. Modern frameworks render correctly.

Simplified API: Direct rendering methods replace document construction patterns. Built-in SaveAs() methods eliminate manual byte handling.

Extended Capabilities: PDF manipulation, digital signatures, PDF/A compliance, watermarks, and merge/split operations are built-in features that wkhtmltopdf couldn't provide.

Active Development: As .NET 10 and C# 14 adoption increases through 2026, IronPDF's regular updates ensure compatibility with current and future .NET versions.

Async Support: Prevent thread blocking in high-load web applications with native async/await support.

커티스 차우
기술 문서 작성자

커티스 차우는 칼턴 대학교에서 컴퓨터 과학 학사 학위를 취득했으며, Node.js, TypeScript, JavaScript, React를 전문으로 하는 프론트엔드 개발자입니다. 직관적이고 미적으로 뛰어난 사용자 인터페이스를 만드는 데 열정을 가진 그는 최신 프레임워크를 활용하고, 잘 구성되고 시각적으로 매력적인 매뉴얼을 제작하는 것을 즐깁니다.

커티스는 개발 분야 외에도 사물 인터넷(IoT)에 깊은 관심을 가지고 있으며, 하드웨어와 소프트웨어를 통합하는 혁신적인 방법을 연구합니다. 여가 시간에는 게임을 즐기거나 디스코드 봇을 만들면서 기술에 대한 애정과 창의성을 결합합니다.