VPS Tutorials
Deploy Paperless-ngx on a VPS (2026): Docker, OCR, Document Archiving, Full-Text Search and Backup Tutorial
Deploy Paperless-ngx v3.0.5 on a VPS with Docker Compose, and configure Chinese OCR, PostgreSQL, Valkey, document import, tag-based archiving, full-text search, HTTPS, backup and restore, and upgrade troubleshooting.

Deploy Paperless-ngx on a VPS (2026): Docker, OCR, Document Archiving, Full-Text Search and Backup Tutorial
If invoices, contracts, receipts and scanned IDs are scattered across your computer, phone and cloud drive, the real problem is usually not storage space but finding them later. Paperless-ngx can import scanned files to a VPS, run OCR automatically, recognize dates, titles and correspondents, and then manage everything with tags, document types and full-text search.
Its approach differs from an ordinary cloud drive: a cloud drive solves "where the files are stored," while Paperless-ngx is more like a searchable digital filing cabinet. Original files are kept, and the system generates archived copies and an index; you can import documents from the consume directory, through web upload, or via email workflows.
This article uses Paperless-ngx v3.0.5 with the official Docker Compose structure: Paperless-ngx, PostgreSQL 18 and Valkey 9. The focus is on a configuration that can run long-term, including Chinese OCR, HTTPS, directory permissions, database backups, upgrade rollback and troubleshooting. It assumes you manage the VPS yourself and do not expose the management port directly to the whole internet.
Good fits:
- Archiving household invoices, warranty cards, insurance policies and rental contracts;
- Sole proprietors keeping reimbursement receipts, supplier bills and tax materials;
- Small teams sharing project documents while managing them by tags and permissions;
- Continuously collecting PDF, JPEG, PNG and TIFF files from scanners, phones or email;
- Wanting to search the text inside scanned files with OCR instead of manually browsing folders.
Poor fits:
- You only want to sync photos and ordinary files and have no need for OCR at all;
- You need multiple people to edit Office documents in real time;
- You need enterprise-grade e-signatures, complex approvals and compliance audits;
- You have no reliable backups but plan to keep your only original contract on a single VPS.
Paperless-ngx does not replace a backup system. Its media directory, database and consume directory are all important data and must be used together with VPS snapshots, object storage or another machine.
| Option | Main problem it solves | OCR and archiving | Who it suits |
|---|---|---|---|
| Paperless-ngx | Document archiving and retrieval | Native OCR, tags and full-text search | Invoice, contract and scan management |
| Nextcloud | File sync, sharing and collaboration | Requires extra apps and configuration | Multi-device files and team collaboration |
| Ordinary cloud drive | File storage and sharing | Usually relies on the platform's built-in search | People who don't want to maintain a service themselves |
| Git | Text version management | Not suitable for scans and attachments | Code, configuration and plain text |
If you already use Nextcloud, you can treat Paperless-ngx as a document archiving layer rather than building another "all-purpose cloud drive." The two can coexist, but do not let multiple programs write to the same media directory at the same time, or you will easily run into permission problems, duplicate imports and inconsistent indexes.
This article is written against the official stable release verified in August 2026:
Paperless-ngx v3.0.5
PostgreSQL 18
Valkey 9
Docker Compose v2
The service relationships are as follows:
浏览器 / 手机扫描 App
│ HTTPS
▼
反向代理(Nginx Proxy Manager)
│ http://webserver:8000
▼
Paperless-ngx Web + Celery Worker
├── PostgreSQL:用户、标签、文档索引元数据
├── Valkey:任务队列和缓存
└── data/media/consume/export:文件、索引和导入导出目录
Paperless-ngx's OCR and thumbnail processing are background tasks. A successful browser upload does not mean OCR has finished; you need to check the task queue and the consumption log.
Estimate based on document count and OCR frequency first, rather than just CPU cores:
| Scale | Recommended configuration | Notes |
|---|---|---|
| Personal trial, fewer than 1000 documents | 2 cores, 2GB, 40GB SSD | Suitable for low-frequency uploads and a small amount of OCR |
| Household archive, 1000-5000 documents | 2-4 cores, 4GB, 80GB SSD | Smoother OCR, thumbnails and database |
| Small team or high-frequency scanning | 4 cores, 8GB, 160GB+ SSD | Focus on disk I/O and backup windows |
Disk is more likely to become a bottleneck than memory. Original PDFs, archived files, thumbnails, OCR intermediate files and the database all grow together. Do not treat a 20GB disk as a long-term archive; first tally your existing document volume, then reserve at least 30% for upgrades and temporary space.
If the VPS has only 1GB of memory, Paperless-ngx may start, but OCR and PostgreSQL can easily trigger OOM at peak times. Low-memory machines can refer to the VPS low-memory ZRAM and Swap optimization tutorial, but Swap only relieves peaks and cannot replace sufficient memory.
First confirm Docker and Compose:
docker version
docker compose version
If the system has no Docker, refer to the Docker deployment practical guide. Create dedicated directories and give the consume directory to the UID used by the container:
sudo mkdir -p /opt/paperless/data /opt/paperless/media /opt/paperless/consume
sudo mkdir -p /opt/paperless/export /opt/paperless/postgres /opt/paperless/valkey
sudo chown -R "$USER":"$USER" /opt/paperless
sudo chmod 750 /opt/paperless
cd /opt/paperless
The directory responsibilities above are:
- data: application runtime data, search index and task-related files;
- media: already archived original files, thumbnails and document attachments;
- consume: files awaiting import, which Paperless-ngx reads and moves automatically;
- export: temporary directory used for exporting archive packages and for restores;
- postgres: the PostgreSQL database;
- valkey: queue and cache data.
Paperless-ngx's PAPERLESS_SECRET_KEY is used for session signing and security tokens. Do not leave the example value change-me in production, and do not commit .env to a public repository.
python3 -c 'import secrets; print(secrets.token_urlsafe(64))'
Create /opt/paperless/.env:
PAPERLESS_SECRET_KEY=替换为上面生成的长随机字符串
PAPERLESS_URL=https://paperless.example.com
PAPERLESS_TIME_ZONE=Asia/Shanghai
PAPERLESS_OCR_LANGUAGE=chi_sim+eng
PAPERLESS_OCR_LANGUAGES=chi_sim chi_tra
USERMAP_UID=1000
USERMAP_GID=1000
POSTGRES_DB=paperless
POSTGRES_USER=paperless
POSTGRES_PASSWORD=替换为独立的数据库密码
Chinese OCR requires attention to two things: PAPERLESS_OCR_LANGUAGE specifies the primary recognition language, and PAPERLESS_OCR_LANGUAGES is responsible for making the container install additional language packs. Simplified Chinese usually uses chi_sim and Traditional Chinese uses chi_tra; actual recognition quality also depends on scan clarity, fonts and layout.
If documents are mostly English, you can change it to eng to reduce image and runtime overhead. After changing the OCR language, already completed documents will not automatically be re-OCR'd in full; you need to reprocess them in batches.
Restrict the environment file permissions:
chmod 600 /opt/paperless/.env
Create /opt/paperless/compose.yaml:
services:
broker:
image: docker.io/valkey/valkey:9-alpine
container_name: paperless-broker
restart: unless-stopped
volumes:
- ./valkey:/data
db:
image: docker.io/library/postgres:18
container_name: paperless-db
restart: unless-stopped
volumes:
- ./postgres:/var/lib/postgresql
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:3.0.5
container_name: paperless-webserver
restart: unless-stopped
depends_on:
- db
- broker
ports:
- "127.0.0.1:8000:8000"
volumes:
- ./data:/usr/src/paperless/data
- ./media:/usr/src/paperless/media
- ./consume:/usr/src/paperless/consume
- ./export:/usr/src/paperless/export
env_file:
- .env
environment:
PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_DBHOST: db
PAPERLESS_DBENGINE: postgresql
volumes: {}
The current official Compose example mounts PostgreSQL 18 data at /var/lib/postgresql; do not mechanically follow the /var/lib/postgresql/data from older tutorials. The official PostgreSQL image changed its data directory layout during major version upgrades, and a wrong mount path can make you think "the data is already persisted" when in fact the container cannot find the database after being recreated.
Here Paperless-ngx is bound only to 127.0.0.1:8000. Public access is handled by a reverse proxy, and the database and Valkey are not mapped to host ports.
First check the Compose and environment variables:
cd /opt/paperless
docker compose config --quiet
After confirming there is no output, start:
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=150 webserver
The first startup needs to run database migrations, create indexes and initialize tasks; a brief unhealthy status within a few minutes does not necessarily mean failure. Keep watching the logs:
docker compose logs -f --tail=200 webserver
Once you see the web service start listening, test on the VPS itself:
curl -I http://127.0.0.1:8000
If it returns 502, first confirm whether the container is still starting; if it returns connection refused, then check the port mapping and container logs, and do not simply change 8000 to listen publicly.
Enter the web container to create an administrator account:
docker compose exec webserver python manage.py createsuperuser
Enter the email, username and a separate password as prompted. Do not use the VPS root password, and do not write the administrator account into the Compose file.
If you don't have a domain yet, you can access the local port through an SSH tunnel:
ssh -L 8000:127.0.0.1:8000 your_user@your_vps_ip
Then open on your own computer:
http://127.0.0.1:8000
This is suitable for initial setup. For long-term use, configuring a domain and HTTPS is still recommended, because scans, contracts and login credentials should not be transmitted over plaintext HTTP.
If you already have Nginx Proxy Manager, you can add a Proxy Host:
Domain Names: paperless.example.com
Forward Hostname / IP: 127.0.0.1
Forward Port: 8000
Scheme: http
Websockets Support: 开启
Block Common Exploits: 开启
After obtaining a Let's Encrypt certificate, enable HTTPS and force HTTP to redirect to HTTPS. Before deploying, you can read the Nginx Proxy Manager reverse proxy and HTTPS tutorial.
If the reverse proxy runs on the same Docker network, Forward Hostname should use webserver rather than 127.0.0.1. Inside a container, 127.0.0.1 points to the proxy container itself, which is a very common cause of 502.
Paperless-ngx's PAPERLESS_URL must match the publicly accessible HTTPS address:
PAPERLESS_URL=https://paperless.example.com
After modifying .env, recreate the web container:
docker compose up -d --force-recreate webserver
If the origin server should not expose public ports, you can also have Paperless-ngx listen only on the loopback address and provide access through Cloudflare Tunnel or a VPN. For related boundaries, see the Cloudflare Tunnel hide origin IP tutorial.
Paperless-ngx's three common import methods:
- Upload files in the web interface;
- Copy files into the consume directory;
- Deliver automatically via email rules or a scanning program.
First copy a test PDF:
cp ~/Downloads/test-invoice.pdf /opt/paperless/consume/
Check the consumption log:
docker compose logs -f --tail=200 webserver | grep -Ei 'consumer|ocr|document'
After processing completes, the original file enters media storage, and files in the consume directory are usually moved or deleted. Do not treat consume as a long-term backup directory, and do not manually rename files under media.
If the consume directory is never processed, check in order:
ls -la /opt/paperless/consume
stat /opt/paperless/consume/test-invoice.pdf
docker compose exec webserver id
docker compose exec webserver ls -la /usr/src/paperless/consume
The host directory's UID/GID must allow the container to read and move files. USERMAP_UID and USERMAP_GID in env should match the actual host user, which you can confirm with the following command:
id -u
id -g
After changing UID/GID, recreate the service:
docker compose up -d --force-recreate webserver
Paperless-ngx's metadata determines whether you can find files quickly later. It is recommended to first establish a small set of stable categories:
- Document types: invoice, contract, policy, receipt, ID, manual;
- Correspondents: bank, carrier, landlord, supplier, customer;
- Tags: to reimburse, paid, to renew, important, family, work;
- Custom fields: amount, contract expiry date, order number or asset number.
Do not create a new tag for every single file. Tags should express reusable filter conditions, while document types and correspondents describe the source and purpose.
You can configure matching rules in the admin interface so that file names or OCR text automatically fill in metadata. For example, if the file name contains "electricity bill," automatically set the document type to "bill"; attachments from a certain email address automatically set the correspondent and tags.
Validate rules with 5-10 test files before going live. Once automatic rules are too broad, they may misclassify contracts, and you will then have to correct them one by one.
After uploading a scanned PDF, Paperless-ngx runs OCR in the background. OCR failures are usually not caused by Paperless-ngx itself but by:
- Scan resolution too low or the image severely skewed;
- The container lacks the corresponding language pack;
- OCR tasks are queued and not yet finished;
- The file is an encrypted PDF or a corrupted PDF;
- Insufficient memory and the worker was killed by the system.
View recent tasks:
docker compose logs --tail=300 webserver | grep -Ei 'ocr|tesseract|celery|task|error'
docker stats --no-stream
For Chinese and mixed Chinese-English documents, chi_sim+eng is recommended; add chi_tra for Traditional Chinese documents as needed. If search results are still incomplete, first download an original file to confirm whether it actually contains recognizable text, then determine whether it is a scan quality or language configuration issue.
Paperless-ngx's full-text search depends on the index. Newly imported files may need to wait for background tasks; do not re-import immediately on the grounds of "can't find it" right after uploading. Check task status and logs first, then wait for indexing to complete.
You can configure a dedicated inbox address for Paperless-ngx so that bill attachments automatically enter the consumption flow. But email automation expands the attack surface: unknown senders may send oversized attachments, malicious files or large amounts of spam.
Recommended security boundaries:
- Use a separate mailbox and do not write your personal main mailbox password into the container;
- Allow only trusted senders, or forward to a manual review mailbox first;
- Limit attachment size and allowed file types;
- Set up disk monitoring for the consume directory and email tasks;
- Keep a manual review step before automatic rules go live.
If you only archive files occasionally, web upload or manually copying to consume is easier to troubleshoot and requires no extra email connection maintenance.
Paperless-ngx has at least three types of data that must be protected:
- media: original documents, archived PDFs, thumbnails and attachments;
- postgres: users, tags, rules, permissions and document metadata;
- data: search index, task status and application runtime data.
consume and export should also be included in the backup scope, especially files in the consume directory that have not yet been processed. Valkey is mainly queues and cache and usually does not hold long-term source-of-truth data, but backing it up can reduce task loss after a restore.
First stop the web service and worker, then perform the database dump:
cd /opt/paperless
docker compose stop webserver
mkdir -p export/backup-$(date +%F)
docker compose exec -T db pg_dump -U paperless paperless > export/backup-$(date +%F)/paperless.sql
tar -czf export/backup-$(date +%F)/paperless-files.tar.gz data media consume .env compose.yaml
docker compose start webserver
The command above packs sensitive environment files and documents together, so permissions must be restricted:
chmod 600 export/backup-*/paperless.sql
chmod 600 export/backup-*/paperless-files.tar.gz
Copy the backup to another machine or object storage; do not leave it only on the same VPS. For backup strategy, see VPS restic, rclone and database restore drills.
Restoring is not finished once you unpack the tar package. It is recommended to do a drill in a temporary directory or on a spare VPS:
docker compose down
tar -xzf paperless-files.tar.gz -C /opt/paperless
cat paperless.sql | docker compose exec -T db psql -U paperless paperless
docker compose up -d
docker compose logs --tail=200 webserver
Before an actual restore, confirm the database is empty or use a dedicated test instance, to avoid mixing old and new data. After restoring, randomly open a few documents and check search, tags, permissions and OCR attachments, rather than only checking whether the homepage can log in.
Do not use latest in production long-term. Pin the version in Compose first, for example:
webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:3.0.5
broker:
image: docker.io/valkey/valkey:9-alpine
db:
image: docker.io/library/postgres:18
Before upgrading, run:
cd /opt/paperless
docker compose config > export/compose.before-upgrade.yaml
docker compose exec -T db pg_dump -U paperless paperless > export/paperless-before-upgrade.sql
docker compose pull
After confirming the image download is complete, recreate:
docker compose up -d
docker compose ps
docker compose logs --tail=300 webserver
After upgrading, check at least:
- Administrator login and HTTPS redirect;
- Whether new files can be imported and OCR completed;
- Whether Chinese full-text search returns results;
- Whether tags, rules and permissions are preserved;
- Whether database disk usage grows abnormally.
Paperless-ngx v3 has migration notes; read the official migration documentation before crossing major versions. Do not roll back only the application image without restoring the database; the application code and database schema must remain compatible.
First bypass the reverse proxy:
curl -I http://127.0.0.1:8000
docker compose ps
docker compose logs --tail=200 webserver
If local port 8000 works, check the target address in Nginx Proxy Manager. When the proxy container and Paperless-ngx are not on the same network, 127.0.0.1 usually points to the wrong container.
Check the directory mount, UID/GID and worker logs:
docker compose exec webserver ls -la /usr/src/paperless/consume
docker compose exec webserver id
docker compose logs --tail=300 webserver | grep -Ei 'consumer|permission|error'
If the container can see the files but cannot move them, fix the host directory permissions; if the container cannot see the files at all, check the Compose relative paths and the current working directory.
Confirm the environment variables and language packs:
docker compose exec webserver env | grep PAPERLESS_OCR
docker compose exec webserver tesseract --list-langs
If chi_sim is missing, pull or rebuild the image again, and confirm that PAPERLESS_OCR_LANGUAGES uses Tesseract language codes. Already archived files may need OCR to be triggered again; changing environment variables will not automatically rebuild the entire index.
Check the task logs first and confirm the OCR and indexing tasks are complete. For PDFs that already have a text layer, you can download the original file to confirm whether the text exists; for scans, you must wait for OCR.
If only some documents cannot be found, check whether the wrong language pack is used, whether the documents are encrypted, and whether the database and data index directory have write permissions.
Internal Compose connections use the service name db, not localhost:
docker compose ps db
docker compose logs --tail=200 db
docker compose exec webserver env | grep PAPERLESS_DB
Confirm that POSTGRES_DB, POSTGRES_USER and POSTGRES_PASSWORD are consistent in .env and at database initialization. An already initialized PostgreSQL will not automatically change the existing user password just because you modify environment variables.
Check Docker, the media directory, the database and logs:
df -h
du -sh /opt/paperless/*
docker system df
Do not directly delete files under media or postgres. First confirm whether there are duplicate imports, too many thumbnails, failed tasks and logs with no retention limit, then handle them through Paperless-ngx's export, cleanup and retention policies.
- Use a separate random key for .env and set permissions to 600;
- Paperless-ngx listens only on the loopback address or private network and does not expose 8000 directly;
- The reverse proxy enables HTTPS, strong passwords and common attack blocking;
- PostgreSQL and Valkey are not mapped to public ports;
- The consume directory is not writable by all system users;
- Email import accepts only trusted sources and limits attachment size;
- Keep at least one off-site backup copy and run restore drills regularly;
- Pin versions before upgrading, export the database and keep a rollback image;
- Do not commit contracts, IDs and other files to public Git repositories;
- When used by a team, grant least privilege by role;
- Monitor disk, database, OCR queue and container restart counts.
Not entirely. It focuses on document archiving and retrieval, not real-time collaboration, photo sync or large-file sharing platforms. You can let it handle structured documents and leave other files to a cloud drive or object storage.
The import flow moves files from the consume directory into media storage and generates archived versions. Original files remain part of the media data, and the consume directory cannot be treated as a backup; on first deployment, test the flow with copies first.
Not necessarily. Scan clarity, fonts, table layouts, stamps and skew all affect results. OCR is suitable for improving retrieval efficiency and should not be treated directly as the sole source for contract amounts and ID information.
This article uses PostgreSQL 18 according to the current official Compose example. New deployments should stay consistent with the example; when upgrading an existing older instance, read the database major version migration notes first and do not simply change the image tag.
Individual users can start with 2 cores, 2GB, an 80GB SSD, Paperless-ngx v3.0.5 and Chinese OCR. Import ten test files first, confirm that archiving rules, full-text search, HTTPS and backup/restore all work, and then migrate historical materials in bulk.
The value of Paperless-ngx is not in "putting files on a VPS" but in making documents accumulated over years findable by content. As long as you back up media, database, index and keys together, and lock down consume directory permissions and the upgrade process, it can become a stable personal digital archive system.