
Install Paperless-ngx on Proxmox LXC (2026)

Prerequisites
- •Proxmox VE 9 host
Tools
- •SSH terminal
- •Web browser
- •nano
Software
- •debian — 13
- •python — 3.13
- •postgresql — 17
- •proxmox-ve — 9
- •paperless-ngx — 2.20.14
Paperless-ngx turns every scrap of paper in your house into a searchable online archive. Scan a document, drop the PDF in a folder, and a few seconds later it is OCR'd, tagged, and indexed. You can search for "electric bill 2023" and the right PDF comes back.
This guide installs Paperless-ngx bare-metal inside an unprivileged Debian 13 LXC container on Proxmox VE 9. No Docker, no helper scripts. We use PostgreSQL for the database, Redis for the task queue, and systemd for process supervision.
Why not the popular Proxmox community helper script? It works fine — the maintainers patch Debian breakage fast, and the current version installs cleanly on Debian 13. We do bare-metal anyway: when something breaks two upgrades from now, you want an install you wrote and understand, not someone else's bash you have to reverse-engineer. Bare-metal also makes it easy to swap components later — a different Python version, a different database, custom OCR languages. The end result is the same shape: a dedicated LXC you can back up, snapshot, and monitor.
What You Will End Up With
- An unprivileged Debian 13 LXC running Paperless-ngx 2.20.14
- PostgreSQL 17 and Redis running alongside it in the same container
- Four systemd services managing the webserver, document consumer, task queue, and scheduler
- Paperless reachable on the LXC at
http://<your-CT-IP>:8000
This guide also includes a short bonus section at the end covering our standard homelab wiring — a Pi-hole local DNS record, a Caddy reverse proxy with valid HTTPS, a PBS backup job, and an Uptime Kuma monitor. Skip the bonus if you do not run those services; the core install is fully functional on its own.
For this guide the Paperless LXC takes CT ID 103 and IP 10.1.20.103. Substitute your own.
NOTE
About 25 to 30 minutes of hands-on time. Most of it is waiting for pip install and apt install to finish.
Step 1: Create the Paperless LXC Container
We create an unprivileged Debian 13 container with nesting=1 and keyctl=1. Nesting is required so systemd inside the container can manage its own cgroups. Keyctl is needed for services that use the kernel keyring — which includes any modern systemd unit.
On the Proxmox host, confirm the Debian 13 template is already downloaded:
pveam list localYou should see debian-13-standard_13.1-2_amd64.tar.zst. If it is missing, run pveam update && pveam download local debian-13-standard_13.1-2_amd64.tar.zst first.
Now create the container.
pct create 103 local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst \
--hostname paperless \
--cores 2 \
--memory 4096 \
--swap 1024 \
--rootfs local-lvm:20 \
--net0 name=eth0,bridge=vmbr0,ip=10.1.20.103/24,gw=10.1.20.1 \
--nameserver 10.1.20.100 \
--unprivileged 1 \
--features nesting=1,keyctl=1 \
--onboot 1 \
--password| Flag | What it does |
|---|---|
103 (positional) | Container VMID. Replace with the next free ID on your Proxmox host. The studio convention is LXCs in 100-199. |
local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst | OS template path. Keep — every tutorial in this series uses Debian 13. |
--hostname paperless | Container hostname. Keep or rename to taste — nothing else in this guide depends on the name. |
--cores 2 | CPU cores. Keep — sized for comfortable OCR bursts on a family-scale install. |
--memory 4096 | RAM in MB. Keep. |
--swap 1024 | Swap in MB. Keep — safety net for OCR memory spikes. |
--rootfs local-lvm:20 | Root filesystem at 20 GB on the local-lvm thinpool. Replace local-lvm if your storage pool is named differently (for example local-zfs). |
--net0 name=eth0,bridge=vmbr0,ip=10.1.20.103/24,gw=10.1.20.1 | Network attachment. Replace bridge, ip, and gw with your environment's values. |
--nameserver 10.1.20.100 | DNS server. Replace with your local DNS resolver (Pi-hole if you run one, otherwise your gateway or any public resolver). |
--unprivileged 1 | Unprivileged container. Keep — security default. |
--features nesting=1,keyctl=1 | Enables systemd cgroup management (nesting) and the kernel keyring (keyctl). Keep — Paperless's systemd services need both. |
--onboot 1 | Auto-start on host boot. Keep. |
--password | Prompts for a root password for the container during creation. Keep. |
You will be prompted for a root password for the container. Set something memorable — you will need it if you ever console into the CT.
The resource choices are sized for a real homelab user with a family's worth of documents. 2 cores handles OCR bursts comfortably, 4 GB of RAM leaves headroom without being wasteful, and 20 GB of disk fits the install plus years of typical document accumulation. You can grow any of these later.
Start the container:
pct start 103Step 2: Enter the Container, Fix Locales, and Update
From here on, everything happens inside the container unless a step says otherwise.
On the Proxmox host, drop into a shell inside CT 103:
pct enter 103Your prompt should now say root@paperless. Refresh the package list.
apt updateThe minimal Debian 13 LXC template ships without any locales generated, but the shell inherits LANG=en_US.UTF-8 from the host. The mismatch causes every later command (apt, perl, python, dpkg triggers) to print noisy Setting locale failed warnings. Three quick commands generate en_US.UTF-8 once and the rest of the install runs cleanly.
apt install -y localessed -i 's/^# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.genlocale-genNow upgrade any packages that have moved since the template was built.
apt upgrade -ySet the LXC's system timezone. This is what journalctl timestamps, date, and anything reading /etc/localtime use — useful when you're debugging the container later. (Paperless has its own internal timezone setting we configure in Step 9. The two are independent: Paperless ignores the system TZ, and the system TZ doesn't affect Paperless's UI. You want both correct.)
timedatectl set-timezone America/New_YorkSubstitute your own zone — timedatectl list-timezones will print the full list.
Step 3: Install System Dependencies
Paperless-ngx is a Python web app, but the OCR pipeline leans on a pile of system libraries: Tesseract for text extraction, Ghostscript for PDF manipulation, ImageMagick for image conversion, and so on. We also need curl for downloading the release tarball — the minimal Debian 13 template does not include it.
NOTE
Older Paperless docs include liblept5 in the dependency list. Leptonica was bumped to version 6 upstream, and Debian 13 ships only the new libleptonica6 package. We omit it from our apt install line entirely — tesseract-ocr pulls in libleptonica6 as a transitive dependency, so the OCR pipeline gets what it needs without us specifying anything.
Install the Python build tools, PostgreSQL client libraries, ImageMagick, file-handling utilities, and curl.
apt install -y python3-pip python3-dev python3-venv \
imagemagick fonts-liberation libpq-dev \
default-libmysqlclient-dev pkg-config libmagic-dev \
poppler-utils curlInstall the OCR toolchain. (ghostscript is omitted here because imagemagick already pulled it in transitively above.) The base tesseract-ocr package ships English language data automatically, so no separate language pack is needed for English.
apt install -y unpaper icc-profiles-free qpdf pngquant tesseract-ocrTIP
If you scan documents in other languages, install the matching language packs after the base install (e.g. apt install -y tesseract-ocr-deu tesseract-ocr-fra tesseract-ocr-spa) and update PAPERLESS_OCR_LANGUAGE in /etc/paperless.conf to a +-separated list like eng+deu+fra. Run apt-cache search tesseract-ocr- to see every available pack.
NOTE
Some older Paperless guides include a step to edit /etc/ImageMagick-6/policy.xml to allow PDF processing. On Debian 13 that file does not exist (the package is now ImageMagick 7), and the default policy already allows PDF — no edit required.
Step 4: Install and Configure PostgreSQL
Debian 13 ships PostgreSQL 17 in the default repos. Install it.
apt install -y postgresqlThe package starts PostgreSQL automatically. Confirm it is running:
systemctl status postgresqlNow create a database and a user for Paperless. We run those commands as the postgres system user using runuser. (runuser is the right tool when you are already root and just need to drop into another user — no sudo install required.)
First, generate a random password for the database user. Copy this value to a safe place — you will paste it into paperless.conf in a moment.
openssl rand -base64 24Create the paperless database user. It will prompt you for the password — paste the value you just generated when asked.
runuser -u postgres -- createuser --pwprompt paperlessCreate the paperless database, owned by the paperless user. We pin UTF-8 encoding explicitly. On a fresh Debian 13 LXC, the PostgreSQL cluster initializes itself with SQL_ASCII encoding because apt install postgresql runs pg_createcluster before any locale variables are exported in the install shell. SQL_ASCII silently accepts bytes but rejects unicode escape sequences on insert — which means the moment Paperless tries to store OCR'd text containing any non-ASCII character (or even a smart quote in a filename), it dies with unsupported Unicode escape sequence. We sidestep the cluster's broken default by creating the database from template0 with explicit UTF-8 encoding and the always-available C.UTF-8 locale.
runuser -u postgres -- createdb --owner=paperless --encoding=UTF8 --locale=C.UTF-8 --template=template0 paperless| Flag | What it does |
|---|---|
--owner=paperless | The paperless user owns the new database. Keep. |
--encoding=UTF8 | Forces UTF-8 encoding regardless of cluster default. Keep — this is the bug fix. |
--locale=C.UTF-8 | UTF-8-aware C locale. Always available on glibc-based systems (no locale-gen required). Keep. |
--template=template0 | Required when overriding encoding/locale — template1 inherits the cluster's broken default and would block the override. Keep. |
Verify the connection works. When prompted, enter the password you set above.
psql -h localhost -U paperless -d paperless -c '\conninfo'You should see something like You are connected to database "paperless" as user "paperless" on host "localhost".
Step 5: Install Redis
Redis is the message broker for Celery, which runs Paperless's background tasks (OCR, consumer, scheduled jobs).
apt install -y redis-serverRedis starts automatically on install. Confirm:
systemctl status redis-serverTest it responds:
redis-cli pingYou should see PONG.
Step 6: Create the Paperless System User
Paperless runs as a dedicated system user with its home directory at /opt/paperless. The --system flag gives it a UID below 1000 and no login shell by default.
adduser paperless --system --home /opt/paperless --group| Flag | What it does |
|---|---|
paperless (positional) | Username. Keep — the systemd units and file ownership throughout the guide all reference this name. |
--system | Creates a system user (UID below 1000, no login shell by default). Keep. |
--home /opt/paperless | Sets the home directory. Keep — every later step uses this path. |
--group | Also creates a matching paperless group. Keep. |
Step 7: Download and Extract Paperless-ngx
We pin a specific version rather than tracking main. Pinning makes the install reproducible and gives you control over when to upgrade.
Set a version variable at the top of the shell so later commands can use it:
export PAPERLESS_VERSION=2.20.14TIP
To find the latest stable version, open github.com/paperless-ngx/paperless-ngx/releases in a browser and read the top entry. Do not take a pre-release unless you know what you are doing.
Move into the paperless home directory so downloads and extraction land there.
cd /opt/paperlessDownload the release tarball. We run curl as the paperless user so the downloaded file ends up owned correctly.
runuser -u paperless -- curl -L -O \
https://github.com/paperless-ngx/paperless-ngx/releases/download/v${PAPERLESS_VERSION}/paperless-ngx-v${PAPERLESS_VERSION}.tar.xzExtract the archive. It creates a paperless-ngx/ subdirectory with all the source, config, and scripts inside.
runuser -u paperless -- tar -xf paperless-ngx-v${PAPERLESS_VERSION}.tar.xzMove the extracted contents up one level so the source lives directly at /opt/paperless/src, /opt/paperless/requirements.txt, and so on. The shopt -s dotglob pulls dotfiles too.
runuser -u paperless -- bash -c "shopt -s dotglob && mv paperless-ngx/* . && rmdir paperless-ngx"Clean up the tarball:
rm paperless-ngx-v${PAPERLESS_VERSION}.tar.xzStep 8: Create a Python Virtual Environment and Install Dependencies
We build an isolated Python 3.13 environment for Paperless so its dependencies do not collide with system packages.
Create the virtual environment at /opt/paperless/.venv:
runuser -u paperless -- python3 -m venv /opt/paperless/.venvUpgrade pip inside the venv so we get recent dependency resolution behavior.
runuser -u paperless -- /opt/paperless/.venv/bin/pip install --upgrade pipInstall the Python dependencies from the requirements.txt shipped with the release. This takes a few minutes — it compiles some native extensions.
runuser -u paperless -- /opt/paperless/.venv/bin/pip install -r /opt/paperless/requirements.txtIf this step fails with a compilation error, the most common cause is a missing system dev package. Re-read the error and install whatever library it names via apt install -y <name>-dev.
Step 9: Configure Paperless
Paperless reads its configuration from /etc/paperless.conf. We will write that file from scratch with just the settings our install needs. (The release tarball ships an example at /opt/paperless/paperless.conf if you want to browse every available setting later — it's a wall of commented-out defaults.)
First generate a Django secret key. This is what Django uses to sign session cookies — treat it like a password.
openssl rand -base64 64 | tr -d '\n'; echoThe tr -d '\n'; echo part strips base64's 76-char line wrap so the key prints on a single line — Paperless reads paperless.conf as KEY=value lines, and a wrapped value would silently break things. Copy the output somewhere safe, just like you did with the database password. Now open the config for editing.
nano /etc/paperless.confPaste the following, then edit the values marked REPLACE below:
REPLACE_ME_SECRET_KEY→ your generated secret keyREPLACE_ME_DB_PASSWORD→ your PostgreSQL password10.1.20.103(inPAPERLESS_URLandPAPERLESS_ALLOWED_HOSTS) → your CT's IP addressAmerica/New_York→ your timezone, if different
# Required services
PAPERLESS_REDIS=redis://localhost:6379
PAPERLESS_DBENGINE=postgresql
PAPERLESS_DBHOST=localhost
PAPERLESS_DBPORT=5432
PAPERLESS_DBNAME=paperless
PAPERLESS_DBUSER=paperless
PAPERLESS_DBPASS=REPLACE_ME_DB_PASSWORD
# Security
PAPERLESS_SECRET_KEY=REPLACE_ME_SECRET_KEY
PAPERLESS_URL=http://10.1.20.103:8000
PAPERLESS_ALLOWED_HOSTS=10.1.20.103,localhost
# OCR
PAPERLESS_OCR_LANGUAGE=eng
PAPERLESS_OCR_MODE=skip
# Consumer
PAPERLESS_CONSUMER_RECURSIVE=true
PAPERLESS_CONSUMER_POLLING=10
# Workers
PAPERLESS_TASK_WORKERS=2
PAPERLESS_THREADS_PER_WORKER=1
# Timezone
PAPERLESS_TIME_ZONE=America/New_YorkSave with Ctrl+X, Y, Enter.
NOTE
If you complete the bonus Caddy step at the end of this guide, you will come back and update PAPERLESS_URL and PAPERLESS_ALLOWED_HOSTS to add the public hostname. For now the IP-only values are correct for the core install.
Lock down the file so only root and the paperless user can read the password.
chown root:paperless /etc/paperless.confchmod 640 /etc/paperless.confStep 10: Create the Runtime Directories
Paperless needs three writable directories at runtime — the consume folder it watches for new uploads, the media folder where stored documents live, and the data folder that holds the Whoosh search index. The migrate step in the next section refuses to run if these do not exist, so we create them now.
mkdir -p /opt/paperless/consume /opt/paperless/media /opt/paperless/dataSet ownership so the paperless user can write to them.
chown -R paperless:paperless /opt/paperless/consume /opt/paperless/media /opt/paperless/dataStep 11: Initialize the Database and Create an Admin User
Before we can run Django's migration command, we need to load the config file we just wrote into the current shell. Without this, manage.py would read no PAPERLESS_DB* env vars, fall back to the SQLite default, and create the schema in the wrong database — only to have the systemd services in Step 12 connect to the (still empty) PostgreSQL and 500 on every request. The systemd EnvironmentFile= directive only applies to services launched by systemd, not to one-shot commands we run by hand.
set -a; source /etc/paperless.conf; set +aset -a tells bash to export every variable it sees from this point forward; sourcing the conf brings in PAPERLESS_DBHOST, PAPERLESS_DBPASS, etc; set +a restores normal behavior. Run env | grep PAPERLESS_DB if you want to confirm the values are present.
Move into the source directory:
cd /opt/paperless/srcRun the migration. The --preserve-environment flag tells runuser to carry the env vars we just loaded into the paperless user's shell, instead of starting it with a clean environment.
runuser -u paperless --preserve-environment -- /opt/paperless/.venv/bin/python3 manage.py migrateYou should see a long stream of Applying ... OK lines ending with Applying auditlog.xxxx... OK. If you instead see migrations applying to a SQLite file under /opt/paperless/data/, the env vars were not loaded — go back, re-run the source line, and try again.
Create the admin user you will log in with. The script prompts for a username, email, and password.
runuser -u paperless --preserve-environment -- /opt/paperless/.venv/bin/python3 manage.py createsuperuserStep 12: Install Systemd Services
Paperless runs as four separate systemd services: the webserver, the document consumer (watches the consume directory), the task queue (Celery workers that do OCR), and the scheduler (Celery beat for periodic tasks like email checking).
The release tarball ships example unit files. Each of ours has two pieces tailored to this install: an EnvironmentFile=/etc/paperless.conf line so the service inherits the config we wrote in Step 9 (without it, Paperless silently falls back to defaults — wrong database, empty CSRF allow-list, default secret key), and ExecStart lines that use our venv's python3 and celery instead of the system ones.
Create the webserver unit:
nano /etc/systemd/system/paperless-webserver.servicePaste the following, then save with Ctrl+X, Y, Enter.
[Unit]
Description=Paperless webserver
After=network.target postgresql.service redis-server.service
Wants=network.target
Requires=redis-server.service postgresql.service
[Service]
EnvironmentFile=/etc/paperless.conf
User=paperless
Group=paperless
WorkingDirectory=/opt/paperless/src
Environment=GRANIAN_HOST=0.0.0.0
Environment=GRANIAN_PORT=8000
Environment=GRANIAN_WORKERS=2
ExecStart=/opt/paperless/.venv/bin/granian --interface asginl --ws paperless.asgi:application
Restart=on-failure
[Install]
WantedBy=multi-user.targetCreate the document consumer unit:
nano /etc/systemd/system/paperless-consumer.servicePaste, then save with Ctrl+X, Y, Enter.
[Unit]
Description=Paperless consumer
Requires=redis-server.service postgresql.service
After=redis-server.service postgresql.service
[Service]
EnvironmentFile=/etc/paperless.conf
User=paperless
Group=paperless
WorkingDirectory=/opt/paperless/src
ExecStart=/opt/paperless/.venv/bin/python3 manage.py document_consumer
Restart=on-failure
[Install]
WantedBy=multi-user.targetCreate the task queue unit (Celery workers):
nano /etc/systemd/system/paperless-task-queue.servicePaste, then save with Ctrl+X, Y, Enter.
[Unit]
Description=Paperless Celery Workers
Requires=redis-server.service postgresql.service
After=redis-server.service postgresql.service
[Service]
EnvironmentFile=/etc/paperless.conf
User=paperless
Group=paperless
WorkingDirectory=/opt/paperless/src
ExecStart=/opt/paperless/.venv/bin/celery --app paperless worker --loglevel INFO
Restart=on-failure
[Install]
WantedBy=multi-user.targetCreate the scheduler unit (Celery beat):
nano /etc/systemd/system/paperless-scheduler.servicePaste, then save with Ctrl+X, Y, Enter.
[Unit]
Description=Paperless Celery Beat
Requires=redis-server.service postgresql.service
After=redis-server.service postgresql.service
[Service]
EnvironmentFile=/etc/paperless.conf
User=paperless
Group=paperless
WorkingDirectory=/opt/paperless/src
ExecStart=/opt/paperless/.venv/bin/celery --app paperless beat --loglevel INFO
Restart=on-failure
[Install]
WantedBy=multi-user.targetReload systemd so it sees the new units:
systemctl daemon-reloadEnable and start all four at once:
systemctl enable --now paperless-webserver paperless-consumer paperless-task-queue paperless-schedulerStep 13: Verify Paperless is Running
Check the status of each service:
systemctl status paperless-webserver paperless-consumer paperless-task-queue paperless-scheduler --no-pagerAll four should show Active: active (running). If any say failed, check its logs with journalctl -u <unit-name> --no-pager -n 50.
Test the webserver responds on localhost:
curl -I http://localhost:8000/accounts/login/You should see HTTP/1.1 200 OK.
Step 14: Upload Your First Document
Open Paperless in a browser at http://10.1.20.103:8000 (substitute your CT's IP). Log in with the superuser credentials you created in Step 11.
Click the + Add Document button in the upper right, or drag a PDF file onto the browser window. Pick anything — an old bill, a warranty card, a PDF manual.
The document appears in the dashboard with a spinning indicator. Behind the scenes, the Celery task queue is running Tesseract OCR on every page and storing the resulting text in PostgreSQL. For a typical 2-page bill this takes 5-15 seconds on the resources we allocated.
When OCR completes, click the document. You will see the extracted text on the right side, and you can now search for any phrase in the document via the search bar at the top.
If that works: congratulations, you have a fully wired Paperless-ngx install.
Standard Homelab Wiring (Optional Bonus)
The remaining steps wire Paperless into the homelab stack we use across this series — local DNS, real HTTPS via Caddy, scheduled backups to PBS, and uptime monitoring. Skip this section if you do not run those services — Paperless is fully usable at http://10.1.20.103:8000 as it stands.
In our setup the supporting services live at:
| Service | IP | Hostname |
|---|---|---|
| Proxmox host | 10.1.20.10 | pve.hake.rodeo |
| Pi-hole | 10.1.20.100 | pihole.hake.rodeo |
| Caddy | 10.1.20.101 | (proxies the others) |
| Uptime Kuma | 10.1.20.102 | uptime.hake.rodeo |
| PBS | 10.1.20.200 | pbs.hake.rodeo |
Substitute your own IPs and domain throughout the bonus steps.
Pi-hole Local DNS Record
We want paperless.hake.rodeo to resolve to the Caddy LXC so Caddy can terminate TLS and proxy to the Paperless backend.
Open your Pi-hole admin UI in a browser, log in, then click Local DNS Records in the left sidebar.
In the form at the top:
- Domain:
paperless.hake.rodeo - IP Address:
10.1.20.101(your Caddy LXC)
Click Add. The record appears in the list below. No restart is required — Pi-hole picks up the change immediately.
Verify resolution from inside your network:
dig +short paperless.hake.rodeo @10.1.20.100You should see 10.1.20.101.
Update Paperless's Allowed Hosts
We update Paperless's config before wiring up Caddy so that the moment the proxy goes live, Paperless is already willing to accept requests for the public hostname. Django will reject any request whose Host header is not in ALLOWED_HOSTS, and reject any login POST whose Origin is not in CSRF_TRUSTED_ORIGINS — both of which Paperless derives from PAPERLESS_URL.
Inside the Paperless container (re-enter with pct enter 103 if you have exited it), open the config:
nano /etc/paperless.confChange the two security lines to the public hostname:
PAPERLESS_URL=https://paperless.hake.rodeo
PAPERLESS_ALLOWED_HOSTS=paperless.hake.rodeo,10.1.20.103,localhostSave with Ctrl+X, Y, Enter, then restart the webserver so it picks up the new config:
systemctl restart paperless-webserverPaperless is now ready for paperless.hake.rodeo even though no requests will arrive under that name yet. Caddy is next.
Caddy Reverse Proxy Block
Exit the Paperless container, then on the Proxmox host enter the Caddy container:
exitpct enter 101Open the Caddyfile:
nano /etc/caddy/CaddyfileAdd the following block at the end of the file. The Cloudflare API token is already loaded from /etc/caddy/caddy.env for the other site blocks, so the same reference works here.
paperless.hake.rodeo {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
reverse_proxy 10.1.20.103:8000
}Save with Ctrl+X, Y, Enter, then reload Caddy. Reload is graceful — it does not drop existing connections.
systemctl reload caddyOpen https://paperless.hake.rodeo in a browser. You should see the Paperless login page with a valid HTTPS certificate, and login should succeed because we configured the trusted hostname before bringing the proxy online.
PBS Backup Job
If you already have a Proxmox Backup Server job running on this host, just fold Paperless into it. In the PVE web UI go to Datacenter → Backup, double-click the existing job, and tick 103 (paperless) in the VM selection list. Save. Optionally select the job and click Run Now to immediately back up the new container without waiting for the next scheduled run.
If you do not have a PBS job set up yet, follow our Proxmox Backup Server setup guide first — that gets you a working backup target, retention policy, and verify schedule, all of which Paperless can ride on the moment it exists.
Uptime Kuma Monitors
We add two monitors so that when something goes wrong, the dashboard tells us what is wrong. A ping monitor against the LXC IP catches the container being down (host reboot, OOM, networking gone). An HTTP monitor against the public URL catches Paperless itself crashing while the LXC is still up. If both go red together the LXC is gone; if only HTTP goes red, the container is fine and the service died.
Open Uptime Kuma and click + Add New Monitor. Configure the first as a ping to the LXC:
- Monitor Type: Ping
- Friendly Name: Paperless LXC
- Hostname:
10.1.20.103 - Heartbeat Interval: 60 seconds
- Retries: 3
Click Save, then click + Add New Monitor again for the service-level check:
- Monitor Type: HTTP(s)
- Friendly Name: Paperless service
- URL:
https://paperless.hake.rodeo/accounts/login/ - Heartbeat Interval: 60 seconds
- Retries: 3
Expand Advanced and check Accept Status Codes 200-299. Paperless returns 200 on the login page even when unauthenticated, which is the cheapest health signal we can hit without setting up authentication.
Click Save. Both monitors appear in your dashboard — green blocks mean healthy.
Next Steps
Now that you have Paperless running you can explore the features it ships with:
- Tags and correspondents. Train Paperless on your filing conventions by creating tags and correspondents, then auto-apply them via matching rules under Settings → Automatic matching.
- Email intake. Paperless can watch an IMAP inbox and ingest attachments automatically. That is a separate tutorial.
- Mobile upload. The iOS and Android apps scan documents with your phone camera and upload straight to the consume folder. Both are open source on the Paperless-ngx GitHub organization.
- AI-assisted tagging and OCR. Paperless-GPT is a companion service that talks to your Paperless API and uses an LLM (OpenAI, Ollama, or any compatible endpoint) to auto-generate titles, suggest tags and correspondents, and run vision-LLM OCR — much better than Tesseract on handwriting or phone-camera scans. Worth setting up once your collection is large enough that hand-tagging gets tedious. We will cover the full install in its own guide.
- More OCR languages. If you scan mixed-language documents, install the language packs (
apt install -y tesseract-ocr-<code>) and updatePAPERLESS_OCR_LANGUAGEin/etc/paperless.confto a+-separated list likeeng+deu+fra, thensystemctl restart paperless-task-queue.
When Paperless releases a new version you will upgrade by stopping the services, downloading the new tarball, replacing the source files, running pip install -r requirements.txt again, running manage.py migrate, and restarting the services. We will cover that in a follow-up.
.png)

