Ubuntu 24.04: install Stirling PDF and run it in production

Install Stirling PDF on Ubuntu 24.04 LTS with Docker Compose: OCR setup, reverse proxy with TLS, hardening and operational monitoring.

Reading time: 18 min

PDF documents are among the most sensitive data holdings in companies and public authorities: employment contracts, invoices, tax records and internal reports must never, for data-protection and GDPR reasons, be sent unchecked to external cloud services. Web-based tools such as Smallpdf or Adobe Cloud do solve everyday tasks like merging or redacting pages, but in a business environment they regularly violate compliance rules.

Stirling PDF resolves this dilemma as a fully open-source, locally hosted complete solution. The application runs as a lightweight Docker container, processes all documents locally in the RAM of your own server and leaves no persistent file remnants on external platforms after processing is finished.

Stirling PDF is deployed on a server with Ubuntu 24.04 LTS (Noble Numbat): with an integrated OCR engine for German and English text, a hardened Nginx reverse proxy with TLS termination, and reproducible update and backup routines for production.

πŸ’‘ Note: A working online demo of the software is provided by the project at pdf.ipx64.xyz. For your own infrastructure we use the official container distribution.

Architecture and components

At its core Stirling PDF is based on a Java application (Spring Boot) and under the hood orchestrates specialized Linux command-line tools. Instead of reinventing the wheel, the container wraps proven open-source libraries:

  • Apache PDFBox and OpenPDF: Handle low-level PDF operations (split pages, merge, rotate, edit metadata).
  • LibreOffice (headless): Converts office formats (DOCX, ODT, PPTX, XLSX) losslessly into standards-compliant PDF files.
  • Tesseract OCR and OCRmyPDF: Search scanned documents, recognize text layers and embed searchable OCR text into existing PDFs.
  • Ghostscript and QPDF: Optimize file sizes, repair damaged PDF structures and decrypt password-protected files.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              STIRLING PDF ARCHITECTURE MODEL                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚   Web browser (client)                                      β”‚
β”‚        β”‚                                                    β”‚
β”‚        β–Ό HTTPS (port 443)                                   β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚ Nginx reverse proxy (TLS termination and SSL)       β”‚   β”‚
β”‚   β”‚ client_max_body_size 100M; proxy_read_timeout 300s; β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚        β”‚ HTTP (127.0.0.1:8080)                              β”‚
β”‚        β–Ό                                                    β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚ Docker container (stirlingtools/stirling-pdf)       β”‚   β”‚
β”‚   β”‚                                                     β”‚   β”‚
β”‚   β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚   β”‚
β”‚   β”‚  β”‚  Spring Boot  β”‚ β”‚  LibreOffice  β”‚ β”‚ Tesseract β”‚  β”‚   β”‚
β”‚   β”‚  β”‚  Web / Auth   β”‚ β”‚  Converter    β”‚ β”‚    OCR    β”‚  β”‚   β”‚
β”‚   β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚              β–Ό                 β–Ό               β–Ό            β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚ Host filesystem / persistent volumes (/opt/...)     β”‚   β”‚
β”‚   β”‚  ./configs       ./customFiles       ./trainingData β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The split between reverse proxy on the host and the Stirling container on 127.0.0.1 ensures that unencrypted HTTP traffic never reaches the external network unprotected.

System requirements and resource planning

Before we start the deployment, we check the hardware resources of the target server. PDF operations with LibreOffice and optical character recognition (OCR) are CPU- and memory-intensive.

Minimum vs. recommended system resources:

Resource Minimum operation (1–2 users) Recommended for production and OCR
CPU 2 vCPUs 4 vCPUs (speeds up parallel OCR jobs)
Memory 2 GB RAM 4 to 8 GB RAM (Tesseract + JVM)
Disk 10 GB free space 25 GB SSD/NVMe (for temp files and Docker images)
Operating system Ubuntu 24.04 LTS (x86_64 or ARM64) Ubuntu 24.04 LTS (x86_64 or ARM64)

⚠️ Watch the OOM killer: When Stirling PDF OCRs a 200-page PDF or renders complex Word documents through LibreOffice, memory use can briefly rise by 1.5 to 2 GB. On systems with only 2 GB RAM an active swap file is mandatory, otherwise the Linux kernel terminates the container with an out-of-memory signal (SIGKILL).

Step 1: Update the system and install Docker

Ubuntu 24.04 still ships the old docker.io package and the outdated Python version of Docker Compose in its default package sources. For stable and secure operation we use the official repository from Docker Inc. with the modern Compose v2 extension (docker-compose-plugin).

Refresh package sources and install base tools:


sudo apt update && sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg lsb-release

Add the official Docker GPG key and configure the repository:


sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Install Docker Engine and Docker Compose v2:


sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Validate service status and autostart:


sudo systemctl enable --now docker
docker compose version

# Expected output:
Docker Compose version v2.29.x (or newer)

Step 2: Create the directory structure and permissions

We place the container configuration in a structured way under /opt/stirling-pdf. All persistent data, language models and custom settings remain isolated in dedicated subdirectories:


sudo mkdir -p /opt/stirling-pdf/{configs,customFiles,trainingData,pipeline}
cd /opt/stirling-pdf

Meaning of the directories:

  • configs/: Holds the application configuration (settings.yml), databases for user accounts and API keys.
  • customFiles/: Allows storing your own logos, CSS customizations or watermark templates.
  • trainingData/: Storage for Tesseract OCR language packs (.traineddata), e.g. German (deu) and English (eng).
  • pipeline/: Optional folder for automated processing pipelines (e.g. automatic OCR for dropped scans).

Step 3: Download OCR language files

By default the Stirling PDF base image ships English language data. To process German scans without errors (including umlauts À, â, ü and ß), we download the optimized Fast Tessdata model directly into our volume:


cd /opt/stirling-pdf/trainingData
sudo curl -fsSL -O https://github.com/tesseract-ocr/tessdata_fast/raw/main/deu.traineddata
sudo curl -fsSL -O https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata
cd /opt/stirling-pdf

❗ Tip on model quality: For archival requirements with maximum character precision you can alternatively use the tessdata_best repository. The tessdata_fast model, however, needs only about a quarter of the RAM and, with standard fonts, delivers nearly identical recognition rates at significantly higher throughput.

Step 4: Production-ready Docker Compose configuration

Stirling PDF offers two primary image variants:

  1. stirlingtools/stirling-pdf:latest: Standard image with the full feature set (LibreOffice, OCR, PDFBox).
  2. stirlingtools/stirling-pdf:latest-fat: Already contains all worldwide Tesseract language files (requires over 4 GB of disk space on pull).

We use the standard image and bind the required language models in a targeted way through the mounted trainingData volume.

Create the file /opt/stirling-pdf/docker-compose.yml:


sudo nano /opt/stirling-pdf/docker-compose.yml

Insert the following configuration:


services:
  stirling-pdf:
    image: stirlingtools/stirling-pdf:latest
    container_name: stirling-pdf
    restart: unless-stopped
    ports:
      # Bind to localhost only β€” protection via Nginx reverse proxy
      - "127.0.0.1:8080:8080"
    volumes:
      - ./trainingData:/usr/share/tessdata:rw
      - ./configs:/configs:rw
      - ./customFiles:/customFiles:rw
      - ./pipeline:/pipeline:rw
    environment:
      # User interface and localization
      - SYSTEM_DEFAULTLOCALE=en-US
      - UI_APPNAME=AdminDocs PDF Studio
      # Security and authentication options
      - DOCKER_ENABLE_SECURITY=true
      - SECURITY_ENABLE_LOGIN=true
      - SECURITY_CSRF_DISABLED=false
      # Performance and resource settings
      - INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
      - SYSTEM_MAXFILESIZE=100
    deploy:
      resources:
        limits:
          cpus: '3.00'
          memory: 3500M
        reservations:
          memory: 1024M
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Explanation of the central parameters:

  • 127.0.0.1:8080:8080: Binds the service exclusively to the local loopback adapter. External requests on port 8080 are dropped immediately and must pass the TLS-secured proxy.
  • DOCKER_ENABLE_SECURITY=true and SECURITY_ENABLE_LOGIN=true: Enables Stirling PDF's built-in role and user system.
  • SYSTEM_MAXFILESIZE=100: Sets the upload limit for input files to 100 megabytes.
  • limits.memory: 3500M: Protects the host from uncontrolled memory hunger on faulty or manipulated PDF files.

Step 5: Start the container and create the initial administrator

We start the container in the background:


cd /opt/stirling-pdf
sudo docker compose up -d

Follow the start process in the logs:


sudo docker compose logs -f stirling-pdf

On first start with the security option enabled (DOCKER_ENABLE_SECURITY=true), Stirling PDF automatically creates a temporary administrator account. Watch the logs for the following string:


#################################################################
# Generated Admin Username: admin                               #
# Generated Admin Password: <generated-one-time-password>       #
#################################################################

Copy this initial password to the clipboard. We use it in the web interface to set a permanent password immediately.

πŸ’‘ Note on the initial password: If the password was missed in the logs, it can always be read in /opt/stirling-pdf/configs/settings.yml or regenerated by stopping the container and resetting the configuration.

Step 6: Hardening with Nginx reverse proxy and Let's Encrypt

For productive use on the intranet or the internet we attach Stirling PDF through an Nginx reverse proxy with a secure TLS certificate.

Install Nginx and Certbot:


sudo apt install -y nginx certbot python3-certbot-nginx

Create the Nginx configuration for the domain:

Replace pdf.example.com with your desired domain name:


sudo nano /etc/nginx/sites-available/stirling-pdf.conf

Insert the following server block:


server {
    listen 80;
    listen [::]:80;
    server_name pdf.example.com;

    # Generous limits for large document uploads
    client_max_body_size 100M;
    client_body_timeout 300s;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;

        # Headers to forward client information
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support for progress displays
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Timeouts for compute-heavy OCR and conversion jobs
        proxy_connect_timeout 90s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
    }
}

Why client_max_body_size and timeouts are indispensable:

By default Nginx limits requests to 1 megabyte (client_max_body_size 1M). Every upload of a scanned PDF or a large handbook would otherwise be rejected with 413 Request Entity Too Large. The extended timeouts (proxy_read_timeout 300s) prevent connection drops (504 Gateway Timeout) when Tesseract analyzes hundreds of pages.

Enable the site and check the configuration:


sudo ln -s /etc/nginx/sites-available/stirling-pdf.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Generate a TLS certificate with Let's Encrypt:


sudo certbot --nginx -d pdf.example.com

Certbot automatically extends the Nginx configuration with TLSv1.2/TLSv1.3 directives and sets up automatic certificate renewal via a systemd timer.

Step 7: Configure the firewall with UFW

We make sure that only the secure web ports are reachable from outside:


sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

# Check the status:
sudo ufw status verbose

Internal port 8080 is isolated thanks to our Compose binding to 127.0.0.1 and does not appear in the external firewall rules at all.

Step 8: Verification and system check

After setup we check the operating state of all components on the Linux console.

1. Check port binding:


ss -tulpn | grep 8080

# Expected result (must show 127.0.0.1, never 0.0.0.0):
tcp   LISTEN 0      4096   127.0.0.1:8080       0.0.0.0:*    users:(("docker-proxy",pid=...))

2. Check the Docker healthcheck:


docker compose -f /opt/stirling-pdf/docker-compose.yml ps

# Expected result (status "healthy"):
NAME           IMAGE                             COMMAND                  SERVICE        STATUS
stirling-pdf   stirlingtools/stirling-pdf:latest "tini -- ./entrypoin…"   stirling-pdf   Up 5 minutes (healthy)

3. Query API status and Tesseract support:


curl -s http://127.0.0.1:8080/api/v1/info/status

The JSON response must report status UP and list the installed OCR languages.

Maintenance, updates and backup strategy

Professional operation requires reliable maintenance routines for container updates and configuration backups.

Performing container updates

Because Stirling PDF is actively developed, monthly updates should be planned:


cd /opt/stirling-pdf
sudo docker compose pull
sudo docker compose down
sudo docker compose up -d
sudo docker image prune -f

Creating a consistent configuration backup

Because the actual PDF files are deleted from temporary storage immediately after processing, for disaster recovery we only need to back up configuration data and user settings:


#!/bin/bash
# Backup script for Stirling PDF
BACKUP_DIR="/var/backups/stirling-pdf"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/stirling_config_$DATE.tar.gz" -C /opt/stirling-pdf configs customFiles trainingData

# Clean backups older than 30 days
find "$BACKUP_DIR" -type f -name "*.tar.gz" -mtime +30 -delete

This script can be stored as a daily cron job under /etc/cron.daily/backup-stirling-pdf.

Typical errors and troubleshooting

In practice, specific error states can appear during PDF processing:

1. Error 413: Request Entity Too Large

  • Symptom: When uploading a PDF larger than 1 MB, the browser aborts with HTTP 413.
  • Cause: The Nginx directive client_max_body_size is missing or set too low.
  • Fix: Set client_max_body_size 100M; in /etc/nginx/sites-available/stirling-pdf.conf and run sudo systemctl reload nginx.

2. Tesseract does not recognize German umlauts

  • Symptom: OCR texts contain hieroglyphs or spaces instead of Γ€, ΓΆ, ΓΌ.
  • Cause: The language file deu.traineddata was not found in the mounted directory or has wrong file permissions.
  • Diagnosis and fix:

``bash ls -lh /opt/stirling-pdf/trainingData/ sudo chmod 644 /opt/stirling-pdf/trainingData/*.traineddata sudo docker compose restart stirling-pdf ``

3. Container is terminated unexpectedly during OCR processing

  • Symptom: docker compose ps shows Exited (137).
  • Cause: Exit code 137 means SIGKILL by the Linux out-of-memory (OOM) killer.
  • Diagnosis: Check dmesg -T | grep -i oom.
  • Fix: Raise the memory limit in docker-compose.yml (memory: 4096M) or set up a 4 GB swap file on the host:

``bash sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab ``

Command Reference (Cheatsheet)

The most important operational commands for management and fault diagnosis at a glance:

Task / command Function / explanation
docker compose -f /opt/stirling-pdf/docker-compose.yml up -d Starts the Stirling PDF stack in the background
docker compose -f /opt/stirling-pdf/docker-compose.yml logs -f Shows real-time logs of the application and the web server
docker compose -f /opt/stirling-pdf/docker-compose.yml restart Restarts the container (e.g. after a configuration change)
docker stats stirling-pdf Monitors live CPU and memory use of the container
nginx -t && systemctl reload nginx Validates the Nginx reverse-proxy configuration and reloads rules
certbot renew --dry-run Simulates automatic renewal of the TLS certificate
ss -tulpn &#124; grep 8080 Checks whether port 8080 is listening exclusively on 127.0.0.1

Further Resources

The following documentation provides deeper information on the technologies and components used:

Resource / documentation Description
Official Stirling PDF documentation Detailed documentation of all parameters, API endpoints and pipeline functions
Stirling-Tools GitHub repository Official source code, issue tracker and release notes
Docker Engine documentation for Ubuntu Best practices for installing and maintaining Docker on Ubuntu Linux
Tesseract OCR project Background on the text-recognition engine and language models
AdminDocs: Docker and Docker Compose guide Fundamental container orchestration and hardening on Linux
Portainer CE web GUI Graphical management of Docker containers and stacks on Ubuntu 24.04 LTS

Conclusion

With the setup built here, Ubuntu 24.04 LTS has a full, GDPR-compliant document hub ready. Through strict encapsulation in the Docker container, decoupling via a hardened Nginx reverse proxy and binding to the local loopback adapter, the security architecture stays transparent and low-maintenance.

πŸ’‘ Practical tip for administrators: In Stirling PDF under the admin settings, set user quotas and session timeouts. When employees edit confidential documents in the browser, temporary browser caches should be invalidated automatically after the session ends. Also enable the option for automatic metadata cleanup so that hidden author and printer information is reliably removed before PDF documents are passed on.

That completely removes the need for unsecured third-party web services on the company network, while data sovereignty remains 100 percent on your own server infrastructure.

Share & export

Export as Markdown

Related posts