Gotenberg alternative: pagenull or self-hosting Gotenberg?
, 7 min read
Gotenberg is a good open-source project: a Docker image with an HTTP API that turns HTML, URLs, Markdown and office documents into PDFs, using Chromium and LibreOffice. It’s MIT-licensed, actively released (version 8.39.0 came out on 27 October 2026), and many teams run it in production. If you’re weighing it against a hosted API like pagenull, this guide tries to help you decide, including when the answer is Gotenberg.
When self-hosting Gotenberg is the right choice
- You convert office documents, or work on existing PDFs: Word and spreadsheets through LibreOffice, merging, splitting, encrypting, watermarking. pagenull does HTML and URLs only.
- Documents can’t leave your network: on-premises, air-gapped, or a policy that rules out any outside processor. Gotenberg runs wherever Docker runs.
- Your volume is very high and steady. Servers you already pay for can cost less per document than any per-render price.
- You have people to run it and want full control: which Chromium build, which flags, how it scales.
If one of these is you, pick Gotenberg. The rest of this guide is about what that choice involves, and what a hosted API takes off your plate.
What running it yourself involves
Starting it is one command (docker run --rm -p 3000:3000 gotenberg/gotenberg:8, from its quick start). Running it for real is the work:
- Capacity. By default one instance runs 6 Chromium conversions at once, has no queue limit, and restarts Chromium after 100 conversions (configuration). You size the memory and CPU for your documents, and add instances behind a load balancer for peaks.
- Timeouts. Requests stop after 30 s by default. A slow page or a large document needs that tuned, and your code needs to retry what fails.
- Access. Authentication is off by default; you either keep the service off the internet or turn on basic auth or OIDC (configuration).
- Rendering URLs or user HTML safely. By default a rendered page may load private addresses, such as your internal services or the cloud metadata endpoint. Gotenberg has a flag for it,
--chromium-deny-private-ips, which covers navigations and sub-resources; you have to turn it on (configuration). - Updates. Chromium gets security fixes often. Following Gotenberg’s releases and redeploying is an ongoing task, not a one-off.
- Monitoring. There’s a health route and a version route (routes); alerts, dashboards and log retention are yours to set up.
- Backups: none needed. Gotenberg keeps no database, and the quick start runs it as a throwaway container with no volume. That’s one thing self-hosting doesn’t add.
What pagenull adds
- No infrastructure. No servers, no Chromium upgrades, no capacity planning; one HTTPS request.
- EU hosting. Hosted in Helsinki, Finland, with EU sub-processors only (trust).
- Nothing stored. Your HTML and the PDF are never written to disk; every response says
pagenull-retention: none(security). - A signed receipt for every render. An Ed25519 signature over the SHA-256 of the exact file, checkable by anyone on /verify. Useful when you later need to show what you sent and when.
- E-invoices from JSON. /v1/invoices builds the EN 16931 XML, calculates the totals and checks the rules. Gotenberg can embed a Factur-X XML into a PDF, but producing a correct XML is up to you (its Factur-X route).
- Templates. Save a layout once and render it with JSON (templates).
- SSRF protection on by default, for the page and everything it loads, after redirects and DNS.
- A countersigned DPA on the Volume plan (pricing).
- A free tier: 100 renders a month, no card (pricing).
Side by side
Gotenberg 8.39.0, checked 2026-11-05 against its own docs and repository. Each Gotenberg cell links to where it says so.
| Gotenberg (self-hosted) | pagenull | |
|---|---|---|
| What it is | Open-source software (MIT licence), a Docker image you run on your own servers source | A hosted API; nothing to run |
| What it converts | HTML, URLs and Markdown through Chromium; office documents through LibreOffice source | HTML and URLs through Chromium; no office documents |
| Working on existing PDFs | Merge, split, encrypt, watermark, stamp, flatten, rotate, metadata and bookmarks routes source | Not offered |
| Authentication | Off by default; basic auth or OIDC when you turn it on source | An API key on every request |
| Rendered pages reaching private addresses | Allowed by default; --chromium-deny-private-ips blocks navigations and sub-resources that resolve to them source | Always refused, for the page and everything it loads, after redirects and DNS |
| Request time limit | 30 s by default (--api-timeout) source | timeoutMs per request: 30 s by default, up to 60 s |
| Capacity | 6 Chromium conversions at once by default; queue limit off by default; Chromium restarts after 100 conversions source | Our side. When every renderer is busy you get 503 busy with Retry-After |
| Page defaults | 8.5 × 11 in, 0.39 in margins, backgrounds off, CSS @page size ignored unless preferCssPageSize source | A4, 12 mm margins, backgrounds on, a CSS @page size wins |
| PDF/A | PDF/A-1b, 2b, 3b and 4, and PDF/UA, on the Chromium routes source | PDF/A-3b for e-invoices only |
| E-invoices (Factur-X / ZUGFeRD) | Embeds a Factur-X XML you produce yourself into a PDF source | Builds the EN 16931 XML from JSON, calculates the totals, checks the rules and embeds it |
| Getting the result elsewhere | Webhook headers send the result to a URL you give source | deliverTo uploads to a presigned URL in your own storage |
| Proof of what was produced | No route signs the output source | An Ed25519-signed receipt with every render, checkable on /verify |
Moving from Gotenberg
A Gotenberg HTML request as you may have it now, with A4 paper, 2 cm top and bottom margins and backgrounds on:
curl --request POST http://localhost:3000/forms/chromium/convert/html \
--form files=@index.html \
--form paperWidth=21cm --form paperHeight=29.7cm \
--form marginTop=2cm --form marginBottom=2cm \
--form printBackground=true \
-o invoice.pdfThe same document through pagenull. The HTML goes in a JSON body instead of a form upload:
curl https://api.pagenull.dev/v1/pdf \
-H "Authorization: Bearer $PAGENULL_API_KEY" \
-H "Content-Type: application/json" \
-o invoice.pdf \
--data-binary @- <<EOF
{
"html": $(jq -Rs . < index.html),
"format": "A4",
"margin": { "top": "20mm", "bottom": "20mm" }
}
EOFOption by option:
paperWidth/paperHeight:format("A4", "Letter" …) orwidth/height.marginTopand the other three:margin.topand so on, with a unit (mm,cm,inorpx).printBackground: on by default here, off by default in Gotenberg.preferCssPageSize: on by default here, so a CSS@pagesize wins; off by default in Gotenberg.landscape,scale: same names.nativePageRanges:pageRanges.emulatedMediaType:emulateMedia.waitForExpression:waitFor, which takes a CSS selector rather than a JavaScript expression. There’s no fixed delay likewaitDelay; have your page add an element or attribute when it’s ready.- Extra files sent next to
index.html(images, CSS): load them from a public URL, or inline them asdata:URLs. pagenull takes one HTML document per request.
Full option list in the API reference. While you compare output, send "test": true: test renders are watermarked and free.
Sources
Facts about other projects and rules, checked on 5 November 2026.
- Gotenberg repository
- Gotenberg 8.39.0: MIT licence
- Gotenberg 8.39.0 release
- Gotenberg docs: introduction and quick start
- Gotenberg docs: configuration flags and defaults
- Gotenberg docs: convert HTML to PDF (page properties, PDF/A)
- Gotenberg docs: convert with LibreOffice
- Gotenberg 8.39.0: every route, as its request collection
- Gotenberg 8.39.0: Factur-X route
- Gotenberg 8.39.0: HTML route with webhook headers