NextPDF Gotenberg security and operations
At a glance
Section titled “At a glance”This bridge sends documents from your application to an external service over the network. That makes it both a server-side request surface and a transport-security surface. The package implements specific, verifiable controls for both. It does not secure the whole system by itself. Those controls work only when you deploy and operate Gotenberg with the matching safeguards. This page explains the controls the package implements and the operational duties that complete them.
Nothing here is a guarantee. Each control is a defined, test-covered behavior with stated limits.
The two policy layers
Section titled “The two policy layers”The bridge applies two distinct security policies at different layers:
- Transport policy (
GotenbergSecurityPolicy) — URL scheme enforcement, server-side request forgery (SSRF) screening, Domain Name System (DNS) rebinding defense, input-size limits, and filename screening. This is the layer documented in depth below. - HTML parse policy — a parse-layer content policy, defaulting to the NextPDF core default policy, which runs before content reaches a renderer. It complements the transport policy but remains independent of it. This page covers the transport policy.
Server-side request forgery (SSRF) screening
Section titled “Server-side request forgery (SSRF) screening”The bridge screens the configured API URL before any byte leaves the process. The control has three parts.
Scheme enforcement. Only https is accepted (case-insensitive). A
plain http:// URL is rejected. Transport Layer Security (TLS) is
therefore mandatory for every conversion and for the health probe.
Address screening. If the host is an IP literal, the bridge rejects it when it falls in a private or reserved range. If the host is a name, the bridge resolves it to all of its A and AAAA records, and rejects the request if any resolved address is private or reserved. Resolving the full record set, rather than a single address, is the control that defeats an attacker who hides a private address behind a name that also returns a public one. This follows the OWASP SSRF prevention guidance approach: retrieve every IP address behind the domain name (A and AAAA records, for IPv4 and IPv6), and validate each one against an allowlist (OWASP Cheat Sheet Series, SSRF prevention, application-layer defense; pinned in the page’s retrieval-augmented generation (RAG) sidecar).
Time-of-check/time-of-use (TOCTOU) re-check. The bridge re-resolves and compares the address set captured during validation immediately before the request. If a new address appears, the request is aborted with a DNS-rebinding error. This closes the window between validation and connection that a rebinding attack would otherwise exploit.
When the bridge uses its cURL-pinned transport, the validated address
set is bound to the connection with CURLOPT_RESOLVE, so the kernel
connects to the vetted address rather than to whatever a fresh
connect-time DNS lookup might return. Redirect following is disabled on
that transport (CURLOPT_FOLLOWLOCATION off, CURLOPT_MAXREDIRS zero),
so a 3xx cannot silently send the request to an unvetted host. The
policy layer receives the response instead.
Operational consequence. The SSRF guard rejects private and reserved addresses by design. If your Gotenberg runs on a private network, you cannot point the bridge at its private address. Expose it through an address the guard accepts and protect that path with network segmentation and authentication, as described under deployment below.
Transport security and public-key pinning
Section titled “Transport security and public-key pinning”TLS peer and host verification are always on in the cURL-pinned
transport (CURLOPT_SSL_VERIFYPEER true, CURLOPT_SSL_VERIFYHOST 2).
On top of standard chain validation, the bridge supports
SubjectPublicKeyInfo (SPKI) pinning.
Each pin is a SHA-256 hash of the server’s SubjectPublicKeyInfo,
expressed as sha256/<base64>. The bridge accepts a certificate when
its SPKI hash matches any pin in the combined primary-plus-backup set.
This backup-pin model follows RFC 7469 §4.3, which identifies a backup
pin — a fingerprint of a secondary, not-yet-deployed key pair — as the
primary recovery path for an inadvertent pin-validation failure, and
§2.5, which requires the pinned set to include at least one pin not
present in the current certificate chain (RFC 7469 §4.3 and §2.5;
pinned in the page’s RAG sidecar). The bridge’s code declares RFC 7469
§2.1 and §2.6 for the at-least-one-backup-pin and combined-set
intersection semantics. Pinning is opt-in: with no pins configured,
standard chain validation applies and pinning is not enforced.
A pin that does not parse raises a configuration error before any request. A live certificate whose SPKI matches no configured pin causes the transport to fail the request — by design.
Pin rotation procedure
Section titled “Pin rotation procedure”An incorrect rotation locks the bridge out of the service. Rotate without an outage:
- Before changing the server key, generate the SPKI pin for the new key and add it to the backup pin list. Deploy that configuration. The bridge now accepts both the current and the future key.
- Roll the server certificate or key to use the new key.
- Confirm conversions still succeed (the new key now matches the backup pin).
- Move the new pin from the backup list to the primary list and remove the retired key’s pin. Deploy.
- Generate and stage the pin for the next rotation as the new backup so the set always carries a usable spare.
Keeping the backup list separate from the primary list lets you stage and validate the next pin without touching the active one.
Authentication
Section titled “Authentication”When apiKey is non-empty, the bridge sends it as an
Authorization: Bearer header on the conversion request. The field is
marked #[\SensitiveParameter] so the value is redacted from stack
traces. The bridge does not load the secret for you; supply it from a
secret manager at process start and never commit it. The token is not
written to the request log — the logged debug entry contains the URL,
file name, format, and content length only.
Response trust boundary
Section titled “Response trust boundary”A response is accepted only when the status is 200, the
Content-Type contains application/pdf, and the body begins with the
%PDF signature. The byte-signature check matters because a declared
content type alone does not prove what the bytes are. The WHATWG MIME
Sniffing standard formalizes the same reasoning in its MIME-type
sniffing algorithm, which derives a computed type from leading
byte-pattern matching rather than from the supplied type. The OWASP file
upload guidance states the corresponding application principle: validate
the file type and do not trust the declared Content-Type header,
because it can be spoofed (WHATWG MIME Sniffing §6.2.3; OWASP Cheat
Sheet Series, file-upload validation; both pinned in the page’s RAG
sidecar). The bridge applies the equivalent check defensively on the
inbound side: a mismatch raises a typed exception, and the bytes are
never returned as a result.
This boundary is also why the PSR-18 contract matters here. A PSR-18
client throws only when it cannot send the request or cannot parse the
response into a PSR-7 object — it does not throw on an error status
code. A well-formed 4xx/5xx response is returned to the caller as
normal (PSR-18, “Exceptions”; pinned in the page’s RAG sidecar). The
bridge therefore inspects the status, type, and signature itself rather
than assuming a returned response is successful. The HTTP semantics for
a content-type constraint violation — a 415 (Unsupported Media Type)
rejection, where a server refuses content in an unsupported format — are
the model the inbound check mirrors (RFC 9110 §15.5.16; pinned in the
page’s RAG sidecar).
Resource limits
Section titled “Resource limits”The bridge has one in-process resource bound: maxFileSize (default
52,428,800 bytes = 50 MiB). It is enforced before the request, so an
oversized input never reaches the service. There is no built-in
concurrency limit, rate limit, or output-size cap in the bridge. Those
are deployment and caller responsibilities (see /integrations/gotenberg/production-usage/).
Set maxFileSize to the smallest value your real documents require — a
tighter cap is a cheaper denial-of-service control.
Deploying and securing the Gotenberg service
Section titled “Deploying and securing the Gotenberg service”The bridge is only as safe as the service it calls. You operate that service; the duties below complete the controls above.
- Terminate TLS in front of Gotenberg. Gotenberg’s container speaks plain HTTP by default. The bridge requires HTTPS, so place Gotenberg behind a TLS-terminating reverse proxy, ingress, or service mesh and point the bridge at the HTTPS endpoint. Pin the proxy’s SPKI if you enable pinning.
- Do not expose Gotenberg publicly. It performs document conversion with LibreOffice and Chromium-class engines, and it is not an internet-facing service. Restrict ingress to the application hosts that call it, using network policy or firewall rules.
- Require authentication on the path. The bridge sends a bearer token when configured; enforce it (or mutual TLS) at the proxy so an unauthenticated request cannot reach the conversion engine.
- Pin a specific service version. The bridge assumes exactly two
service paths —
/forms/libreoffice/convertand/health. Pin the Gotenberg image to a specific patch tag, verify those two paths against the version you deploy, and re-verify on every upgrade. - Size conversion capacity deliberately. Each conversion holds a worker for the request duration. Size the Gotenberg deployment for your peak concurrent conversion rate and bound in-flight conversions on the caller side to match. Capacity is a property of your deployment, not of this package.
- Treat conversion inputs as untrusted. Documents pushed through
conversion are processed by complex engines. Constrain
maxFileSize, isolate the Gotenberg deployment (its own network segment, minimal egress, no access to internal services), and keep the engine patched.
What this package does not claim
Section titled “What this package does not claim”- It is not “secure by default”: the controls are real but they depend on correct deployment and configuration.
- It does not make conversion “tamper-proof” or the output “certified”. It validates the transport and the response shape; it does not attest document content.
- It does not provide signing, timestamping, or long-term validation. Those are post-processing concerns. The Pro edition’s PAdES support is the B-B baseline only and does not include B-T, B-LT, or B-LTA; nothing in this bridge implies a timestamp or LTV capability.
- It does not support “all Office files”. It supports the six enumerated formats and rejects everything else before any request.
See also
Section titled “See also”- /integrations/gotenberg/configuration/ — transport-selection rules and the full pin model.
- /integrations/gotenberg/production-usage/ — retries, timeouts, concurrency, and observability.
- /integrations/gotenberg/troubleshooting/ — every security exception and its trigger.
- /integrations/gotenberg/overview/ — the conversion flow and dependency model.