<?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>Cloud – Serverküche</title><link>https://serverkueche.de/en/tags/cloud/</link><description>Cloud – 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>Sun, 19 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/cloud/index.xml" rel="self" type="application/rss+xml"/><item><title>Self-hosting Nextcloud: your own cloud behind Traefik</title><link>https://serverkueche.de/en/tutorials/self-host-nextcloud/</link><pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/self-host-nextcloud/</guid><description>Set up Nextcloud with Docker behind Traefik: your own cloud for files, calendar and contacts – with MariaDB, Redis and automatic HTTPS.</description><content:encoded><![CDATA[<p>Your files, your calendar, your contacts – but on your server instead of at a cloud
corporation. <strong>Nextcloud</strong> is the flagship of self-hosting: a full-featured cloud you run
yourself. In this recipe we set it up cleanly behind Traefik, with its own database,
caching and automatic HTTPS.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end, <strong>Nextcloud 34</strong> runs behind your Traefik proxy, reachable at
<code>https://cloud.YOUR_DOMAIN</code> with a valid Let&rsquo;s Encrypt certificate. Nextcloud is your
private cloud: sync files (like Dropbox), calendar and contacts across all devices, photos,
notes, office documents. You connect the official <strong>desktop sync client</strong> and the <strong>phone
apps</strong> with your server – the data lives exclusively with you.</p>
<p>We deliberately use the <strong>classic <code>nextcloud</code> image</strong> (not &ldquo;Nextcloud All-in-One&rdquo;) plus
<strong>MariaDB</strong> as the database and <strong>Redis</strong> for caching and file locking. This combination is
stable, well documented and fits exactly into the Traefik pattern from the Serverküche.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-sky-300 bg-sky-50 dark:border-sky-800 dark:bg-sky-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">ℹ️</span>Why not Nextcloud AIO?
  </p>
  <div class="prose-kitchen text-sm">Nextcloud also offers an &ldquo;All-in-One&rdquo; package (AIO). But it brings its <strong>own TLS and own
ports</strong> and wants to handle the reverse-proxy part itself – that clashes with an existing
Traefik. For our setup, the classic image is the right, controllable choice.</div>
</div>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A running <strong>Traefik reverse proxy</strong> with the shared <code>proxy</code> network and the Let&rsquo;s Encrypt
resolver <code>le</code> – set up as in the tutorial
<a href="/en/tutorials/traefik-reverse-proxy/">reverse proxy with Traefik</a>.</li>
<li>A subdomain <code>cloud.YOUR_DOMAIN</code> whose DNS record (A/AAAA) points to your server IP – see
<a href="/en/tutorials/connect-domain-to-server/">connecting a domain to your server</a>.</li>
<li>A working <strong>backup</strong>. A cloud is the place where data loss hurts most – 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">Nextcloud is more frugal than its reputation: for private use or a small family (1–5 users),
<strong>2 vCPU and 4 GB RAM</strong> are quite enough – the tested VPS 1000 fits. The bottleneck is
memory: with too little RAM the server starts swapping and gets sluggish. Redis (we build it
in shortly) relieves this noticeably. For Nextcloud Office/Collabora or many parallel users
you should rather plan for 8 GB.</div>
</div>
<p>How big your server should be for your user count is estimated by the
<a href="/en/server-calculator/">server calculator</a> in a few clicks.</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">For a private Nextcloud with a few users, the VPS 1000 is quite enough.</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>At your DNS provider, create an entry <code>cloud.YOUR_DOMAIN</code> that points to your server IP
(A record for IPv4, AAAA for IPv6). Check that it resolves:</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 cloud.YOUR_DOMAIN</span></span></code></pre></div>
</div>
<p>You should see your server IP. Only once the record is set can Traefik fetch the HTTPS
certificate later.</p>
<h3 id="step-2-determine-the-proxy-subnet">Step 2: Determine the proxy subnet</h3>
<p>Nextcloud sits behind Traefik. So that Nextcloud&rsquo;s brute-force protection recognizes the
<strong>real</strong> visitor IP (and doesn&rsquo;t ban the internal Traefik IP), we have to enter Traefik as a
&ldquo;trusted proxy&rdquo;. For that you need the subnet of your <code>proxy</code> network:</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 network inspect proxy -f <span class="s1">&#39;{{(index .IPAM.Config 0).Subnet}}&#39;</span></span></span></code></pre></div>
</div>
<p>You get something like <code>172.18.0.0/16</code>. <strong>Remember this value</strong> – it goes into the
configuration as <code>TRUSTED_PROXIES</code> shortly.</p>
<h3 id="step-3-create-the-composeyaml">Step 3: Create the compose.yaml</h3>
<p>Create a folder and change into it:</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 ~/nextcloud <span class="o">&amp;&amp;</span> <span class="nb">cd</span> ~/nextcloud</span></span></code></pre></div>
</div>
<p>Create the <code>compose.yaml</code>. Replace <code>cloud.YOUR_DOMAIN</code>, all passwords and the
<code>TRUSTED_PROXIES</code> subnet from step 2:</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">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">nc-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">mariadb:11.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">nc-db</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 class="l">transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW</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">MARIADB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">A_STRONG_ROOT_PASSWORD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MARIADB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">nextcloud</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MARIADB_USER</span><span class="p">:</span><span class="w"> </span><span class="l">nextcloud</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MARIADB_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">nc_db:/var/lib/mysql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">healthcheck</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">test</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;CMD&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;healthcheck.sh&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--connect&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--innodb_initialized&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">10s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">5s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">retries</span><span class="p">:</span><span class="w"> </span><span class="m">6</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">nc-redis</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">redis:8-alpine</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">nc-redis</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 class="l">redis-server --requirepass A_STRONG_REDIS_PASSWORD</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">nc-app</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">nextcloud:34-apache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">nc-app</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></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">nc-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">condition</span><span class="p">:</span><span class="w"> </span><span class="l">service_healthy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">nc-redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">service_started</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">MYSQL_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">nc-db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MYSQL_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">nextcloud</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MYSQL_USER</span><span class="p">:</span><span class="w"> </span><span class="l">nextcloud</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MYSQL_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">REDIS_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">nc-redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">REDIS_HOST_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">A_STRONG_REDIS_PASSWORD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">NEXTCLOUD_TRUSTED_DOMAINS</span><span class="p">:</span><span class="w"> </span><span class="l">cloud.YOUR_DOMAIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">OVERWRITEPROTOCOL</span><span class="p">:</span><span class="w"> </span><span class="l">https</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">OVERWRITECLIURL</span><span class="p">:</span><span class="w"> </span><span class="l">https://cloud.YOUR_DOMAIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">TRUSTED_PROXIES</span><span class="p">:</span><span class="w"> </span><span class="m">172.18.0.0</span><span class="l">/16</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PHP_MEMORY_LIMIT</span><span class="p">:</span><span class="w"> </span><span class="l">1024M</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PHP_UPLOAD_LIMIT</span><span class="p">:</span><span class="w"> </span><span class="l">10G</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">nc_html:/var/www/html</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.nc.rule=Host(`cloud.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.nc.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.nc.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.routers.nc.middlewares=nc-dav&#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.nc.loadbalancer.server.port=80&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># .well-known redirect for CalDAV/CardDAV (see below)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.nc-dav.redirectregex.regex=https://(.*)/.well-known/(?:card|cal)dav&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.nc-dav.redirectregex.replacement=https://cloud.YOUR_DOMAIN/remote.php/dav/&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.middlewares.nc-dav.redirectregex.permanent=true&#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">nc_html</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">nc_db</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>What matters here:</p>
<ul>
<li><strong>Three containers:</strong> <code>nc-app</code> (Nextcloud), <code>nc-db</code> (MariaDB), <code>nc-redis</code>. Only <code>nc-app</code> is
on the <strong><code>proxy</code> network</strong> (for Traefik) <em>and</em> on the internal <code>default</code> network. Database
and Redis stay exclusively internal – they have <strong>no <code>ports:</code></strong> and are not reachable from
outside.</li>
<li><strong>Two named volumes:</strong> <code>nc_html</code> (program code, <code>config.php</code>, user data) and <code>nc_db</code> (the
MariaDB data). The <code>nc_db</code> volume is mandatory: without the entry, the database lands in an
<strong>anonymous</strong> volume, and a <code>docker compose down</code> followed by <code>up</code> would <strong>lose it
irrecoverably</strong>.</li>
<li><strong><code>healthcheck</code> + <code>depends_on: service_healthy</code>:</strong> Nextcloud starts faster than the
database initializes. Without this healthcheck the initial install fails with a &ldquo;Connection
refused&rdquo;. This way the app container waits until MariaDB is really ready.</li>
<li><strong><code>NEXTCLOUD_TRUSTED_DOMAINS</code>:</strong> without your own domain here, Nextcloud greets you with
&ldquo;Access through untrusted domain&rdquo;.</li>
<li><strong><code>OVERWRITEPROTOCOL: https</code></strong> tells Nextcloud it runs behind HTTPS – otherwise it builds
internal links as <code>http://</code> and logins/redirects break.</li>
<li><strong><code>TRUSTED_PROXIES</code>:</strong> the subnet from step 2. This lets Nextcloud recognize the real client
IP; otherwise the brute-force protection bans the Traefik IP.</li>
<li><strong><code>PHP_MEMORY_LIMIT</code> / <code>PHP_UPLOAD_LIMIT</code>:</strong> the defaults (512 MB) are too small for large
uploads. Set generously here.</li>
<li><strong><code>loadbalancer.server.port=80</code>:</strong> the Apache image listens on port 80 in the container.</li>
<li><strong>The <code>nc-dav</code> middleware</strong> solves a classic Nextcloud problem – more on that shortly.</li>
</ul>
<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>CalDAV/CardDAV: the .well-known redirect
  </p>
  <div class="prose-kitchen text-sm">Nextcloud wants to send calendar/contact clients via <code>/.well-known/caldav</code> and
<code>/.well-known/carddav</code> to <code>/remote.php/dav/</code>. Behind a reverse proxy, Nextcloud&rsquo;s own
redirect doesn&rsquo;t work reliably – this later leads to a warning in the security check and to
problems with calendar setup. The <code>nc-dav</code> middleware above does the redirect directly in
Traefik. Important: the pattern must match the <strong>full <code>https://…</code> URL</strong> – <code>redirectregex</code>
always checks the complete URL, not just the path. A pure path pattern like <code>^/.well-known/…</code>
therefore never matches.</div>
</div>
<h3 id="step-4-start-and-run-through-the-initial-setup">Step 4: Start and run through the initial setup</h3>
<p>Start the stack:</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></code></pre></div>
</div>
<p>On the first start, MariaDB initializes the database and Nextcloud unpacks itself – that
takes a minute or two. Follow it in the log:</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 logs -f nc-app</span></span></code></pre></div>
</div>
<p>On the first run the image unpacks Nextcloud into the volume; you see lines like
<code>Initializing nextcloud 34.0.1.2 ...</code> and finally <code>Initializing finished</code>, followed by the
Apache start. If <code>restarting</code> appears repeatedly instead, take a look at the &ldquo;When things go
wrong&rdquo; section. With <code>Ctrl+C</code> you leave the log view again (the container keeps running).</p>
<p>Then open <code>https://cloud.YOUR_DOMAIN</code>. Traefik fetches the certificate on the first access
(can take a few seconds). Because we already configured the database and Redis via
environment variables, Nextcloud detects that automatically and only asks for an
<strong>administration account</strong>:</p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-nextcloud/nextcloud-setup_hu_c50c35957723d189.webp" srcset="/en/tutorials/self-host-nextcloud/nextcloud-setup_hu_41179e40d0ac8fe6.webp 480w, /en/tutorials/self-host-nextcloud/nextcloud-setup_hu_c50c35957723d189.webp 768w, /en/tutorials/self-host-nextcloud/nextcloud-setup_hu_64a4383ace5c261b.webp 1200w, /en/tutorials/self-host-nextcloud/nextcloud-setup_hu_ab4c44f37cb05b2f.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-nextcloud/nextcloud-setup_hu_a6187c5c382c3ab8.webp"
    alt="The Nextcloud initial setup detects the autoconfig and only asks for the name and password of the administration account" title="Initial setup: the database is preconfigured, you only create the admin account"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Initial setup: the database is preconfigured, you only create the admin account</figcaption></figure></p>
<p>Set an admin name and a strong password and click <strong>Install</strong>. Nextcloud sets up the instance
– after that you land on the login page:</p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-nextcloud/nextcloud-login_hu_ab5d9fc01a9c0266.webp" srcset="/en/tutorials/self-host-nextcloud/nextcloud-login_hu_5703a02035c4a1d2.webp 480w, /en/tutorials/self-host-nextcloud/nextcloud-login_hu_ab5d9fc01a9c0266.webp 768w, /en/tutorials/self-host-nextcloud/nextcloud-login_hu_1328b37d05a830c5.webp 1200w, /en/tutorials/self-host-nextcloud/nextcloud-login_hu_82532d9c9d89c6e1.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-nextcloud/nextcloud-login_hu_ff7e27fbd50c96bb.webp"
    alt="The Nextcloud login page under your own HTTPS domain" title="The login page at cloud.YOUR_DOMAIN – with valid HTTPS from Traefik"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The login page at cloud.YOUR_DOMAIN – with valid HTTPS from Traefik</figcaption></figure></p>
<p>After the login, the <strong>dashboard</strong> greets you, and under <strong>Files</strong> you find your cloud with a
few example files:</p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-nextcloud/nextcloud-dashboard_hu_db3401a59a72d3bd.webp" srcset="/en/tutorials/self-host-nextcloud/nextcloud-dashboard_hu_c4f9d6d8f49e635d.webp 480w, /en/tutorials/self-host-nextcloud/nextcloud-dashboard_hu_db3401a59a72d3bd.webp 768w, /en/tutorials/self-host-nextcloud/nextcloud-dashboard_hu_9086315a95ddf210.webp 1200w, /en/tutorials/self-host-nextcloud/nextcloud-dashboard_hu_860e5d90f28daa69.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-nextcloud/nextcloud-dashboard_hu_f4c7dc282828c0f.webp"
    alt="The Nextcloud dashboard with the &ldquo;Recommended files&rdquo; widget" title="The dashboard after the first login"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The dashboard after the first login</figcaption></figure></p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-nextcloud/nextcloud-dateien_hu_c6651a5b12cc3b8d.webp" srcset="/en/tutorials/self-host-nextcloud/nextcloud-dateien_hu_c2ff2f12d453b9c0.webp 480w, /en/tutorials/self-host-nextcloud/nextcloud-dateien_hu_c6651a5b12cc3b8d.webp 768w, /en/tutorials/self-host-nextcloud/nextcloud-dateien_hu_b5d67ad5d77fc9b.webp 1200w, /en/tutorials/self-host-nextcloud/nextcloud-dateien_hu_a5c245e619d92d9f.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-nextcloud/nextcloud-dateien_hu_f58abd3991247124.webp"
    alt="The Files interface of Nextcloud with folders and example files" title="The Files app – the heart of your own cloud"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The Files app – the heart of your own cloud</figcaption></figure></p>
<h3 id="step-5-set-up-background-jobs-cron">Step 5: Set up background jobs (cron)</h3>
<p>Nextcloud has to perform tasks regularly (cleanup, notifications, thumbnails). By default
this happens via &ldquo;AJAX&rdquo; on every page load – which is unreliable. The recommended way is a
<strong>cron sidecar</strong>: a second container with the same image that only runs <code>cron.php</code>. Add a
service to the <code>compose.yaml</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="w">  </span><span class="nt">nc-cron</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">nextcloud:34-apache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">nc-cron</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entrypoint</span><span class="p">:</span><span class="w"> </span><span class="l">/cron.sh</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></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">nc-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">condition</span><span class="p">:</span><span class="w"> </span><span class="l">service_healthy</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">nc_html:/var/www/html</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></span></code></pre></div>
</div>
<p>The sidecar shares the <code>nc_html</code> volume with the app and runs the jobs every five minutes.
Apply the change and set the mode in Nextcloud to &ldquo;Cron&rdquo;:</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 <span class="nb">exec</span> -u www-data nc-app php occ background:cron</span></span></code></pre></div>
</div>
<p>Under <strong>Administration → Basic settings</strong>, &ldquo;Cron&rdquo; should now be active.</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>occ – the command-line tool
  </p>
  <div class="prose-kitchen text-sm"><p><code>occ</code> is Nextcloud&rsquo;s admin tool. You always call it as the user <code>www-data</code> in the app
container: <code>docker exec -u www-data nc-app php occ &lt;command&gt;</code>. Two useful cleanup commands
right after installation:</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 <span class="nb">exec</span> -u www-data nc-app php occ db:add-missing-indices
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> -u www-data nc-app php occ config:system:set default_phone_region --value<span class="o">=</span>DE</span></span></code></pre></div>
</div></div>
</div>
<h3 id="step-6-connect-the-clients">Step 6: Connect the clients</h3>
<p>Now the real benefit. There are three ways to use your cloud:</p>
<ol>
<li><strong>Desktop sync client</strong> (Windows/macOS/Linux): install the &ldquo;Nextcloud Desktop&rdquo; client,
enter <code>https://cloud.YOUR_DOMAIN</code> as the server address, log in and choose a local folder.
Files are synced like with Dropbox. With <strong>virtual files</strong> (Windows/macOS) they only take
up space on the disk when you open them – handy when your cloud is larger than the local
disk.</li>
<li><strong>Phone app</strong> (Android/iOS): the official Nextcloud app, same server address. Ideal for
automatic <strong>photo upload</strong> from the smartphone.</li>
<li><strong>Calendar &amp; contacts:</strong> in your device&rsquo;s system settings, create a CalDAV/CardDAV account
with the address <code>https://cloud.YOUR_DOMAIN</code> – thanks to the <code>.well-known</code> middleware from
step 3, automatic detection works.</li>
</ol>
<p>Features like calendar, contacts or mail are separate <strong>apps</strong> you install with one click
under <strong>Administration → Apps</strong> – so Nextcloud is extensible without you touching anything on
the server.</p>
<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>Don&#39;t work with the admin account
  </p>
  <div class="prose-kitchen text-sm">The administration account from step 4 is meant for <strong>management</strong>, not for everyday use.
Create a normal user account under <strong>Administration → Accounts</strong> and sync your files through
it. That way your powerful admin account isn&rsquo;t permanently logged in on all devices – a
simple but effective security gain. For family or team, create further accounts here; each
gets its own encrypted area.</div>
</div>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong>&ldquo;Access through untrusted domain&rdquo; instead of the login page.</strong> Your domain isn&rsquo;t in
<code>trusted_domains</code>. Check <code>NEXTCLOUD_TRUSTED_DOMAINS</code> in the Compose file. To set it
afterwards, use occ: <code>docker exec -u www-data nc-app php occ config:system:set trusted_domains 1 --value=cloud.YOUR_DOMAIN</code>.</p>
<p><strong>The security check reports &ldquo;Your web server is not set up properly to resolve
.well-known/caldav&rdquo;.</strong> The CalDAV/CardDAV redirect isn&rsquo;t working. Check the <code>nc-dav</code>
middleware labels (step 3) and that the router includes them via
<code>...routers.nc.middlewares=nc-dav</code>. The regex must match the full <code>https://…</code> URL –
<code>redirectregex</code> checks the complete URL, not just the path.</p>
<p><strong>Warning &ldquo;The &lsquo;Strict-Transport-Security&rsquo; HTTP header is not set&rdquo;.</strong> The HSTS header is
missing. Set it as a Traefik middleware and attach it to the router:
<code>traefik.http.middlewares.nc-secure.headers.stsSeconds=15552000</code>. Attach it additionally to
the <code>nc-dav</code> middleware on the router (<code>...routers.nc.middlewares=nc-dav,nc-secure</code>). The
header must come from the proxy, not from Nextcloud. In <a href="/en/tutorials/harden-optimize-nextcloud/">part
2</a> we set up exactly this <code>nc-secure</code> middleware
fully (including <code>includeSubdomains</code>).</p>
<p><strong>Large uploads break off or end with a timeout / &ldquo;413&rdquo;.</strong> The PHP limit is too small.
Increase <code>PHP_UPLOAD_LIMIT</code> and <code>PHP_MEMORY_LIMIT</code> (step 3) and restart the container. In
rare cases these variables don&rsquo;t take effect – then mount your own <code>php.ini</code> snippet into the
image.</p>
<p><strong>On the first start the installation aborts with &ldquo;MySQL server has gone away&rdquo; or &ldquo;Connection
refused&rdquo;.</strong> The app container was faster than the database. That&rsquo;s exactly what the
<code>healthcheck</code> with <code>depends_on: condition: service_healthy</code> is for – check that both are
present in your Compose file, and restart with <code>docker compose up -d</code>.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li>
<p><strong>Backups are mandatory for a cloud.</strong> A consistent backup needs <strong>three things</strong>: the
<strong>database</strong> (MariaDB dump), the <strong>data directory</strong> (your files) and the <strong>configuration</strong>
(<code>config.php</code>). Put Nextcloud into maintenance mode briefly before the backup so the
database and files match each other:</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 <span class="nb">exec</span> -u www-data nc-app php occ maintenance:mode --on
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> nc-db mariadb-dump -u root -pA_STRONG_ROOT_PASSWORD nextcloud &gt; nextcloud-db.sql
</span></span><span class="line"><span class="cl">docker <span class="nb">exec</span> -u www-data nc-app php occ maintenance:mode --off</span></span></code></pre></div>
</div>
<p>Back up the dump together with the <code>nc_html</code> volume <strong>encrypted and off-site</strong> with
<a href="/en/tutorials/restic-backups/">Restic</a> – thanks to the dump you don&rsquo;t need to additionally
back up the raw <code>nc_db</code> volume. The <code>nc_html</code> volume contains both the program code and the
<code>config.php</code> as well as – under <code>data/</code> – <strong>your actual user files</strong>. If your cloud grows a
lot, you can later move this <code>data/</code> directory to a separate, larger volume; for now one
volume for everything is enough.</p>
</li>
<li>
<p><strong>Updates: only one major version per step.</strong> From 34 to 35, never directly to 36. Bump the
image tag (<code>nextcloud:35-apache</code>), then <code>docker compose pull</code> and <code>docker compose up -d</code>.
The container runs the necessary <code>occ upgrade</code> <strong>automatically</strong> on start. <strong>Always make a
backup first.</strong> Bump the cron sidecar to the same version.</p>
</li>
<li>
<p><strong>Check security.</strong> After setup, run the official
<a href="https://scan.nextcloud.com/">Nextcloud Security Scan</a> against your domain (target: grade A)
and work through the warnings from the admin overview. The overview under <strong>Administration →
Overview</strong> runs automatic checks and shows exactly what&rsquo;s still missing. Keep Nextcloud up
to date – security holes are closed promptly, but only if you update.</p>
</li>
<li>
<p><strong>Honest about the effort:</strong> a self-hosted cloud wants to be maintained. Reckon with an
update pass roughly monthly and a regular look at the admin overview. In return, the data
then really belongs to you.</p>
</li>
</ul>
]]></content:encoded></item></channel></rss>