DOCX to PDF in Production: Why Headless LibreOffice Hangs, and When to Use an API

The command libreoffice --headless --convert-to pdf is the first result for "docx to pdf linux" and it is a production trap. A Stack Overflow thread…

The command libreoffice --headless --convert-to pdf is the first result for "docx to pdf linux" and it is a production trap. A Stack Overflow thread documenting the same failure pattern—no output, no error, no converted file—has been viewed 205,000 times [1]. The fixes are tribal knowledge spread across a decade of bug reports.

The hang with no error

You run the command. The terminal sits there. No PDF appears, no stderr explains why, and your process monitor shows soffice.bin consuming CPU until you kill it.

A GUI LibreOffice instance already running on the same user profile blocks headless conversion. The bug has been known since 2011, and the workaround is launching each conversion with a separate -env:UserInstallation=file:///tmp/... profile directory [1]. Without this flag, your headless command waits indefinitely for a lock that never releases.

On Ubuntu 14.04, installing only libreoffice-core or libreoffice-common is not enough to convert to PDF; you need libreoffice-writer additionally. The command does not error; it simply waits on a socket that never connects [1]. You discover this only by strace or by happening across the right package list.

Permissions failures are equally silent. If --outdir /pdf points to a root-owned directory, a regular user running LibreOffice is denied write access but receives no error message [1].

Older LibreOffice versions may not have returned errors on failure. Version 24.2.3.2 finally prints some error strings like "Error in option: --outdir must be followed by output directory path" and "Error: source file could not be loaded" [2]. Until you upgrade to that point, you are debugging blind.

No safe concurrency

A single soffice instance processes one conversion at a time. The dxpdf team found that real concurrency means running N instances and becoming a process supervisor yourself. Per the dxpdf post-mortem, each instance needs its own profile directory; sharing one causes lock contention and corrupted profiles, and dead workers leave temp dirs to clean up.

The practical result is that teams wrap every invocation in timeout-and-kill logic and reap zombie soffice children. A LibreOffice headless cheatsheet documents that two soffice processes cannot share a user profile—the second silently exits with code 1 and no output file—and recommends wrapping invocations in timeout 120 soffice ... because corrupt inputs can hang rather than fail [3]. One Stack Overflow user reports headless LibreOffice taking about 20 seconds per PDF with the portable version, converting DOCX files one by one [4].

The Docker and serverless tax

Packaging LibreOffice for containers bloats images and strains platforms. A Docker container on Debian 10 with libreoffice, default-jre-headless, and libreoffice-java-common installed exceeds 1GB [5]. Trial-and-error minimal deb lists exist—one user found roughly seven packages sufficient for LibreOffice 7.5—but these break on edge cases [5].

An Alpine-based Docker image with LibreOffice achieves roughly 200MB versus the 1GB+ full install, but still shows a ~3 second cold start, ~500ms warm conversion per DOCX, and maximum concurrency of 8-10 simultaneous conversions [6]. For AWS Lambda, the documented setup uses a pre-built public ECR Docker image and recommends at least 512MB of memory for the function [7].

Latency and output drift

Per dxpdf benchmarks, font resolution dominates conversion cost: resolving an uncached font takes 120-185ms versus about 4ms for an embedded or cached font. Document size is not the predictor you expect. Their tests on an M3 Max converted a 3-page 34KB doc in 170ms, a 9-page 1.3MB doc in 55ms, and a 171-page 14MB doc in 420ms.

Per dxpdf, the same input produces slightly different PDFs across LibreOffice versions. Lines reflow, tables shift, font substitution changes. Upgrading your base image becomes a visual-regression event if your output is contractual.

Fidelity gaps with Office formats

A Reddit thread on PPTX-to-PDF conversion reports missing formulas and tables cut off in output [8]. One commenter notes that Microsoft set the format up so only Microsoft could reliably open it. For DOCX the situation is better but not resolved; complex styles, track changes, and embedded media still drift.

The same job as one API call

MarkupGo's Office to PDF API converts over 130 document formats to PDF, including .docx, .xlsx, .pptx, .key, .vsd, .odt, and .numbers [9]. The API is currently in BETA [9].

The API accepts multipart/form-data with the document uploaded as binary data, not JSON [9]. Each document is limited to 5MB and requests are limited to 10 files; contact MarkupGo for larger needs [9]. The properties field supports landscape orientation, page ranges, notes export, and bookmarks export [9]. The merge option combines multiple files into a single PDF and must be true when sending multiple files [9]. Appending /buffer to the convert URL returns PDF bytes directly instead of a task object [9].

With the markupgo-node client, installed via npm or yarn and initialized with your API key [10], a DOCX-to-PDF conversion is a two-liner:

import MarkupGo from "markupgo-node";
import fs from "fs";

const markupgo = new MarkupGo({ API_KEY: process.env.MARKUPGO_API_KEY });

const buffer = fs.readFileSync("invoice.docx");

markupgo.office.convert([{ data: buffer, ext: "docx" }])
  .buffer()
  .then((pdfBuffer) => {
    fs.writeFileSync("invoice.pdf", Buffer.from(pdfBuffer));
  });

The office.convert method accepts files and options and returns either a task via .json() or the PDF bytes via .buffer() [10].

For merging a DOCX and XLSX into one PDF with landscape orientation:

const files = [
  { data: fs.readFileSync("report.docx"), ext: "docx" },
  "./spreadsheet.xlsx"
];

const options = {
  merge: true,
  properties: { landscape: true }
};

markupgo.office.convert(files, options)
  .buffer()
  .then((pdfBuffer) => {
    fs.writeFileSync("combined.pdf", Buffer.from(pdfBuffer));
  });

Start with the free trial—100 credits monthly, no credit card required—and convert your first DOCX without installing a desktop suite on your server.

Sources

  1. stackoverflow.com
  2. stackoverflow.com
  3. The LibreOffice headless cheatsheet (converterer.com)
  4. stackoverflow.com
  5. unix.stackexchange.com
  6. medium.com
  7. dev.to
  8. r/libreoffice on Reddit: PPTX to PDF using libreoffice (reddit.com)
  9. markupgo.com
  10. markupgo.com