Skip to content

NextPDF Gotenberg troubleshooting

The bridge fails loudly and early. Every failure raises a typed exception with a message that names the cause. Use this page as the catalog. For each failure, you get the exception type, the message fragment you will see, the exact trigger in the code path, and the fix.

The exception families are:

  • GotenbergConvertException — a conversion-layer failure (config, transport, or response).
  • RuntimeException — a validation-layer failure raised by the security policy before any network traffic.
  • ValueError — an unrecognized file extension.
  • InvalidSpkiPinException — a malformed Transport Layer Security (TLS) SubjectPublicKeyInfo (SPKI) pin string.

”Invalid Gotenberg configuration: apiUrl is empty”

Section titled “”Invalid Gotenberg configuration: apiUrl is empty””
  • Type: GotenbergConvertException
  • Trigger: You called convertFile() or convertString() while GotenbergConfig::isValid() is false. This happens when apiUrl is an empty string.
  • Fix: Supply a non-empty HTTPS URL. If you build the config with fromArray(), note that it silently substitutes '' for a missing or non-string api_url. Validate your config source during boot.

These failures come from the security policy, which guards against server-side request forgery (SSRF). The bridge raises them before it sends any request. Each one is a plain RuntimeException.

”Gotenberg API URL must use HTTPS (got: http)”

Section titled “”Gotenberg API URL must use HTTPS (got: http)””
  • Trigger: The configured URL scheme is not https. The check is case-insensitive, so HTTPS:// is accepted.
  • Fix: Put Gotenberg behind TLS and configure the HTTPS endpoint. Plain HTTP is rejected even for local development.

”Invalid Gotenberg API URL: unable to parse”

Section titled “”Invalid Gotenberg API URL: unable to parse””
  • Trigger: The URL cannot be parsed into a scheme and host.
  • Fix: Provide a syntactically valid absolute URL, for example https://gotenberg.example.com:3000.

”Gotenberg API URL must not resolve to a private or reserved IP address”

Section titled “”Gotenberg API URL must not resolve to a private or reserved IP address””
  • Trigger: The host is a private or reserved Internet Protocol (IP) literal, or a hostname that resolves (via all A/AAAA records) to any private or reserved address. This blocks the private ranges from Request for Comments (RFC) 1918, loopback, and link-local addresses.
  • Fix: Point the bridge at a routable public address or a properly segmented service address. If your Gotenberg is intentionally on a private network, the bridge’s SSRF guard rejects it by design. Expose it through an address the guard accepts, then protect that path with network controls and authentication. See /integrations/gotenberg/security-and-operations/.

”Gotenberg API URL DNS answer changed since validation — possible DNS rebinding attack”

Section titled “”Gotenberg API URL DNS answer changed since validation — possible DNS rebinding attack””
  • Trigger: Between initial validation and the request, a fresh Domain Name System (DNS) resolution returned an address that was not in the originally validated set.
  • Fix: This is the time-of-check/time-of-use guard firing. Investigate DNS for the host. A valid cause is a load balancer rotating addresses. A malicious cause is a rebinding attack. Use a stable address or a name with a stable record set for the Gotenberg endpoint.

The security policy raises these failures before the request. Each one is a plain RuntimeException unless noted.

  • Type: GotenbergConvertException
  • Trigger: convertFile() could not canonicalize the path, or the resolved path is not a regular readable file. A directory triggers this too.
  • Fix: Pass a path to an existing, readable file. The path is canonicalized with realpath() first, which also defeats traversal.

”File size ( bytes) exceeds maximum allowed size ( bytes)”

Section titled “”File size ( bytes) exceeds maximum allowed size ( bytes)””
  • Trigger: The input is larger than maxFileSize (default 52,428,800 bytes = 50 MiB).
  • Fix: Raise maxFileSize if the document legitimately needs it, or reject the upload upstream. Keep the cap as low as your real documents allow. It is the bridge’s only built-in resource limit.

The bridge validates the filename. For file conversions, the filename is the basename of the resolved path; for convertString(), it is the name you pass in. Each of these is a RuntimeException:

Message fragmentTrigger
must not be emptyempty filename
path traversal sequences (..)the name contains ..
forward slashesthe name contains /
backslashesthe name contains \
null bytesthe name contains a NUL byte
control charactersthe name contains an ASCII control character (0–31)
  • Fix: Pass a clean basename. For convertString(), supply a plain name such as report.docx. It is used for format detection and as the multipart upload filename, not as a path.
  • Type: ValueError
  • Trigger: The file extension is not one of docx, xlsx, pptx, odt, ods, odp (case-insensitive, leading dot tolerated).
  • Fix: Convert only the six recognized formats. The bridge does not recognize the legacy binary formats (.doc, .xls, .ppt), .rtf, .csv, plain text, or images. Convert these inputs to a recognized format before you call the bridge, or route them through a different path.

All of these are GotenbergConvertException.

  • Trigger: The PHP Standard Recommendation 18 (PSR-18) client (or the cURL-pinned transport) raised an exception while sending the request. The cause is connection refusal, a timeout, a TLS handshake failure, or a pin mismatch.
  • Exception code: the underlying client exception’s code.
  • Cause: the original PSR-18 client exception is attached as the previous exception.
  • Fix: Check four things. Check service reachability with isAvailable(). Check the network path. Check the TLS chain. If pinning is configured, check that the server’s current SubjectPublicKeyInfo (SPKI) matches a configured pin. A pin mismatch after a certificate rotation is the classic cause. See the rotation procedure in /integrations/gotenberg/security-and-operations/.
  • Trigger: The cURL-pinned transport’s curl_exec failed with a non-zero cURL error number, or returned a non-string body.
  • Fix: The cURL error number identifies the cause (TLS, resolve, timeout, pin). A pinning failure surfaces here when CURLOPT_PINNEDPUBLICKEY rejects the certificate. Confirm that the configured pins and the resolved address are current.

”Gotenberg conversion failed with HTTP : ”

Section titled “”Gotenberg conversion failed with HTTP : ””
  • Trigger: The response status was not 200. The body is included, truncated to the first 500 characters, with an ellipsis appended when longer.
  • Fix: Read the included body. Gotenberg’s error message explains why the conversion was rejected: unsupported document content, an internal LibreOffice failure, or an authentication rejection on a 401 or 403. A 401/403 means the apiKey is missing or wrong. A 5xx is a service-side failure and is the candidate for a bounded retry.

”Unexpected Content-Type from Gotenberg: (expected application/pdf)”

Section titled “”Unexpected Content-Type from Gotenberg: (expected application/pdf)””
  • Trigger: The status was 200, but the response Content-Type did not contain application/pdf.
  • Fix: This usually means a proxy or gateway returned an HTML error or redirect page with a 200. The bridge disables redirect following on the pinned transport deliberately, so a 3xx response is not silently followed to an unvetted host. A body arriving with the wrong Content-Type signals that something between you and Gotenberg is interfering. Inspect the network path.

”Response body does not start with %PDF header — invalid PDF data”

Section titled “”Response body does not start with %PDF header — invalid PDF data””
  • Trigger: Status 200, Content-Type acceptable, but the body does not begin with the %PDF signature.
  • Fix: The upstream returned something that is not a Portable Document Format (PDF) file despite the headers. Treat the response as untrusted and investigate the service. Do not write the body to disk. The bridge refuses to return it as a result.

”Invalid SPKI pin format: (expected sha256/)”

Section titled “”Invalid SPKI pin format: (expected sha256/)””
  • Type: InvalidSpkiPinException
  • Trigger: A configured pin string does not start with sha256/ or sha256//.
  • Fix: Format each pin as sha256/<base64-encoded-spki-hash>. The transport also accepts the cURL-native sha256//<base64> form. Generate the value from the server certificate’s SubjectPublicKeyInfo, not from the whole certificate.

”It says unavailable but the service is up”

Section titled “”It says unavailable but the service is up””

isAvailable() returns false without any network call when the URL is empty, not HTTPS, or resolves to a private/reserved address. It also returns false on any network error, or when /health returns 500 or above; in those cases, it catches the error instead of throwing. Check, in order:

  1. The configured URL is non-empty and HTTPS.
  2. The host does not resolve to a private/reserved address (the SSRF guard rejects it even for the probe).
  3. <apiUrl>/health is reachable from the application host and returns a status below 500.
  • /integrations/gotenberg/configuration/ — all options and transport-selection rules.
  • /integrations/gotenberg/production-usage/ — retry policy and the failure-handling contract.
  • /integrations/gotenberg/security-and-operations/ — the SSRF model and pin rotation.
  • /integrations/gotenberg/quickstart/ — the exhaustive catch order in context.