Das brauchen echt viele Firmen, und es kommt immer als kleines Ticket: "Können wir das auch als PDF haben?"
Klar, du kannst ein Print-Stylesheet ausliefern und die Leute Strg+P drücken lassen. Sehr oft reicht das nicht, und professionell sieht es nie aus. Es funktioniert außerdem überhaupt nicht mehr, sobald das PDF ohne Menschen vor einem Browser entstehen muss — Rechnungen nach Zeitplan, ein Vertrag im Anhang einer Bestätigungsmail, ein Report aus einem Cronjob.
Irgendwann muss der Server HTML also selbst in ein PDF verwandeln. Dafür nutze ich zwei Engines, und in diesem Artikel geht es darum, wie du zwischen ihnen wählst.
Warum überhaupt HTML?
Du könntest PDFs direkt mit einer Library wie FPDF oder TCPDF erzeugen und jedes Element an eine Koordinate setzen. Ich habe das gemacht. Es funktioniert, und es ist elend: Das Layout lebt als Haufen von x/y-Werten in PHP, und jede Designänderung bedeutet, dass jemand eine Figma-Datei in Arithmetik übersetzt.
Über HTML zu gehen heißt, dass das Dokument ein Template ist, das Styling CSS, und die Person, die das Design ändert, keine Backend-Entwicklerin sein muss. Außerdem nutzt du die Template-Engine weiter, die du sowieso schon hast.
Das PDF ist nur eine weitere Ansicht deiner Daten. Behandle es so, und es hört auf, ein Sonderprojekt zu sein.

Option 1: wkhtmltopdf
wkhtmltopdf verpackt einen alten WebKit-Build und macht mit einem Befehl aus einer HTML-Datei ein PDF. Es ist klein, es ist schnell, und es macht diesen Job seit weit über einem Jahrzehnt.
Was ich daran mag:
- Klein. Ein vollständiger Alpine-Container mit wkhtmltopdf und einem kleinen HTTP-Wrapper misst etwa 350 MB. Ein Container mit Google Chrome darin ist ein Vielfaches davon.
- Schnell und günstig. Kein Browserstart, wenig Speicher. Davon kannst du viele nebeneinander laufen lassen.
- Hervorragende Seitensteuerung. Kopfzeilen, Fußzeilen, Seitenzahlen, Ränder und Seitengröße sind erstklassige Kommandozeilenoptionen und kein CSS, gegen das du kämpfst.
Was wehtun wird:
- Die Render-Engine ist alt. Flexbox ist unzuverlässig, CSS Grid wird nichts. Wenn dein Template ein modernes Layout nutzt, sieht es nicht aus wie deine Browser-Vorschau.
- JavaScript-Unterstützung ist begrenzt. Alles, was clientseitig rendert, ist ein Glücksspiel.
- Das Projekt ist archiviert. Die Entwicklung ist eingestellt. Es funktioniert noch, aber es wird keine Features und kein neues CSS mehr bekommen.
wkhtmltopdf ist die richtige Antwort, wenn das Dokument ein Dokument ist: eine Rechnung, ein Vertrag, ein Zertifikat, ein Report. Strukturierter Inhalt, tabellenbasiertes Layout, braucht Seitenzahlen, muss zehntausendmal am Tag erzeugt werden, ohne umzufallen.
Option 2: Chrome headless
Die andere Möglichkeit ist, ein echtes Chrome zu steuern, meist über Puppeteer, und dessen Print-to-PDF-Funktion aufzurufen. Es ist dieselbe Engine, in der deine Designerin das Template vorab gesehen hat, was eine ganze Problemklasse verschwinden lässt.
Was ich daran mag:
- Es rendert exakt, was ein Browser rendert. Grid, Flexbox, Webfonts, SVG, Custom Properties — alles, weil es Chrome ist.
- JavaScript funktioniert. Clientseitig gezeichnete Charts, nach dem Laden geholte Inhalte, alles Dynamische.
- Es bleibt aktuell. Chrome hört nicht auf, CSS-Features zu bekommen.
Was wehtun wird:
- Es ist ein Browser. Ein Container mit Chrome ist schwer, und jeder Render frisst echten Speicher. Nebenläufigkeit kostet dich etwas.
- Startzeit. Pro Request einen Browser zu starten ist langsam. Du wirst am Ende einen am Leben halten und Pages verwalten, was mehr Code ist.
- Kopf- und Fußzeilen sind fummeliger. Es gibt sie, aber als HTML-Templates mit eigenen Eigenheiten statt als einfache Flags.
Chrome headless ist die richtige Antwort, wenn das Dokument eine gestaltete Seite ist: etwas mit echtem Layout, Markenstyling, Charts, oder alles, was jemand mit einem Mockup vergleichen wird.
Wie ich tatsächlich wähle
Eine Frage: Hätte dieses Dokument 2013 gut ausgesehen?
Rechnungen, Lieferscheine, Antragsformulare, Zertifikate — ja. Nimm wkhtmltopdf, halte den Container klein, halte den Durchsatz hoch.
Alles mit einem Layout, das jemand in diesem Jahrzehnt entworfen hat — nein. Nimm Chrome, akzeptiere das Gewicht, und hör auf, Tage an CSS zu verlieren, das im Browser rendert und im PDF nicht.
Die zweite Frage, falls die erste unentschieden ausgeht: Wie viele davon pro Minute? Hohes Volumen drückt Richtung wkhtmltopdf, niedriges Volumen mit hohem optischen Anspruch Richtung Chrome.
Das eine, was beide Setups brauchen
Welche Engine du auch nimmst: Lass sie nicht in deinem Anwendungscontainer laufen.
Dein PHP-Container sollte keinen Browser enthalten, und ein PDF-Binary auch nicht. Der Renderer ist ein eigener Service mit eigenem Image, und deine Anwendung spricht per HTTP mit ihm. Das hält das Anwendungs-Image klein, erlaubt dir, den Renderer unabhängig zu skalieren oder zu ersetzen, und macht einen späteren Engine-Wechsel zu einer geänderten URL statt zu einem Rebuild von allem.
Diese Architektur habe ich separat aufgeschrieben, in wie du einen PDF-Generator-Service für dein Backend baust, und das Chrome-spezifische Setup in Chrome headless für die PDF-Erzeugung.
Der Teil, vor dem dich niemand warnt: Schriften
Beide Engines ersetzen stillschweigend eine Schrift, die sie nicht haben. Dein Container ist nicht dein Laptop — er hat fast keine Schriften installiert. Das PDF kommt subtil falsch heraus, niemand kann sagen warum, und du verlierst einen Nachmittag, bevor dir auffällt, dass die Hausschrift schlicht nicht im Image ist.
Installiere die Schriften, die du brauchst, explizit im Renderer-Image, und prüf das Ergebnis auf einer Maschine, die diese Schriften nie gesehen hat. Das ist der mit Abstand häufigste "läuft doch lokal"-Bug bei der PDF-Erzeugung.