<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Paperless – Serverküche</title><link>https://serverkueche.de/en/tags/paperless/</link><description>Paperless – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>en-US</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Mon, 20 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/paperless/index.xml" rel="self" type="application/rss+xml"/><item><title>Self-hosting Paperless-ngx: the paperless office with OCR</title><link>https://serverkueche.de/en/tutorials/self-host-paperless-ngx/</link><pubDate>Mon, 20 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/self-host-paperless-ngx/</guid><description>Set up Paperless-ngx with Docker behind Traefik: archive documents searchably via OCR – with full-text search, tags and automatic import.</description><content:encoded><![CDATA[<p>Invoices, contracts, official mail – the paper pile grows and grows, and in the end you
never find anything anyway. <strong>Paperless-ngx</strong> turns it into a searchable digital archive:
you throw a document in, it&rsquo;s recognized via <strong>OCR</strong>, tagged, and back in seconds via the
full-text search. In this recipe we set it up behind Traefik.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end, <strong>Paperless-ngx 2.20</strong> runs behind your Traefik proxy, reachable at
<code>https://paperless.YOUR_DOMAIN</code> with HTTPS. Paperless is a document management system: you
feed it scans or PDFs, it reads out the text via <strong>OCR</strong> (even from pure image scans),
recognizes date and content, and stores everything searchably. Via tags, correspondents and
document types you bring order into it; the full-text search later finds every document.</p>
<p>The clever part is the <strong>consume folder</strong>: everything you place there (e.g. from a network
scanner) is imported and processed automatically. Paperless consists of <strong>five containers</strong> –
the web server, a PostgreSQL database, a Redis broker for background processing, plus
<strong>Gotenberg</strong> and <strong>Tika</strong>, which convert office documents into PDF. The official template
provides the complete team.</p>
<p>The gain over a folder full of PDFs on the disk: Paperless makes every document
<strong>searchable</strong> (even scanned paper), keeps the original and archive version cleanly
separated, and can be automated via rules. Your documents stay on your server – no cloud
service reads along, and you&rsquo;re not tied to a proprietary format.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A running <strong>Traefik reverse proxy</strong> with the <code>proxy</code> network and the resolver <code>le</code> – see
<a href="/en/tutorials/traefik-reverse-proxy/">reverse proxy with Traefik</a>.</li>
<li>A subdomain <code>paperless.YOUR_DOMAIN</code> with a DNS record to your server IP – see
<a href="/en/tutorials/connect-domain-to-server/">connecting a domain to your server</a>.</li>
<li>A <strong>backup</strong>. Your most important documents land in Paperless – first set up
<a href="/en/tutorials/restic-backups/">backups with Restic</a>.</li>
</ul>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>How big does the server need to be?
  </p>
  <div class="prose-kitchen text-sm">Paperless itself is frugal, but it&rsquo;s five containers, and the <strong>OCR processing is
CPU-heavy</strong>: when recognizing a scan, a core runs at full tilt for a few seconds to minutes.
For private use, <strong>2 vCPU and 4 GB RAM</strong> are quite enough; the tested VPS 1000 (4 vCore /
8 GB) has plenty of headroom. During bulk imports of many documents you notice the CPU load –
then Paperless just works through the queue bit by bit.</div>
</div>
<p>Not sure which server size is enough? The <a href="/en/server-calculator/">server calculator</a>
calculates the RAM and CPU needs for your services.</p>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Recommendation</span>
    <span title="Links marked with * are affiliate links.">Ad</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 1000 G12</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">4 vCores · 8 GB RAM · 256 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">from €10.36/month</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">The VPS 1000 handles Paperless including OCR with ease.</p>
    </div>
    <a href="https://www.netcup.com/en/server/vps/vps-1000-g12-iv-12m?ref=44083" rel="sponsored noopener" target="_blank"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Go to netcup →
</a>

  </div><div class="px-4 pb-4"><p class="not-prose my-3 flex flex-wrap items-center gap-x-2 gap-y-1 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200">
  <span>💶 <strong>5 € voucher</strong> for new netcup customers:</span>
  <code class="rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</code>
  <span class="text-xs text-slate-500 dark:text-slate-400">(new customers only, no domains)</span>
</p></div>
</div>

<h2 id="step-by-step">Step by step</h2>
<h3 id="step-1-create-the-dns-record">Step 1: Create the DNS record</h3>
<p>Create <code>paperless.YOUR_DOMAIN</code> (A/AAAA to your server IP) and check:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dig +short paperless.YOUR_DOMAIN</span></span></code></pre></div>
</div>
<p>Your server IP must come back – otherwise Traefik won&rsquo;t fetch a certificate later.</p>
<h3 id="step-2-generate-a-secret-key">Step 2: Generate a secret key</h3>
<p>Paperless encrypts sessions with a secret key. The default is <strong>publicly known</strong> – a real
risk for an instance on the internet. Generate your own:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">head -c <span class="m">50</span> /dev/urandom <span class="p">|</span> base64</span></span></code></pre></div>
</div>
<p>Copy the output – it goes into the configuration as <code>PAPERLESS_SECRET_KEY</code> shortly.</p>
<h3 id="step-3-create-the-composeyaml">Step 3: Create the compose.yaml</h3>
<p>Create the project – <strong>including</strong> the two bind-mount folders:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/paperless/<span class="o">{</span>consume,export<span class="o">}</span> <span class="o">&amp;&amp;</span> <span class="nb">cd</span> ~/paperless</span></span></code></pre></div>
</div>
<p>You deliberately create the subfolders <code>consume</code> and <code>export</code> <strong>now, as a normal user</strong>: if
the Docker daemon created them at startup, they would belong to <code>root</code> – then neither you
(<code>cp</code> into the consume folder) nor Paperless itself (runs via <code>USERMAP_UID</code> as UID 1000)
could write into them.</p>
<p>Create <code>compose.yaml</code>. Replace <code>paperless.YOUR_DOMAIN</code>, the passwords and the
<code>PAPERLESS_SECRET_KEY</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">paperless</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">broker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/library/redis:8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">redisdata:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">default]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/library/postgres:18</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_DB</span><span class="p">:</span><span class="w"> </span><span class="l">paperless</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_USER</span><span class="p">:</span><span class="w"> </span><span class="l">paperless</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">A_STRONG_DB_PASSWORD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">pgdata:/var/lib/postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">default]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">gotenberg</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/gotenberg/gotenberg:8.34</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;gotenberg&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--chromium-disable-javascript=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--chromium-allow-list=file:///tmp/.*&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">default]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tika</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/apache/tika:3.3.1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">default]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">webserver</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/paperless-ngx/paperless-ngx:2.20.15</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">db, broker, gotenberg, tika]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_REDIS</span><span class="p">:</span><span class="w"> </span><span class="l">redis://broker:6379</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_DBHOST</span><span class="p">:</span><span class="w"> </span><span class="l">db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_DBUSER</span><span class="p">:</span><span class="w"> </span><span class="l">paperless</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_DBPASS</span><span class="p">:</span><span class="w"> </span><span class="l">A_STRONG_DB_PASSWORD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_TIKA_ENABLED</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_TIKA_GOTENBERG_ENDPOINT</span><span class="p">:</span><span class="w"> </span><span class="l">http://gotenberg:3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_TIKA_ENDPOINT</span><span class="p">:</span><span class="w"> </span><span class="l">http://tika:9998</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_URL</span><span class="p">:</span><span class="w"> </span><span class="l">https://paperless.YOUR_DOMAIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_SECRET_KEY</span><span class="p">:</span><span class="w"> </span><span class="l">YOUR_LONG_SECRET_KEY</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_OCR_LANGUAGE</span><span class="p">:</span><span class="w"> </span><span class="l">eng</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_TIME_ZONE</span><span class="p">:</span><span class="w"> </span><span class="l">Europe/Berlin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_ADMIN_USER</span><span class="p">:</span><span class="w"> </span><span class="l">admin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PAPERLESS_ADMIN_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">A_STRONG_ADMIN_PASSWORD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">USERMAP_UID</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">USERMAP_GID</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">data:/usr/src/paperless/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">media:/usr/src/paperless/media</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./export:/usr/src/paperless/export</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./consume:/usr/src/paperless/consume</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.enable=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.ppl.rule=Host(`paperless.YOUR_DOMAIN`)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.ppl.entrypoints=websecure&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.ppl.tls.certresolver=le&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.services.ppl.loadbalancer.server.port=8000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">default, proxy]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">media</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pgdata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">redisdata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">proxy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<p>The most important points:</p>
<ul>
<li><strong>Only <code>webserver</code> is on the <code>proxy</code> network</strong> and carries Traefik labels (port <strong>8000</strong>).
The four helper services (db, broker, gotenberg, tika) stay internal.</li>
<li><strong><code>PAPERLESS_URL</code></strong> is mandatory behind a proxy. Without it, Paperless rejects the login
with a CSRF/&ldquo;403 Forbidden&rdquo; error. It simultaneously sets the allowed hosts and the trusted
origins.</li>
<li><strong><code>PAPERLESS_OCR_LANGUAGE: eng</code></strong> sets text recognition to English. English, German and a
few other languages are already included in the image – no additional package needed.</li>
<li><strong><code>PAPERLESS_ADMIN_USER</code> / <code>_PASSWORD</code></strong> create the superuser automatically on the first
start, so you can log in directly.</li>
<li><strong><code>USERMAP_UID</code>/<code>GID</code></strong> should match your server user&rsquo;s ID (determine via <code>id -u</code> or
<code>id -g</code>, usually <code>1000</code>). Otherwise there&rsquo;s &ldquo;Permission denied&rdquo; in the consume folder.</li>
<li><strong><code>export</code> and <code>consume</code></strong> are deliberately folders in the project directory (bind mounts):
<code>consume</code> is the inbox, <code>export</code> the target for backups.</li>
</ul>
<p>Paperless uses <strong>four storage areas</strong> in total, which you should keep apart: <code>media</code> contains
your <strong>processed documents</strong> (the heart!), <code>data</code> the search index and helper data, <code>consume</code>
is the inbox and <code>export</code> the backup target. For the backup, <code>media</code> and <code>data</code> plus the
database count – exactly what the <code>document_exporter</code> further below takes off your hands.</p>
<h3 id="step-4-start-and-log-in">Step 4: Start and log in</h3>
<p>Pull the images (several GB) and start:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose up -d
</span></span><span class="line"><span class="cl">docker compose logs -f webserver</span></span></code></pre></div>
</div>
<p>On the first start, Paperless sets up the database (migrations) – that takes a moment. Check
that all five containers are running:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose ps</span></span></code></pre></div>
</div>
<p>You should see <code>webserver</code>, <code>db</code>, <code>broker</code>, <code>gotenberg</code> and <code>tika</code> with status <code>running</code> (the
<code>webserver</code> becomes <code>healthy</code> after a short while). Once the worker is ready (<code>celery@… ready</code>
in the log), open <code>https://paperless.YOUR_DOMAIN</code>. The login page appears:</p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-paperless-ngx/paperless-login_hu_8c17b0adc53e6d7c.webp" srcset="/en/tutorials/self-host-paperless-ngx/paperless-login_hu_eab31263f0071468.webp 480w, /en/tutorials/self-host-paperless-ngx/paperless-login_hu_8c17b0adc53e6d7c.webp 768w, /en/tutorials/self-host-paperless-ngx/paperless-login_hu_4c5105a0d43bf62d.webp 1200w, /en/tutorials/self-host-paperless-ngx/paperless-login_hu_b34074ae087a306b.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-paperless-ngx/paperless-login_hu_548768b5baa3e389.webp"
    alt="The login page of Paperless-ngx under your own domain" title="The login – the superuser was created via environment variable"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The login – the superuser was created via environment variable</figcaption></figure></p>
<p>Log in with <code>admin</code> and your password. You land on the <strong>home page</strong> with a short welcome
message and initial statistics. At the bottom left you see the running <strong>version</strong> – handy to
know the starting point before an update. You can change the language under <strong>Settings</strong> if
needed; by default Paperless follows your browser&rsquo;s language:</p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-paperless-ngx/paperless-dashboard_hu_532c17eeb9a02757.webp" srcset="/en/tutorials/self-host-paperless-ngx/paperless-dashboard_hu_24d85502f951755a.webp 480w, /en/tutorials/self-host-paperless-ngx/paperless-dashboard_hu_532c17eeb9a02757.webp 768w, /en/tutorials/self-host-paperless-ngx/paperless-dashboard_hu_5f8d48fef757cd12.webp 1200w, /en/tutorials/self-host-paperless-ngx/paperless-dashboard_hu_d217a5d44c702c2a.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-paperless-ngx/paperless-dashboard_hu_b512bdf2de2fef4d.webp"
    alt="The home page of Paperless-ngx with a welcome message and statistics widget" title="The home page after the first login"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The home page after the first login</figcaption></figure></p>
<h3 id="step-5-import-the-first-document">Step 5: Import the first document</h3>
<p>Now the core. There are three ways to get a document in:</p>
<ol>
<li><strong>Via the web interface:</strong> click <strong>Upload documents</strong> at the top right and choose a PDF or
image file.</li>
<li><strong>Via the consume folder:</strong> place a file in <code>~/paperless/consume</code> – Paperless detects it
automatically, processes it and then deletes it from the folder. Ideal for a network
scanner that scans directly there.</li>
<li><strong>From a mailbox:</strong> under <strong>Email</strong>, Paperless can fetch an IMAP account and import
attachments automatically – handy for invoices that come to you by mail anyway.</li>
</ol>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cp ~/an-invoice.pdf ~/paperless/consume/</span></span></code></pre></div>
</div>
<p>Paperless accepts PDFs, images (JPG/PNG/TIFF) and – thanks to Gotenberg and Tika – office
files like Word or Excel. For a pure image scan, the <strong>OCR</strong> reads out the text and places it
as a searchable layer over the document; for a PDF with an existing text layer, Paperless
skips the recognition and is correspondingly faster. The original is preserved untouched –
Paperless additionally creates a searchable archive version.</p>
<p>After a few seconds (OCR takes some time), the document appears under <strong>Documents</strong> – with a
preview and the recognized title:</p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-paperless-ngx/paperless-dokumente_hu_84777342d82cdc6b.webp" srcset="/en/tutorials/self-host-paperless-ngx/paperless-dokumente_hu_9bb40ad0b8765b92.webp 480w, /en/tutorials/self-host-paperless-ngx/paperless-dokumente_hu_84777342d82cdc6b.webp 768w, /en/tutorials/self-host-paperless-ngx/paperless-dokumente_hu_a4d3e509f7d12dd9.webp 1200w, /en/tutorials/self-host-paperless-ngx/paperless-dokumente_hu_e1dfa31205f60c26.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-paperless-ngx/paperless-dokumente_hu_cd05852f670666a2.webp"
    alt="The document list of Paperless-ngx with a processed document as a tile" title="The document list – the imported document is immediately searchable"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The document list – the imported document is immediately searchable</figcaption></figure></p>
<p>A click opens the <strong>detail view</strong>: on the left the metadata (title, date, correspondent,
tags) and the tabs for content, metadata and history, on the right the document with a
zoomable preview. Paperless has already <strong>recognized the date</strong> from the scan and suggests it
for confirmation – exactly what the OCR does for you in the background:</p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-paperless-ngx/paperless-dokument-detail_hu_2f3ba14ecbae68c7.webp" srcset="/en/tutorials/self-host-paperless-ngx/paperless-dokument-detail_hu_cc142753382bd2b3.webp 480w, /en/tutorials/self-host-paperless-ngx/paperless-dokument-detail_hu_2f3ba14ecbae68c7.webp 768w, /en/tutorials/self-host-paperless-ngx/paperless-dokument-detail_hu_510c85a62c169f85.webp 1200w, /en/tutorials/self-host-paperless-ngx/paperless-dokument-detail_hu_975081bceb44bc8e.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-paperless-ngx/paperless-dokument-detail_hu_96c911f3a177fc51.webp"
    alt="The detail view of a document in Paperless-ngx with metadata on the left and the recognized document on the right" title="Detail view: OCR recognized text and date, metadata can be supplemented"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Detail view: OCR recognized text and date, metadata can be supplemented</figcaption></figure></p>
<h3 id="step-6-order-with-tags-correspondents--document-types">Step 6: Order with tags, correspondents &amp; document types</h3>
<p>So that the search works later, you assign <strong>tags</strong> (e.g. <code>Tax</code>, <code>Insurance</code>), assign a
<strong>correspondent</strong> (the sender) and a <strong>document type</strong> (invoice, contract …). You can do that
by hand – or automate it via <strong>workflows</strong>: rules that automatically tag incoming documents
based on their content. This way your archive sorts itself over time.</p>
<p>The <strong>full-text search</strong> at the top then searches not only titles and tags, but the complete
recognized text – a search for <code>invoice amount</code> or a customer name finds the matching
document in seconds. Combined with the filters (correspondent, period, document type), the
paper pile finally becomes a searchable archive.</p>
<p>A simple example of a <strong>workflow</strong>: if a new document contains the word &ldquo;electricity bill&rdquo;,
automatically assign the tag <code>Energy</code>, set the correspondent to your electricity provider and
the document type to <code>Invoice</code>. You create such rules under <strong>Administration → Workflows</strong>;
they take effect for every incoming document. At the start it pays to sort a few documents by
hand – from that you quickly see which rules repeat and can be automated. After a few weeks,
the majority of your mail lands in the right place without your involvement.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Scanner directly into the consume folder
  </p>
  <div class="prose-kitchen text-sm">The full convenience comes with a document scanner. Set up a network share on the server
(e.g. Samba) that points to <code>~/paperless/consume</code>, and configure your scanner to scan there.
From then on: insert sheet, press button – a few seconds later the document is recognized,
tagged and searchable in the archive. For on the go there are also community apps (e.g.
&ldquo;Paperless Mobile&rdquo;) that connect to your instance.</div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Your documents are on the internet
  </p>
  <div class="prose-kitchen text-sm">Paperless holds sensitive documents – invoices, contracts, official mail. As soon as the
instance is reachable via <code>paperless.YOUR_DOMAIN</code>, the login page is open on the net. So set
a <strong>long, unique password</strong>, and enable <strong>two-factor authentication</strong> under <strong>Settings</strong>.
Whoever wants maximum security makes Paperless reachable only via a VPN – for a purely private
archive that only you use, that&rsquo;s worth considering.</div>
</div>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong>On login, &ldquo;Forbidden (403)&rdquo; or &ldquo;CSRF verification failed&rdquo; appears.</strong> <code>PAPERLESS_URL</code> is not
set or set wrong. It must exactly match your HTTPS address (<code>https://paperless.YOUR_DOMAIN</code>,
without a trailing slash). After the correction, <code>docker compose up -d</code>.</p>
<p><strong>Files in the consume folder aren&rsquo;t processed, &ldquo;permission denied&rdquo;.</strong> Most common cause: the
folders weren&rsquo;t created beforehand (step 3), but generated by the Docker daemon on the first
start – then they belong to <code>root</code>. With <code>sudo chown -R $(id -u):$(id -g) ~/paperless/consume ~/paperless/export</code> they belong to you again. Otherwise: <code>USERMAP_UID</code>/<code>USERMAP_GID</code> don&rsquo;t
match the folder&rsquo;s owner – determine your ID with <code>id -u</code> and <code>id -g</code>, enter the values and
restart.</p>
<p><strong>Uploaded documents get stuck &ldquo;in processing&rdquo;.</strong> The background processing runs via Redis.
Check that the <code>broker</code> container is running, and look under <strong>File Tasks</strong> for the error
message of the failed task.</p>
<p><strong>Text recognition returns gibberish or recognizes nothing.</strong> Wrong OCR language. Set
<code>PAPERLESS_OCR_LANGUAGE=eng</code> (or <code>eng+deu</code> for mixed documents). Only installed languages
work.</p>
<p><strong>Office documents (Word, Excel) aren&rsquo;t accepted or end in a timeout.</strong> Gotenberg and Tika
are responsible for those. Check that both containers are running and
<code>PAPERLESS_TIKA_ENABLED=1</code> plus the two endpoint variables are set.</p>
<p><strong>During bulk import the server gets very slow, the CPU is constantly maxed out.</strong> OCR is
compute-intensive, and Paperless uses all cores by default. On small servers you can throttle
the load by limiting the number of workers or threads (<code>PAPERLESS_TASK_WORKERS</code>,
<code>PAPERLESS_THREADS_PER_WORKER</code>). Then the import takes longer, but the interface stays
usable.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li>
<p><strong>The clean backup is done by the <code>document_exporter</code>.</strong> It writes all documents,
thumbnails, metadata and the database content into the <code>export</code> folder – portable and
re-importable:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose <span class="nb">exec</span> -T webserver document_exporter ../export</span></span></code></pre></div>
</div>
<p>Then back up the <code>export</code> folder <strong>encrypted and off-site</strong> with
<a href="/en/tutorials/restic-backups/">Restic</a>. Alternatively you back up the volumes <code>media</code>,
<code>data</code> and the database directly – but the exporter is the recommended, migration-safe way.</p>
</li>
<li>
<p><strong>Use the same version when restoring.</strong> An export contains an image matching the database
schema; only import it into a Paperless instance of the <strong>same version</strong>
(<code>document_importer ../export</code>).</p>
</li>
<li>
<p><strong>Updates.</strong> Before the update, pull a backup, then raise the image tag (e.g. <code>2.20.15</code> →
next version), <code>docker compose pull</code> and <code>docker compose up -d</code>. The database migrations run
automatically at startup. Stay on the stable <code>2.x</code> series – the <code>3.0</code> beta isn&rsquo;t meant for
production yet.</p>
</li>
<li>
<p><strong>Automate the backup.</strong> Best put the <code>document_exporter</code> in a daily cron job (e.g. at
night) that afterwards backs up the <code>export</code> folder via Restic. That way you have a fresh,
restorable state every morning – without having to think about it.</p>
</li>
<li>
<p><strong>Honest about the effort:</strong> Paperless runs very low-maintenance after that. The actual
effort is filing new documents – but with a bit of set-up automation, the software
increasingly takes that off your hands too. Plan the rough tag structure once, then the
archive largely maintains itself.</p>
</li>
</ul>
]]></content:encoded></item></channel></rss>