<?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>Comparison – Serverküche</title><link>https://serverkueche.de/en/tags/comparison/</link><description>Comparison – 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>Sat, 10 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/comparison/index.xml" rel="self" type="application/rss+xml"/><item><title>Stalwart or Mailcow? Two mail servers, both set up</title><link>https://serverkueche.de/en/tutorials/stalwart-or-mailcow/</link><pubDate>Sat, 10 Oct 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/stalwart-or-mailcow/</guid><description>One container or eighteen? Both mail servers really set up and compared: effort, RAM, attack surface – and what Mailcow really costs next to Traefik.</description><content:encoded><![CDATA[<p>We set up both: <a href="/en/tutorials/mailcow-mail-server/">Mailcow</a> on 10 August,
<a href="/en/tutorials/stalwart-mail-server/">Stalwart</a> the day after, both on the same test VPS. This
article isn&rsquo;t a third recipe – it answers the question that comes before them: which of the two
do you take?</p>
<p>The answer doesn&rsquo;t hang on a feature list. It hangs on an architectural decision that carries a
price tag.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>Nothing – we&rsquo;re deciding. By the end of this article you&rsquo;ll know which of the two mail servers to
set up, and why the other one would be wrong for your case.</p>
<p>The starting position in numbers, measured rather than guessed:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Stalwart</th>
					<th>Mailcow</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Containers</td>
					<td>1</td>
					<td>18</td>
			</tr>
			<tr>
					<td>Images on disk</td>
					<td>407 MB</td>
					<td>around 4.5 GB</td>
			</tr>
			<tr>
					<td>RAM</td>
					<td>46 MiB idle</td>
					<td>6 GB set as the minimum</td>
			</tr>
			<tr>
					<td>Recommended server</td>
					<td>VPS 1000 (8 GB)</td>
					<td>VPS 2000</td>
			</tr>
			<tr>
					<td>Behind a Traefik</td>
					<td>one volume, one object</td>
					<td>five switches, restart hook</td>
			</tr>
			<tr>
					<td>Webmail in the browser</td>
					<td>no</td>
					<td>yes (SOGo)</td>
			</tr>
			<tr>
					<td>Licence</td>
					<td>AGPL-3.0 + commercial</td>
					<td>GPL-3.0</td>
			</tr>
	</tbody>
</table>
<p>The sentence everything comes down to sits in the third row. <strong>Both</strong> can be put behind the
shared <a href="/en/tutorials/traefik-reverse-proxy/">Traefik</a> – that is not what tells them apart, even
though you often read otherwise. What tells them apart is the price: Stalwart is one container
next to everything else and makes do with 46 MiB when idle. Mailcow is eighteen, and its minimum
of 6 GB of RAM has to be there on top of everything else the machine already runs.</p>
<p>That isn&rsquo;t a recommendation against Mailcow. It&rsquo;s the sum you should do before deciding – because
more often than not it ends at a second server for 14 to 27 euros a month.</p>
<p><figure class="my-6"><img src="architecture-schema.svg" alt="Diagram: Traefik and the certs-dumper as the shared foundation, below it Stalwart as a single container on the left and Mailcow with nginx and seventeen more on the right." title="The same foundation for both – and below it one container or eighteen." loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The same foundation for both – and below it one container or eighteen.</figcaption></figure></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>Which versions are in here
  </p>
  <div class="prose-kitchen text-sm">The two setups were built with <strong>Stalwart 0.16.21</strong> and <strong>mailcow-dockerized 2026-07b</strong> on
Debian 13. At the time this article was published, <strong>0.16.25</strong> (5 Oct) and <strong>2026-09a</strong> (6 Oct)
are current; the measured image size, the RAM figure and the certificate path below come from
0.16.25. Nothing about
the architecture discussed here changed between those releases.</div>
</div>
<h2 id="prerequisites">Prerequisites</h2>
<p>For this article you need exactly one thing: a <strong>domain whose DNS you control yourself</strong>. We
deliberately do not assume a running server – if you&rsquo;re reading this, you&rsquo;re still deciding.</p>
<p>Two things apply to both solutions equally, so they&rsquo;re no help in telling them apart – but they
will cost you time either way:</p>
<ul>
<li><strong>Deliverability.</strong> Whether your mail arrives or lands in spam is decided by PTR, SPF, DKIM and
DMARC – not by the software. That&rsquo;s the subject of the recipe on
<a href="/en/tutorials/email-deliverability/">email deliverability</a>, and it&rsquo;s the real reason running
your own mail server is work.</li>
<li><strong>Outbound SMTP.</strong> At netcup the default policy &ldquo;netcup Mail block&rdquo; blocks ports 25, 465 and
587 outbound. You take it off the server yourself in the SCP, without a support ticket – see
<a href="/en/tutorials/netcup-firewall-setup/">netcup firewall setup</a>.</li>
</ul>
<h2 id="step-by-step">Step by step</h2>
<p>Each of the following steps is one dimension of the decision. If one of them settles it for you,
you can skip the rest – the table in step 6 pulls everything together.</p>
<h3 id="step-1-the-architecture--one-container-or-eighteen">Step 1: The architecture – one container or eighteen</h3>
<p>Stalwart is a single process written in Rust. That one binary contains SMTP, IMAP, POP3, JMAP,
CalDAV, CardDAV, WebDAV, Sieve, a spam and phishing filter, and the data store (RocksDB). The
image is correspondingly small:</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 pull stalwartlabs/stalwart:v0.16.25
</span></span><span class="line"><span class="cl">docker image ls stalwartlabs/stalwart</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">REPOSITORY              TAG        SIZE
</span></span><span class="line"><span class="cl">stalwartlabs/stalwart   v0.16.25   407MB</span></span></code></pre></div>
</div>
<p>Idling – freshly started, without mailboxes – the container used <strong>46 MiB of RAM</strong> at 0 % CPU.
Take that as a floor, not an operating figure: with real mailboxes the RocksDB cache and the spam
classifier&rsquo;s training data grow, and the full-text index comes on top. The order of magnitude
holds all the same, and it&rsquo;s the reason Stalwart runs alongside a server that already does
something else.</p>
<p>Mailcow is the opposite, and deliberately so. The stack consists of <strong>18 services</strong>, taken from
the project&rsquo;s own <code>docker-compose.yml</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">unbound        mysql          redis          clamd
</span></span><span class="line"><span class="cl">rspamd         php-fpm        sogo           dovecot
</span></span><span class="line"><span class="cl">postfix        postfix-tlspol memcached      nginx
</span></span><span class="line"><span class="cl">acme           netfilter      watchdog       dockerapi
</span></span><span class="line"><span class="cl">olefy          ofelia</span></span></code></pre></div>
</div>
<p>That list isn&rsquo;t bloat, it&rsquo;s the bill for the feature set. <code>sogo</code> is the webmail including calendar,
<code>clamd</code> and <code>olefy</code> are virus scanner and macro checker, <code>unbound</code> a DNS resolver of its own,
<code>watchdog</code> and <code>ofelia</code> handle monitoring and cron jobs. Each of these parts is a mature,
specialised tool, and Postfix and Dovecot are among the best-seasoned server programs there are.</p>
<p>The difference isn&rsquo;t quality, it&rsquo;s the <strong>number of moving parts</strong> you answer for. With Stalwart
it&rsquo;s one.</p>
<h3 id="step-2-what-it-costs-to-move-in-next-to-a-reverse-proxy">Step 2: What it costs to move in next to a reverse proxy</h3>
<p>There&rsquo;s a claim here that we carried for a long time ourselves: that Mailcow cannot run behind a
shared Traefik because its own nginx occupies ports 80 and 443. That only holds for the default
configuration. Mailcow documents the way around it
<a href="https://docs.mailcow.email/post_installation/reverse-proxy/r_p-traefik3/">explicitly</a> – you bind
its nginx to a local port and take the certificate off it:</p>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">HTTP_BIND</span><span class="o">=</span><span class="s">127.0.0.1</span>
</span></span><span class="line"><span class="cl"><span class="na">HTTP_PORT</span><span class="o">=</span><span class="s">8080</span>
</span></span><span class="line"><span class="cl"><span class="na">HTTPS_BIND</span><span class="o">=</span><span class="s">127.0.0.1</span>
</span></span><span class="line"><span class="cl"><span class="na">HTTPS_PORT</span><span class="o">=</span><span class="s">8443</span>
</span></span><span class="line"><span class="cl"><span class="na">SKIP_LETS_ENCRYPT</span><span class="o">=</span><span class="s">y</span>
</span></span><span class="line"><span class="cl"><span class="na">AUTODISCOVER_SAN</span><span class="o">=</span><span class="s">n</span></span></span></code></pre></div>
</div>
<p>So both can sit behind the same proxy. The difference is what it costs you.</p>
<p>With Mailcow it&rsquo;s the six switches above, three Traefik routers (the UI, <code>autoconfig</code>,
<code>autodiscover</code>) – and one detail you have to know about: Postfix and Dovecot only read their
certificate at start-up. The dumper therefore has to restart them after every renewal:</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">certdumper</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/kereis/traefik-certs-dumper:1.8.28</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">restart-containers mailcowdockerized-postfix-mailcow-1,mailcowdockerized-dovecot-mailcow-1</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">DOMAIN</span><span class="p">:</span><span class="w"> </span><span class="l">mail.YOUR_DOMAIN  </span><span class="w"> </span><span class="c"># writes cert.pem/key.pem flat, the way Mailcow expects them</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">/opt/traefik/:/traefik:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/var/run/docker.sock:/var/run/docker.sock:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">./data/assets/ssl:/output:rw</span></span></span></code></pre></div>
</div>
<p>Two things deliberately differ from mailcow&rsquo;s own template. The container names there
(<code>mailcow_postfix-mailcow_1</code>) come from the old Compose v1; current Docker names the containers
<code>mailcowdockerized-…-1</code>, and with the old names the dumper finds nothing to restart. And it needs
the Docker socket to be allowed to restart anything at all.</p>
<p>On top of that, mailcow marks this page as <strong>&ldquo;community supported&rdquo;</strong> – it carries a warning box.
That&rsquo;s not a dealbreaker, but it is a difference from a path the vendor carries itself.</p>
<p>Two things stay true regardless:</p>
<ol>
<li>The <strong>mail ports</strong> 25, 465, 587, 143, 993, 110, 995 and 4190 remain directly on the host – for
both solutions equally.</li>
<li>On hosts with IPv6, <code>generate_config.sh</code> creates an <code>/etc/docker/daemon.json</code> and <strong>restarts
Docker immediately</strong>. On a shared server that restart takes everything else down with it. It
happens once, during setup – but it happens.</li>
</ol>
<p>And then there&rsquo;s the point that usually decides it in practice and has nothing to do with ports:
<strong>6 GB of RAM as the minimum</strong>, with ClamAV rather 8. You need that on top of everything already
running on the machine. A VPS 1000 with 8 GB is effectively full at that point. That&rsquo;s why people
do end up on a second server for Mailcow – not because it couldn&rsquo;t work otherwise, but because
the memory runs out.</p>
<p>Stalwart, at 46 MiB idle, moves in alongside. There seems to be a catch: Stalwart
fetches its Let&rsquo;s Encrypt certificate via TLS-ALPN-01 on port 443 by default, and your proxy has
had that port for ages. There&rsquo;s a clean solution for it, and it&rsquo;s written up in
<a href="https://stalw.art/docs/server/reverse-proxy/traefik">Stalwart&rsquo;s own documentation</a>: Stalwart
stops fetching a certificate at all and <strong>gets it from Traefik</strong> instead.</p>
<p>In between sits a tiny container that writes certificate and key out of Traefik&rsquo;s <code>acme.json</code>
into a shared volume:</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">traefik-certs-dumper</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/kereis/traefik-certs-dumper:1.8.28</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">always</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">restart-containers stalwart</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">OVERRIDE_UID</span><span class="p">:</span><span class="w"> </span><span class="m">2000</span><span class="w">      </span><span class="c"># Stalwart runs as uid 2000 in the container</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">OVERRIDE_GID</span><span class="p">:</span><span class="w"> </span><span class="m">2000</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">/opt/traefik/:/traefik:ro     </span><span class="w"> </span><span class="c"># where acme.json lives in the Traefik recipe</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/run/docker.sock:/var/run/docker.sock:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">certs:/output</span></span></span></code></pre></div>
</div>
<p>The dumper looks for <code>acme.json</code> directly under <code>/traefik</code>. So you mount the directory that holds
Traefik&rsquo;s <code>acme.json</code> – in the <a href="/en/tutorials/traefik-reverse-proxy/">Traefik recipe</a> that&rsquo;s
<code>/opt/traefik/</code>. If the path is off, Docker silently creates an empty directory, the dumper only
logs <code>/traefik/acme.json is not a file. Retrying...</code>, and Stalwart never gets a certificate.</p>
<p>Those two <code>OVERRIDE</code> lines are not decoration: the dumper writes <code>key.pem</code> with permissions
<code>0640</code>, and Stalwart runs as <strong>uid 2000</strong> inside the container. Without handing over ownership it
can&rsquo;t get at its own key.</p>
<p>Stalwart mounts the same volume read-only (<code>certs:/data/certs:ro</code>), needs the fixed name
<code>container_name: stalwart</code> for the restart, attaches to the web interface
with the usual Traefik labels, and publishes the mail ports directly on the container. In the
setup wizard you <strong>turn off &ldquo;Automatically Obtain TLS Certificate&rdquo;</strong> – otherwise Stalwart keeps
trying to get one itself. Then, in the console under <em>Settings → TLS → Certificates</em>, you point
at the two files – in the interface the types are called <em>Text value read from file</em> and
<em>Secret read from file</em>:</p>
<div class="sk-code">
  <span class="sk-code-head">JSON</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;certificate&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;@type&#34;</span><span class="p">:</span> <span class="s2">&#34;File&#34;</span><span class="p">,</span> <span class="nt">&#34;filePath&#34;</span><span class="p">:</span> <span class="s2">&#34;/data/certs/mail.YOUR_DOMAIN/cert.pem&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;privateKey&#34;</span><span class="p">:</span>  <span class="p">{</span> <span class="nt">&#34;@type&#34;</span><span class="p">:</span> <span class="s2">&#34;File&#34;</span><span class="p">,</span> <span class="nt">&#34;filePath&#34;</span><span class="p">:</span> <span class="s2">&#34;/data/certs/mail.YOUR_DOMAIN/key.pem&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
</div>
<p>Stalwart lists the certificate right away, <strong>but only serves it after a restart</strong>
(<code>docker compose restart stalwart</code>). Until then a mail client still gets the self-signed
placeholder <code>CN=rcgen self signed cert</code>.</p>
<p>That&rsquo;s also why the dumper carries <code>--restart-containers stalwart</code>: Stalwart reads the files only
at start-up. When Traefik renews the certificate, the dumper writes the new files and restarts
Stalwart – a matter of seconds. Without that line Stalwart would keep running with the old
certificate after the first renewal until it expires 90 days later. The Docker socket is full
access to Docker even with <code>:ro</code> – which puts the dumper in the same trust class as Traefik, which
has the socket too. The console action <em>Reload → TLS certificates</em> would be the alternative, but
in the Community Edition it answered <code>API error 403</code> for us.</p>
<p>This is more elegant than the DNS-01 challenge you might reach for first: you need no API
credentials at your DNS provider, and the host keeps <strong>a single ACME client</strong> – the one you&rsquo;re
running anyway.</p>
<p>We rebuilt this on the test VPS, alongside the Traefik running there permanently, and simulated
a renewal: Traefik writes <code>acme.json</code>, the dumper follows and restarts Stalwart. Stalwart then
serves, on its own mail ports, the certificate it never requested – here the handshake on the
IMAPS port:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">subject=CN=mail.YOUR_DOMAIN
</span></span><span class="line"><span class="cl">issuer=C=US, O=Let&#39;s Encrypt, CN=YR2
</span></span><span class="line"><span class="cl">Verify return code: 0 (ok)</span></span></code></pre></div>
</div>
<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>What still stays on the server
  </p>
  <div class="prose-kitchen text-sm">The <strong>mail ports</strong> 25, 465, 993 and 4190 still have to be free on the host – they don&rsquo;t speak
HTTP and can&rsquo;t be routed through an HTTP proxy. Traefik can take them over as TCP routers with
<code>tls.passthrough</code>, but then you additionally need the Proxy Protocol so Stalwart sees the real
sender IP. Publishing the ports on the container is simpler.</div>
</div>
<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"
     data-track-content data-content-name="Affiliate-Box · /en/tutorials/stalwart-or-mailcow/" data-content-piece="VPS 2000 G12.5">
  <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 2000 G12.5</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">8 vCores · 16 GB RAM · 256 GB SSD</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">from €26.92/month</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">If the choice falls on Mailcow: this is the second server it needs – 16 GB lets ClamAV run along too.</p>
    </div>
    <a href="https://www.netcup.com/en/server/vps/vps-2000-g12.5-iv-24m-eu?ref=44083#vps-2000-g12.5-iv-12m-eu" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 2000 G12.5 · {page}"
   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"><div class="not-prose my-3 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"
     data-voucher-api="vps-2000" data-voucher-msg-empty="No code is available for this plan right now – use the €5 code." data-voucher-msg-limit="No more codes today – use the €5 code or try again tomorrow." data-voucher-msg-error="The code couldn&#39;t be fetched right now – use the €5 code.">
  <p class="flex flex-wrap items-center gap-x-2 gap-y-1"><span>💶 <span class="font-semibold">1 month free</span>:</span>
    <button type="button" data-voucher-reveal data-copy-title="Click to copy"
            class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">Show code</button>
    <button type="button" data-voucher-next hidden
            class="rounded border border-herb-500/40 px-2 py-0.5 text-xs text-herb-800 hover:bg-white dark:text-herb-400 dark:hover:bg-slate-800">another code</button>
  </p>
  <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
    Single use, valid for VPS 2000 G12.5. Redeem it in an empty cart – the code adds the server itself.
    <a href="https://www.netcup.com/en/checkout/cart?ref=44083" rel="sponsored noopener" target="_blank"
       data-track-event="Affiliate|netcup: Gutschein einlösen|VPS 2000 G12.5 · {page}"
       class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">Redeem in the cart →</a>
  </p><p class="mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 border-t border-herb-500/30 pt-2">
    <span>💶 <span class="font-semibold">€5 voucher</span> for new netcup customers:</span>
    <button type="button" data-voucher-code data-track-voucher="36nc17844976032"
            title="Click to copy" class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</button>
    <span class="text-xs text-slate-500 dark:text-slate-400">always valid · not for domains or VPS Lite</span>
  </p><p class="mt-1 text-xs"><a href="/en/netcup-voucher/" class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">All netcup vouchers →</a></p>
</div></div>
</div>

<p>If it&rsquo;s going to be Stalwart and you have no server at all yet, one size down is enough; the
plans are in the <a href="/en/netcup-recommendation/">netcup overview</a>, and what your particular setup
needs is estimated by the <a href="/en/server-calculator/">server calculator</a>.</p>
<h3 id="step-3-effort-during-setup-and-in-operation">Step 3: Effort during setup and in operation</h3>
<p>The figures from the two recipes speak for themselves: Stalwart is done in roughly 60 minutes and
seven steps and is rated <em>Intermediate</em>, Mailcow takes about 90 minutes, eight steps, and counts
as an <em>Advanced</em> topic.</p>
<p>What comes afterwards is more interesting. An update looks like this with Stalwart:</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"><span class="nb">cd</span> /opt/stalwart
</span></span><span class="line"><span class="cl">docker compose pull <span class="o">&amp;&amp;</span> docker compose up -d</span></span></code></pre></div>
</div>
<p>And like this with Mailcow:</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"><span class="nb">cd</span> /opt/mailcow-dockerized
</span></span><span class="line"><span class="cl">sudo ./update.sh</span></span></code></pre></div>
</div>
<p>The script pulls in the compose file, updates all images and restarts the stack. It takes work off
your hands – and it changes 18 services in one go.</p>
<p>The rhythms differ noticeably, and both demand attention. Stalwart published <strong>thirteen releases</strong>
between 12 July and 5 October, so roughly weekly; you decide each time whether to take it, and
stay on a pinned version tag for that. Mailcow managed thirteen releases between January and
early October of the same year, so roughly monthly, but has no version number in the classical sense:
it&rsquo;s a rolling release with month tags, and <code>update.sh</code> always fetches the current state.</p>
<p>Neither of them is a &ldquo;set it up once and forget it&rdquo;. Stalwart leaves you more control over when
something happens; Mailcow takes the individual decisions off your hands.</p>
<h3 id="step-4-feature-set--what-mailcow-has-and-stalwart-doesnt">Step 4: Feature set – what Mailcow has and Stalwart doesn&rsquo;t</h3>
<p>There&rsquo;s a stubborn misconception here, and it&rsquo;s worth clearing out of the way first:
<strong>Stalwart does calendars and contacts.</strong> CalDAV, CardDAV and WebDAV are fully on board, in the
free Community edition at that. &ldquo;I want appointments and an address book on the same server&rdquo; is
therefore no longer a deciding question.</p>
<p>The real difference is the browser.</p>
<p>Mailcow ships <strong>SOGo</strong>, a ready-made web interface for mail, calendar and contacts. You sign in on
any machine and you&rsquo;re in. Stalwart&rsquo;s web interface is something else: an <strong>administration
console</strong> plus a small self-service portal where users manage their password, their app passwords
and two-factor authentication. There&rsquo;s no webmailer in it.</p>
<p><figure class="my-6"><img src="/en/tutorials/stalwart-or-mailcow/stalwart-konsole_hu_a19ec6f07805601a.webp" srcset="/en/tutorials/stalwart-or-mailcow/stalwart-konsole_hu_f57dc908d1a542e3.webp 480w, /en/tutorials/stalwart-or-mailcow/stalwart-konsole_hu_a19ec6f07805601a.webp 768w, /en/tutorials/stalwart-or-mailcow/stalwart-konsole_hu_14cb790471e8f0d1.webp 1200w, /en/tutorials/stalwart-or-mailcow/stalwart-konsole_hu_e97000a8fa212723.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/stalwart-or-mailcow/stalwart-konsole_hu_e4237369675ef16c.webp"
    alt="The Stalwart management console with the overview of the accounts created." title="Stalwart: a console for administering – but no webmail."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Stalwart: a console for administering – but no webmail.</figcaption></figure></p>
<p><figure class="my-6"><img src="/en/tutorials/stalwart-or-mailcow/mailcow-dashboard_hu_ad9179c8d5d1303e.webp" srcset="/en/tutorials/stalwart-or-mailcow/mailcow-dashboard_hu_6e0d3ec62afbb156.webp 480w, /en/tutorials/stalwart-or-mailcow/mailcow-dashboard_hu_ad9179c8d5d1303e.webp 768w, /en/tutorials/stalwart-or-mailcow/mailcow-dashboard_hu_695dfece3465dfb.webp 1200w, /en/tutorials/stalwart-or-mailcow/mailcow-dashboard_hu_ce16a419b9c361b7.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/stalwart-or-mailcow/mailcow-dashboard_hu_e670b172b8905e61.webp"
    alt="The mailcow dashboard with the system overview after signing in." title="Mailcow: the same kind of admin interface – and from here it&#39;s a direct hop into the SOGo webmailer."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Mailcow: the same kind of admin interface – and from here it&#39;s a direct hop into the SOGo webmailer.</figcaption></figure></p>
<p>In practice that means: with Stalwart every user needs a mail program – Thunderbird, the Mail app
on iOS and macOS, DAVx5 on Android for calendars and contacts. For you alone or in a household
that&rsquo;s perfectly fine, because those programs are set up anyway. But as soon as someone has to
reach their mail from a machine that isn&rsquo;t theirs, something is missing. You can put a webmail
like Roundcube next to it – at which point it&rsquo;s container number two.</p>
<p>The second difference: Mailcow brings a <strong>virus scanner</strong> along in ClamAV, Stalwart doesn&rsquo;t.
Stalwart&rsquo;s built-in filter covers spam and phishing; you can attach a virus scanner via milter or
MTA hooks, but you have to run it yourself.</p>
<h3 id="step-5-attack-surface-licence-and-updates">Step 5: Attack surface, licence and updates</h3>
<p>The security argument for Stalwart is often told too loudly. Rust brings memory safety, and
Stalwart has a published <a href="https://stalw.art/blog/security-audit">security audit</a> to show. But it
does not follow that Postfix and Dovecot are insecure – they are among the most thoroughly
examined programs on the internet.</p>
<p>The sustainable argument is more sober: it&rsquo;s about the <strong>length of the update chain</strong>. Eighteen
services mean eighteen projects with their own security advisories that somebody has to reconcile.
That mailcow does exactly that, reliably, is the project&rsquo;s real value – but it&rsquo;s a dependency you
are taking on.</p>
<p>With the licences the picture flips, and that&rsquo;s rarely mentioned. <strong>Mailcow is entirely
GPL-3.0</strong> – everything it can do, you can use for free. Stalwart is dual-licensed: the Community
edition under AGPL-3.0, alongside a commercial Enterprise licence. Both editions share the same
code base and, as the vendor writes, the same protocols and core features. What the Enterprise
edition adds is operational tooling for large installations: multi-tenancy, custom branding,
archiving and un-deletion of deleted mailboxes, an AI-driven spam classifier, read replicas.</p>
<p>For a household or your own company domain, none of that is relevant. There&rsquo;s still a trap in it:
the large feature list on GitHub does <strong>not</strong> mark which point belongs to which edition. If you&rsquo;re
relying on a particular function, check the edition comparison first rather than trusting the
README.</p>
<h3 id="step-6-the-decision-table">Step 6: The decision table</h3>
<table>
	<thead>
			<tr>
					<th>Your case</th>
					<th>Take</th>
					<th>Because</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>You have a server with Traefik and want mail on it</td>
					<td>Stalwart</td>
					<td>Mailcow only fits there with 6 GB of RAM free</td>
			</tr>
			<tr>
					<td>Everyone should reach their mail from any browser</td>
					<td>Mailcow</td>
					<td>SOGo webmail is ready to go</td>
			</tr>
			<tr>
					<td>You look after mailboxes for other people</td>
					<td>Mailcow</td>
					<td>multi-tenancy costs extra with Stalwart</td>
			</tr>
			<tr>
					<td>You want as little maintenance as possible</td>
					<td>Stalwart</td>
					<td>one image instead of eighteen</td>
			</tr>
			<tr>
					<td>You have less than 8 GB of RAM available</td>
					<td>Stalwart</td>
					<td>Mailcow sets 6 GB as the minimum</td>
			</tr>
			<tr>
					<td>You want a virus scanner without extra work</td>
					<td>Mailcow</td>
					<td>ClamAV is part of the stack</td>
			</tr>
			<tr>
					<td>Thunderbird, iOS Mail or DAVx5 are in use anyway</td>
					<td>Stalwart</td>
					<td>CalDAV and CardDAV are included free</td>
			</tr>
			<tr>
					<td>You need many years of production experience behind you</td>
					<td>Mailcow</td>
					<td>in use since the end of 2016</td>
			</tr>
	</tbody>
</table>
<p>And if, while reading, you&rsquo;ve started to suspect that you don&rsquo;t actually want to maintain either
of them: a reputable mail host is a perfectly legitimate answer to this question. Deliverability
and reputation are continuous work, and that stays with you – no matter which software you pick.</p>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<p>The first two points apply to every self-hosted solution and cost the most time if you don&rsquo;t know
about them.</p>
<div class="troubleshoot not-prose">
<p><strong>Mail to the outside hangs, the logs show timeouts on port 25.</strong> This isn&rsquo;t the software, it&rsquo;s
the host: netcup&rsquo;s default policy &ldquo;netcup Mail block&rdquo; blocks 25, 465 and 587 outbound. You take
the policy off the server yourself in the SCP under <em>Firewall → Policys</em> – no support case, no
ticket. Affects Stalwart and Mailcow equally, see
<a href="/en/tutorials/netcup-firewall-setup/">netcup firewall setup</a>.</p>
<p><strong>The mail arrives but lands in spam everywhere.</strong> Then reverse DNS (PTR), SPF, DKIM or DMARC are
missing. This too is independent of the product and described step by step in
<a href="/en/tutorials/email-deliverability/">email deliverability</a>.</p>
<p><strong>Mailcow won&rsquo;t start, the logs report <code>address already in use</code> for port 80 or 443.</strong> A reverse
proxy is already running on the server and Mailcow&rsquo;s own nginx wants the same ports. Either give
Mailcow the host to itself, or bind its nginx to a local port as shown in step 2 and put it
behind the proxy.</p>
<p><strong>After the Mailcow setup all the other containers are suddenly gone.</strong> On an IPv6 host
<code>generate_config.sh</code> created <code>/etc/docker/daemon.json</code> and restarted Docker. That&rsquo;s intended
behaviour and prevents an open relay, but it hits the whole host. Containers with
<code>restart: unless-stopped</code> come back by themselves, the rest you start by hand.</p>
<p><strong>The mail client warns about a self-signed certificate <code>CN=rcgen self signed cert</code>.</strong> Stalwart
is still serving its placeholder. Check <code>docker compose logs traefik-certs-dumper</code> first: if it
says <code>/traefik/acme.json is not a file</code>, the path in the dumper doesn&rsquo;t point at the directory
holding Traefik&rsquo;s <code>acme.json</code>. If the files are in the volume, only the Stalwart restart is
missing – registered certificates are served only after that.</p>
<p><strong>Containers get killed by the kernel after a short while (OOM).</strong> The server is too small for the
stack you chose. With Mailcow and its 18 containers this happens considerably earlier than with
Stalwart; as a first measure turn ClamAV off in <code>mailcow.conf</code> with <code>SKIP_CLAMD=y</code>, since the
virus scanner is the biggest memory eater.</p>

</div>
<p class="mt-3 text-sm text-slate-500 dark:text-slate-400">
  Your problem is not listed?
  <a href="/en/errors/" class="font-medium text-paprika-700 hover:underline dark:text-paprika-400">Search all error messages →</a>
</p>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<p>The maintenance effort differs less than the container count suggests – it&rsquo;s just distributed
differently.</p>
<p><strong>Updates</strong> you take individually and deliberately with Stalwart by moving the version tag up;
plan a look at the release notes about once a month, even though something appears weekly. With
Mailcow, <code>update.sh</code> runs monthly and does everything at once; read the notes it prints, as larger
jumps occasionally list manual steps.</p>
<p><strong>Backups</strong> are plain with Stalwart: two volumes, <code>stalwart_config</code> and <code>stalwart_data</code>, and for a
consistent state you stop the container briefly. Mailcow brings its own <code>backup_and_restore.sh</code>
that pulls the many volumes and the database together. In both cases the result belongs <strong>off the
server</strong>, encrypted at best with <a href="/en/tutorials/restic-backups/">restic</a>.</p>
<p>Particularly important in both cases are the DKIM keys: if they&rsquo;re lost, your signatures break
after a restore until you set the DNS records again. And a backup you&rsquo;ve never restored is only a
hope – test the restore once on a test system.</p>
<p><strong>Reputation</strong> you keep an eye on regardless of the software: check occasionally whether your
server IP has ended up on a block list. A compromised mailbox sending spam ruins the reputation of
your entire domain quickly – and that&rsquo;s the one task neither of the two mail servers takes off
your hands.</p>
]]></content:encoded></item></channel></rss>