Install Paperless-ngx on Proxmox VE

Why a Proxmox VM for Paperless-ngx

Paperless-ngx runs a multi-service stack (app server, PostgreSQL, Redis) and CPU-intensive OCR processing. A VM is recommended over an LXC container because OCR processing benefits from dedicated CPU allocation and predictable memory limits. Proxmox’s backup tooling with pre-freeze hooks gives you consistent snapshots of the database without manual intervention.

Prerequisites

  • Proxmox VE 8.x installed and accessible
  • An ISO uploaded to Proxmox storage (Ubuntu 22.04 Server or Debian 12 recommended)
  • At least 4 GB RAM and 4 vCPUs to allocate
  • 20 GB disk for the VM OS and application
  • NAS or network storage for document files (optional but recommended)

Create the VM

Via Proxmox Web UI

  1. Click Create VM
  2. Configure:
    • Name: paperless
    • ISO: Ubuntu 22.04 Server
    • Disk: 20 GB (on SSD storage if available — important for OCR I/O)
    • CPU: 4 cores (2 minimum, but OCR benefits from more)
    • Memory: 4096 MB (2 GB per OCR worker + 1 GB for OS and services)
    • Network: virtio on your bridge

Via CLI

qm create 120 --name paperless --cores 4 --memory 4096 \
  --scsihw virtio-scsi-single --scsi0 local-lvm:20 \
  --cdrom local:iso/ubuntu-22.04.4-live-server-amd64.iso \
  --net0 virtio,bridge=vmbr0 --boot order=scsi0 --ostype l26

Start the VM and complete the Ubuntu installation through the VNC console.

NFS Mount for Document Storage

Storing documents on a NAS via NFS separates data from the VM. If the VM is destroyed or rebuilt, documents remain on the NAS. This also simplifies backup — the NAS handles document backups independently.

Inside the VM:

sudo apt install -y nfs-common
sudo mkdir -p /mnt/documents

# Mount NFS share from your NAS
sudo mount -t nfs nas-ip:/volume1/paperless /mnt/documents

# Auto-mount on boot
echo "nas-ip:/volume1/paperless /mnt/documents nfs defaults,_netdev 0 0" | sudo tee -a /etc/fstab

Create the required subdirectories:

sudo mkdir -p /mnt/documents/{media,consume,export}
sudo chown -R 1000:1000 /mnt/documents

If you do not have a NAS, use local VM storage. The Docker Compose below works with either approach — just change the volume paths.

Install Docker

sudo apt update
sudo apt install -y ca-certificates curl gnupg
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 $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker $USER

Log out and back in.

Docker Compose Configuration

mkdir -p /opt/paperless && cd /opt/paperless

Create docker-compose.yml:

services:
  paperless:
    image: ghcr.io/paperless-ngx/paperless-ngx:2.20.9
    container_name: paperless
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    ports:
      - "8000:8000"
    environment:
      PAPERLESS_DBHOST: db
      PAPERLESS_DBPORT: 5432
      PAPERLESS_DBNAME: paperless
      PAPERLESS_DBUSER: paperless
      PAPERLESS_DBPASS: secure_db_password  # Change this
      PAPERLESS_REDIS: redis://redis:6379
      PAPERLESS_SECRET_KEY: generate-with-openssl-rand-hex-32  # Change this
      PAPERLESS_URL: https://docs.yourdomain.com  # Change to your access URL
      PAPERLESS_ADMIN_USER: admin
      PAPERLESS_ADMIN_PASSWORD: change_this_password  # Change after first login
      PAPERLESS_OCR_LANGUAGE: eng
      PAPERLESS_TIME_ZONE: America/New_York  # Your timezone
      PAPERLESS_TASK_WORKERS: 2            # Match to vCPU count / 2
      PAPERLESS_THREADS_PER_WORKER: 2      # 2 threads per worker
      PAPERLESS_CONSUMER_POLLING: 30
      PAPERLESS_CONSUMER_RECURSIVE: "true"
      PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS: "true"
      USERMAP_UID: 1000
      USERMAP_GID: 1000
    volumes:
      - paperless_data:/usr/src/paperless/data
      - /mnt/documents/media:/usr/src/paperless/media       # NFS or local
      - /mnt/documents/consume:/usr/src/paperless/consume   # Scanner drop folder
      - /mnt/documents/export:/usr/src/paperless/export     # Backup exports
    networks:
      - paperless-net

  db:
    image: postgres:16-alpine
    container_name: paperless-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: paperless
      POSTGRES_PASSWORD: secure_db_password  # Must match PAPERLESS_DBPASS
      POSTGRES_DB: paperless
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "paperless"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - paperless-net

  redis:
    image: redis:7-alpine
    container_name: paperless-redis
    restart: unless-stopped
    volumes:
      - redis_data:/data
    networks:
      - paperless-net

volumes:
  paperless_data:
  postgres_data:
  redis_data:

networks:
  paperless-net:

Start the stack:

docker compose up -d

Monitor startup:

docker compose logs -f paperless

Wait for Listening on 0.0.0.0:8000.

First-Time Setup

Open http://<vm-ip>:8000 and log in with the admin credentials from the environment variables. Change the password immediately.

Upload a test document to verify OCR processing works. With 4 vCPUs and 2 workers, expect 2-5 seconds per page.

Consume Folder via SMB or NFS

SMB Share (for Windows/Mac Scanners)

Inside the VM, set up Samba to expose the consume folder:

sudo apt install -y samba

sudo tee -a /etc/samba/smb.conf > /dev/null <<EOF

[paperless-scan]
   path = /mnt/documents/consume
   browseable = yes
   writable = yes
   valid users = paperless-user
   create mask = 0644
   directory mask = 0755
EOF

sudo useradd -M -s /sbin/nologin paperless-user
sudo smbpasswd -a paperless-user
sudo systemctl restart smbd

Direct NFS Consume

If your scanner can write to NFS (or you have another system pushing files), the NFS-mounted consume folder at /mnt/documents/consume is already accessible from the NAS side. Drop files there from any device with NFS access to your NAS.

Proxmox Backup with Pre-Freeze Script

The challenge with backing up Paperless is PostgreSQL consistency. A snapshot taken mid-transaction can result in a corrupted database. Proxmox supports pre-freeze and post-thaw hooks that run inside the VM during backup.

Set Up the QEMU Guest Agent

Inside the VM:

sudo apt install -y qemu-guest-agent
sudo systemctl enable --now qemu-guest-agent

In Proxmox, enable the QEMU Agent for the VM:

qm set 120 --agent enabled=1

Create the Pre-Freeze Script

Inside the VM, create /etc/paperless-backup-hooks.sh:

#!/bin/bash
# /etc/paperless-backup-hooks.sh
# Called by QEMU guest agent before Proxmox snapshot

ACTION=$1

case "$ACTION" in
  freeze)
    echo "$(date): Pre-freeze -- dumping PostgreSQL" >> /var/log/paperless-backup.log
    docker exec paperless-db pg_dump -U paperless paperless > /opt/paperless/pre-snapshot-dump.sql
    echo "$(date): Pre-freeze complete" >> /var/log/paperless-backup.log
    ;;
  thaw)
    echo "$(date): Post-thaw -- snapshot complete" >> /var/log/paperless-backup.log
    ;;
esac
chmod +x /etc/paperless-backup-hooks.sh

Configure the QEMU guest agent to use these hooks. Create /etc/qemu/fsfreeze-hook.d/paperless.sh:

sudo mkdir -p /etc/qemu/fsfreeze-hook.d
sudo cp /etc/paperless-backup-hooks.sh /etc/qemu/fsfreeze-hook.d/paperless.sh
sudo chmod +x /etc/qemu/fsfreeze-hook.d/paperless.sh

Now Proxmox snapshots automatically dump the database before freezing the filesystem.

Schedule Backups in Proxmox

In the Proxmox web UI:

  1. Go to Datacenter > Backup
  2. Click Add
  3. Set:
    • Node: your node
    • Storage: your backup storage
    • Selection mode: Include
    • VMs: 120 (paperless)
    • Schedule: daily at 02:00
    • Compression: ZSTD
    • Mode: Snapshot

The pre-freeze hook runs automatically, giving you a consistent database dump inside every snapshot.

Resource Monitoring for OCR

OCR processing causes CPU spikes that can affect other VMs on the same host. Monitor from the Proxmox web UI under VM 120 > Summary.

Expected Resource Usage

StateCPURAMDisk I/O
Idle (no processing)<5%~800 MBMinimal
Single document OCR25-50% (2 workers)1.5-2.5 GBModerate reads/writes
Bulk import (queue of 100+ docs)100% sustained2.5-3.5 GBHeavy

Preventing OCR from Starving Other VMs

If OCR spikes interfere with other workloads, set CPU limits on the VM:

# Limit to 200% of one core (2 full cores out of 4 allocated)
qm set 120 --cpulimit 2

Or reduce OCR workers in Docker Compose:

PAPERLESS_TASK_WORKERS: 1

Alerts

Set up Proxmox email alerts for high resource usage. Under Datacenter > Options, configure SMTP settings. Proxmox automatically alerts on sustained high CPU or memory pressure.

Scaling Paperless on Proxmox

If your document volume grows:

  • More OCR throughput: Increase vCPUs and PAPERLESS_TASK_WORKERS proportionally. 1 worker per 2 vCPUs is a good ratio.
  • More storage: Expand the NFS share on your NAS, or add a disk to the VM.
  • More RAM: Each OCR worker uses 500-800 MB during processing. Add 1 GB per additional worker.

To resize the VM without rebuilding:

# Stop VM first
qm set 120 --memory 8192
qm set 120 --cores 8
# Start VM

Troubleshooting

NFS Mount Fails on Boot

Symptom: Docker Compose fails because /mnt/documents is empty — NFS mounted after Docker started.

Fix: Add _netdev and x-systemd.automount to the fstab entry to ensure NFS mounts before Docker:

nas-ip:/volume1/paperless /mnt/documents nfs defaults,_netdev,x-systemd.automount 0 0

Also add depends_on or a systemd override for Docker to wait for the mount:

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/wait-for-nfs.conf > /dev/null <<EOF
[Unit]
After=remote-fs.target
Requires=remote-fs.target
EOF
sudo systemctl daemon-reload

QEMU Guest Agent Not Responding

Symptom: Proxmox shows “QEMU Guest Agent not running” and pre-freeze hooks do not execute.

Fix: Inside the VM:

sudo systemctl status qemu-guest-agent
sudo systemctl enable --now qemu-guest-agent

If the service fails, check the VM hardware config in Proxmox includes the agent serial port (it is added automatically for new VMs but may be missing on older ones).

High CPU Usage Affecting Other VMs

Symptom: Other VMs on the same Proxmox host become sluggish when Paperless is processing documents.

Fix: Apply a CPU limit:

qm set 120 --cpulimit 2

This caps Paperless to 2 cores regardless of how many are allocated. Alternatively, reduce PAPERLESS_TASK_WORKERS to 1.

Database Corruption After Ungraceful Shutdown

Symptom: Paperless fails to start with PostgreSQL errors after a host crash or power loss.

Fix: PostgreSQL’s WAL should recover automatically. If it does not, restore from the pre-freeze dump:

docker compose down
docker volume rm paperless_postgres_data
docker compose up -d db
# Wait for db to initialize
docker exec -i paperless-db psql -U paperless paperless < /opt/paperless/pre-snapshot-dump.sql
docker compose up -d

If no dump exists, restore from the latest Proxmox backup.

Consume Folder Permissions With NFS

Symptom: Files in the consume folder are not processed. Logs show permission errors.

Fix: NFS UID/GID mapping must match the container’s USERMAP_UID/USERMAP_GID (1000 by default). On the NAS, ensure the NFS export allows write access from the VM’s IP and that files are owned by UID 1000:

# On the NAS or NFS server
chown -R 1000:1000 /volume1/paperless/consume

Some NAS systems (Synology, TrueNAS) have “squash” settings that remap UIDs — set squash to no_root_squash or map to the correct UID.

Resource Requirements

  • VM allocation: 4 vCPU, 4 GB RAM, 20 GB disk (plus NFS for documents)
  • Actual usage: ~800 MB idle, 2.5-3.5 GB during OCR with 2 workers
  • Storage: Plan 1-5 MB per document page (original + OCR archive)

Comments