<?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>Mailserver – Serverküche</title><link>https://serverkueche.de/en/tags/mailserver/</link><description>Mailserver – 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>Tue, 11 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/mailserver/index.xml" rel="self" type="application/rss+xml"/><item><title>Stalwart: the lightweight mail server in a single container</title><link>https://serverkueche.de/en/tutorials/stalwart-mail-server/</link><pubDate>Tue, 11 Aug 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/stalwart-mail-server/</guid><description>Set up Stalwart Mail Server with Docker: SMTP, IMAP, JMAP and spam filter in a single, lean container – the modern alternative to Mailcow.</description><content:encoded><![CDATA[<p>Your own mail server doesn&rsquo;t have to consist of two dozen containers. <strong>Stalwart</strong> packs the complete mail server – SMTP, IMAP, JMAP, spam filter and storage – into <strong>a single</strong> container written in Rust. That makes it lean, fast and low-maintenance: the modern, lightweight alternative to heavyweights like <a href="/en/tutorials/mailcow-mail-server/">Mailcow</a>.</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>Mail servers stay demanding – no matter how lean
  </p>
  <div class="prose-kitchen text-sm">Stalwart is simpler to set up than classic stacks, but doesn&rsquo;t take the core task of a mail server operator off your hands: <strong>deliverability</strong>. Whether your mails arrive or land in spam is decided by SPF, DKIM, DMARC and a clean reverse-DNS entry – not the software. That&rsquo;s exactly what we cover in the follow-up tutorial. Whoever doesn&rsquo;t fancy this ongoing maintenance is often better off with a reputable mail host. That&rsquo;s an honest trade-off, not a weakness.</div>
</div>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end, <strong>Stalwart 0.16.14</strong> runs as a <strong>single Docker container</strong> on your server and provides everything a full-featured mail server needs:</p>
<ul>
<li><strong>SMTP</strong> (send/receive) on ports 25, 465 and 587,</li>
<li><strong>IMAP</strong> (993) and the modern <strong>JMAP</strong> for fast, efficient clients,</li>
<li><strong>ManageSieve</strong> (4190) for server-side filter rules,</li>
<li>a <strong>built-in spam and phishing filter</strong> (no separate Rspamd/ClamAV needed),</li>
<li><strong>integrated storage</strong> (RocksDB) – no external database,</li>
<li>a <strong>modern web interface</strong> to manage domains, accounts and settings.</li>
</ul>
<p>The big difference from Mailcow: it&rsquo;s <strong>one</strong> process, <strong>one</strong> container, a few hundred megabytes of image. That significantly reduces RAM demand, attack surface and maintenance effort. Tested with <strong>Docker 29</strong> on <strong>Debian 13</strong>.</p>
<p>Why is the lean approach worth it? A classic mail server stack consists of a good dozen services – Postfix, Dovecot, Rspamd, a database server, a virus scanner, Redis, a web server – all of which run individually and have to be updated and tuned to each other. Each of them is a potential source of error and a piece of attack surface. Stalwart bundles the same functionality into a single application written in Rust. Rust also brings memory safety out of the box – for software that accepts unfiltered data from the entire internet, that&rsquo;s a real security argument.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A <strong>dedicated server</strong> with <strong>Debian 13</strong> and <a href="/en/tutorials/install-docker/">Docker installed</a>, on which the mail and web ports are free (no reverse proxy occupying 25/443).</li>
<li>A <strong>domain</strong> whose <a href="/en/tutorials/connect-domain-to-server/">DNS you control yourself</a> – for the mail server&rsquo;s A record and the MX record.</li>
<li><strong>Outbound port 25 must be open.</strong> Many providers block it against spam; at netcup you enable it via a support ticket. Without an open port 25 you can&rsquo;t deliver mail to other servers.</li>
<li>A <strong>PTR/reverse-DNS entry</strong> for the server IP (in the netcup SCP) that points to your mail hostname.</li>
</ul>
<p>Stalwart is frugal – a small server is enough to start. How much your setup needs in total is estimated by the <a href="/en/server-calculator/">server calculator</a>.</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"
     data-track-content data-content-name="Affiliate-Box · /en/tutorials/stalwart-mail-server/" data-content-piece="VPS 1000 G12">
  <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">Stalwart is lean – the VPS 1000 is quite enough to start.</p>
    </div>
    <a href="https://www.netcup.com/en/server/vps/vps-1000-g12-iv-12m?ref=44083" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 1000 G12 · {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"><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 data-track-voucher="36nc17844976032"
        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-records">Step 1: Create the DNS records</h3>
<p>First set up the DNS entries – they need time to propagate. <code>YOUR_DOMAIN</code> is your mail domain, <code>YOUR_SERVER_IP</code> the server&rsquo;s IPv4 address:</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">mail.YOUR_DOMAIN.   A     YOUR_SERVER_IP
</span></span><span class="line"><span class="cl">YOUR_DOMAIN.        MX    10 mail.YOUR_DOMAIN.</span></span></code></pre></div>
</div>
<p>The <strong>MX record</strong> refers other mail servers to <code>mail.YOUR_DOMAIN</code>. This hostname is your server&rsquo;s identity. SPF, DKIM and DMARC we add in the deliverability tutorial – Stalwart generates the DKIM keys itself during setup.</p>
<h3 id="step-2-write-the-composeyaml">Step 2: Write the <code>compose.yaml</code></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 /opt/stalwart <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /opt/stalwart</span></span></code></pre></div>
</div>
<p>Create the file <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="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">stalwart</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">stalwartlabs/stalwart:v0.16.14</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">stalwart</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 class="nt">ports</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;25:25&#34;</span><span class="w">       </span><span class="c"># SMTP (server-to-server)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;465:465&#34;</span><span class="w">     </span><span class="c"># SMTPS (send, implicit TLS)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;587:587&#34;</span><span class="w">     </span><span class="c"># Submission (send, STARTTLS)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;143:143&#34;</span><span class="w">     </span><span class="c"># IMAP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;993:993&#34;</span><span class="w">     </span><span class="c"># IMAPS</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;4190:4190&#34;</span><span class="w">   </span><span class="c"># ManageSieve</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;8080:8080&#34;</span><span class="w">   </span><span class="c"># web interface &amp; JMAP (initial setup)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;443:443&#34;</span><span class="w">     </span><span class="c"># HTTPS (after setup, own certificate)</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">stalwart_data:/opt/stalwart</span><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="l">stalwart_data:</span></span></span></code></pre></div>
</div>
<p>The most important thing: <strong>one</strong> service, <strong>one</strong> volume for all data. Stalwart fetches its TLS certificate later itself via Let&rsquo;s Encrypt – you don&rsquo;t need a separate reverse proxy for that. Make sure port <strong>443</strong> is free on the host (Stalwart also uses it for the certificate retrieval, see step 4).</p>
<h3 id="step-3-start-the-container-and-get-the-startup-password">Step 3: Start the container and get the startup password</h3>
<p>Start 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">docker compose up -d</span></span></code></pre></div>
</div>
<p>On the very first start, Stalwart runs in <strong>bootstrap mode</strong>: there&rsquo;s no configuration yet, and port 8080 is open for the initial setup. In the process it generates a <strong>one-time administrator password</strong> that you read from the logs:</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 <span class="p">|</span> grep -A2 <span class="s2">&#34;temporary administrator&#34;</span></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">🔑 Stalwart bootstrap mode - temporary administrator account
</span></span><span class="line"><span class="cl">   password: &lt;ONE-TIME-PASSWORD&gt;</span></span></code></pre></div>
</div>
<p>Note this password – it&rsquo;s shown only once. (If you&rsquo;d rather set a fixed password, you can store a <code>STALWART_RECOVERY_ADMIN=admin:YOUR_PASSWORD</code> under <code>environment:</code> in the <code>compose.yaml</code>.)</p>
<h3 id="step-4-log-in-and-run-through-the-setup-wizard">Step 4: Log in and run through the setup wizard</h3>
<p>Open the web interface in the browser: <code>http://YOUR_SERVER_IP:8080/admin</code>. You land on the login. Log in as user <strong><code>admin</code></strong> with the one-time password from step 3 – the input is done in two steps (first username, then password):</p>
<p><figure class="my-6"><img src="/en/tutorials/stalwart-mail-server/stalwart-login_hu_6a97246da2b059b6.webp" srcset="/en/tutorials/stalwart-mail-server/stalwart-login_hu_f98585ce477f211.webp 480w, /en/tutorials/stalwart-mail-server/stalwart-login_hu_6a97246da2b059b6.webp 768w, /en/tutorials/stalwart-mail-server/stalwart-login_hu_f8ca7e9283e1c12d.webp 1200w, /en/tutorials/stalwart-mail-server/stalwart-login_hu_dc0b1cbec541fe27.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/stalwart-mail-server/stalwart-login_hu_bf8b30e5adbfa6dc.webp"
    alt="The login form of the Stalwart web interface with a field for the username." title="The login of the Stalwart web interface."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The login of the Stalwart web interface.</figcaption></figure></p>
<p>After logging in, the <strong>setup wizard</strong> greets you. In the first step you set the <strong>server identity</strong>:</p>
<ul>
<li><strong>Server Hostname:</strong> <code>mail.YOUR_DOMAIN</code> – the FQDN from your A record.</li>
<li><strong>Default Email Domain:</strong> <code>YOUR_DOMAIN</code> – your mail domain.</li>
<li><strong>Automatically Obtain TLS Certificate</strong> – leave enabled; Stalwart fetches the Let&rsquo;s Encrypt certificate itself. For that, <strong>port 443</strong> must be reachable from outside: Stalwart uses the <strong>TLS-ALPN-01</strong> challenge over 443 by default, it doesn&rsquo;t need port 80 for it.</li>
<li><strong>Generate Email Signing Keys</strong> – leave enabled; that generates your <strong>DKIM keys</strong> right away.</li>
</ul>
<p><figure class="my-6"><img src="/en/tutorials/stalwart-mail-server/stalwart-ersteinrichtung_hu_296e4e2801320a99.webp" srcset="/en/tutorials/stalwart-mail-server/stalwart-ersteinrichtung_hu_6414bf32979fd589.webp 480w, /en/tutorials/stalwart-mail-server/stalwart-ersteinrichtung_hu_296e4e2801320a99.webp 768w, /en/tutorials/stalwart-mail-server/stalwart-ersteinrichtung_hu_a27e4938f244fea7.webp 1200w, /en/tutorials/stalwart-mail-server/stalwart-ersteinrichtung_hu_8c9c2eb13d269dce.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/stalwart-mail-server/stalwart-ersteinrichtung_hu_832316163c36a06e.webp"
    alt="The Stalwart initial setup wizard with the fields for server hostname and default domain." title="Step 1 of the wizard: the server identity."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Step 1 of the wizard: the server identity.</figcaption></figure></p>
<p>Click through the further steps of the wizard:</p>
<ul>
<li><strong>Storage:</strong> the default <em>RocksDB</em> is an embedded key-value store and perfect for single-server setups – no external database server needed. Only for large, clustered installations do you reach for PostgreSQL or similar.</li>
<li><strong>Directory:</strong> the <em>internal directory</em> manages accounts and passwords directly in Stalwart. Whoever already runs a central LDAP/Active Directory can connect it here instead – for getting started you stay with the internal directory.</li>
<li><strong>Logging:</strong> defines how verbosely Stalwart logs. <code>Info</code> is a good compromise in everyday use; on problems you briefly turn up to <code>Debug</code>.</li>
<li><strong>DNS:</strong> for the start, manual management is enough – Stalwart shows you the records to create later under the respective domain.</li>
</ul>
<p>At the end, Stalwart generates your <strong>permanent administrator account</strong> and shows its password <strong>a single time</strong> – write it down immediately. From now on the bootstrap mode is over and port 8080 is no longer open for setup; you reach the console in future via HTTPS.</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>Already using a reverse proxy?
  </p>
  <div class="prose-kitchen text-sm">Unlike classic mail servers, Stalwart can run its <strong>web interface</strong> behind an existing <a href="/en/tutorials/traefik-reverse-proxy/">Traefik</a> without problems – then simply route the HTTP port <strong>8080</strong> there. The <strong>mail ports</strong> (25, 465, 587, 993 …) must still be directly at the server, though, because they don&rsquo;t speak HTTP and can&rsquo;t be routed through an HTTP proxy.</div>
</div>
<h3 id="step-5-create-a-domain-and-first-mailbox">Step 5: Create a domain and first mailbox</h3>
<p>After the setup you&rsquo;re in the <strong>management console</strong>. Via the left navigation you reach <em>Directory → Domains</em> and <em>Directory → Accounts</em>. If needed, create further domains and then your first mailbox:</p>
<p><figure class="my-6"><img src="/en/tutorials/stalwart-mail-server/stalwart-konsole_hu_2ef5c76733e28449.webp" srcset="/en/tutorials/stalwart-mail-server/stalwart-konsole_hu_9fc8af4a5ccc8ba2.webp 480w, /en/tutorials/stalwart-mail-server/stalwart-konsole_hu_2ef5c76733e28449.webp 768w, /en/tutorials/stalwart-mail-server/stalwart-konsole_hu_afc30161afa073ab.webp 1200w, /en/tutorials/stalwart-mail-server/stalwart-konsole_hu_19cde03b3a66ce0e.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/stalwart-mail-server/stalwart-konsole_hu_bd242b0b392c7da7.webp"
    alt="The Stalwart management console with the accounts overview." title="The management console: manage accounts."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The management console: manage accounts.</figcaption></figure></p>
<p>Via <strong>Create account</strong> you create a mailbox – email address, display name and a strong password (password manager!). This account can then log in via IMAP/JMAP and SMTP:</p>
<p><figure class="my-6"><img src="/en/tutorials/stalwart-mail-server/stalwart-konto-anlegen_hu_1a34a4be611e9c1c.webp" srcset="/en/tutorials/stalwart-mail-server/stalwart-konto-anlegen_hu_861e75c9986096d3.webp 480w, /en/tutorials/stalwart-mail-server/stalwart-konto-anlegen_hu_1a34a4be611e9c1c.webp 768w, /en/tutorials/stalwart-mail-server/stalwart-konto-anlegen_hu_6f6515fa35490bf6.webp 1200w, /en/tutorials/stalwart-mail-server/stalwart-konto-anlegen_hu_27303f9e2190956f.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/stalwart-mail-server/stalwart-konto-anlegen_hu_630cbc48b81a2a18.webp"
    alt="The form for creating a new mailbox in Stalwart." title="Create a new mailbox."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Create a new mailbox.</figcaption></figure></p>
<h3 id="step-6-set-it-up-in-a-mail-program">Step 6: Set it up in a mail program</h3>
<p>Enter the new account in your mail program. Stalwart supports <strong>autodiscovery</strong>, so usually the email address and password are enough. Manually you use:</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">IMAP:        mail.YOUR_DOMAIN, port 993, SSL/TLS
</span></span><span class="line"><span class="cl">SMTP:        mail.YOUR_DOMAIN, port 587, STARTTLS
</span></span><span class="line"><span class="cl">User:        the full email address</span></span></code></pre></div>
</div>
<p>Besides the tried-and-true IMAP, Stalwart also speaks <strong>JMAP</strong> – a modern, JSON-based protocol designed for today&rsquo;s networks. JMAP transfers only real changes instead of re-syncing whole folders, gets by with a single connection and is thus noticeably more sparing with battery and data – especially on the phone. If your mail client supports JMAP (e.g. the Thunderbird family in newer versions), you benefit directly; otherwise you stay with IMAP, which works just the same.</p>
<p>A mail from you to yourself should land in the inbox immediately – that proves that local delivery, IMAP and SMTP are in place. The acid test, though, is sending <strong>to the outside</strong> and receiving <strong>from the outside</strong>, and there deliverability decides.</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>Note
  </p>
  <div class="prose-kitchen text-sm">Check your deliverability after the setup with a service like <a href="https://www.mail-tester.com">mail-tester.com</a>: it rates SPF, DKIM, DMARC and reverse DNS. How you reach the full score is the topic of the deliverability tutorial.</div>
</div>
<h3 id="step-7-spam-filter-and-sieve-rules">Step 7: Spam filter and Sieve rules</h3>
<p>A big advantage of Stalwart: the <strong>spam and phishing filter is already built in</strong> and active – you don&rsquo;t have to run and maintain a separate Rspamd container. It rates incoming mails based on numerous features (reputation, SPF/DKIM/DMARC result, content heuristics) and marks or blocks suspicious ones. In the console under <em>Settings → Spam Filter</em> you can adjust the sensitivity and put senders on allow or block lists.</p>
<p>For your own rules on incoming and outgoing mail, Stalwart supports <strong>Sieve</strong> – the standardized filter language. With it you sort e.g. newsletters automatically into a folder or forward certain senders. You manage Sieve scripts server-side (via ManageSieve on port 4190) or directly in the web interface, so the rules take effect independent of the mail program – even when your computer is off.</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>Tip
  </p>
  <div class="prose-kitchen text-sm">Because the filter works server-side, it applies to <strong>all</strong> your devices at once. A Sieve rule created once or a spam decision takes effect on the phone just like on the laptop – unlike client-side filters you&rsquo;d have to set up anew on every device.</div>
</div>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong>The web interface on port 8080 doesn&rsquo;t respond.</strong> the container is still booting or port 8080 is
occupied/blocked. check <code>docker compose ps</code>, read <code>docker compose logs</code> and make sure the
<a href="/en/tutorials/firewall-ufw-setup/">firewall</a> lets port 8080 (and later 443) through.</p>
<p><strong>I missed the bootstrap password.</strong> it&rsquo;s only logged once. set a
<code>STALWART_RECOVERY_ADMIN=admin:YOUR_PASSWORD</code> under <code>environment:</code> in the <code>compose.yaml</code> and restart
with <code>docker compose up -d</code> – that gives you a fixed recovery-admin account.</p>
<p><strong>No TLS certificate, the HTTPS address shows a warning.</strong> Let&rsquo;s Encrypt can&rsquo;t reach your server on
port 443 (Stalwart uses the TLS-ALPN-01 challenge over 443), or the A record of <code>mail.YOUR_DOMAIN</code>
is wrong. check the A record and open port 443 in the firewall – Stalwart retries the certificate
request automatically afterwards.</p>
<p><strong>Mails to the outside stay stuck, logs show timeouts on port 25.</strong> your provider blocks outbound
SMTP traffic on port 25. have port 25 enabled outbound at the provider (at netcup via a ticket).
Without it, no delivery to other servers is possible.</p>
<p><strong>Other servers don&rsquo;t accept your mails or they land in spam.</strong> missing or wrong PTR entry, no
SPF/DKIM/DMARC. that&rsquo;s not a Stalwart error but a matter of DNS/reputation configuration – see the
deliverability tutorial.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<p><strong>Updates.</strong> Stalwart is a single container – an update is correspondingly simple:</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>Before bigger jumps, read the release notes on <a href="https://github.com/stalwartlabs/stalwart/releases">GitHub</a>; stay on a specific version tag (like <code>v0.16.14</code> here) instead of <code>latest</code> so updates remain reproducible.</p>
<p><strong>Backups.</strong> All data – messages, accounts, configuration and the <strong>DKIM keys</strong> – lives in the <code>stalwart_data</code> volume. Back up this volume regularly away from the server, cleanest encrypted with <a href="/en/tutorials/restic-backups/">Restic</a>. Because Stalwart writes the data during operation, you back up most consistently by briefly stopping the container (<code>docker compose stop</code>), backing up the volume and starting again. The DKIM keys are especially important: if they&rsquo;re lost, your signatures break after a restore until you reset the DNS records. A backup you&rsquo;ve never restored is just a hopeful guess – test the restoration once on a test system.</p>
<p><strong>Keep an eye on reputation.</strong> Occasionally check whether your server IP has landed on a block list, and take a look at the Stalwart console under <em>Observability</em>. A compromised mailbox that sends spam quickly ruins the reputation of your entire domain.</p>
]]></content:encoded></item><item><title>Your own mail server with Mailcow: setup from scratch</title><link>https://serverkueche.de/en/tutorials/mailcow-mail-server/</link><pubDate>Mon, 10 Aug 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/mailcow-mail-server/</guid><description>Set up Mailcow (dockerized) on your own server: Postfix, Dovecot, Rspamd and SOGo webmail under your domain – a complete mail server stack with HTTPS.</description><content:encoded><![CDATA[<p>Your email is the backbone of your digital identity – password resets, invoices, contracts all run through it. Yet for most people it sits at a corporation in the US. With <strong>Mailcow</strong> you bring the complete mail server onto your own server: Postfix, Dovecot, spam filter and webmail, neatly packed in Docker. This tutorial gets the stack running – the tricky <strong>deliverability</strong> (SPF, DKIM, DMARC, reverse DNS) we handle afterwards in the <a href="/en/tutorials/email-deliverability/" data-track-event="Navigation|Tutorial-Link|/en/tutorials/email-deliverability/ ← /en/tutorials/mailcow-mail-server/">deliverability recipe</a>.</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>Running your own mail server is work – be honest with yourself
  </p>
  <div class="prose-kitchen text-sm">A mail server is not &ldquo;set up once and forget&rdquo;. From now on you&rsquo;re responsible for deliverability, reputation, backups and updates. If your mails land in spam or the server fails, it affects your most important communication. If you don&rsquo;t want to maintain this regularly, a reputable mail host is often the better choice – that&rsquo;s no weakness but an honest trade-off. Whoever really wants control is in the right place here.</div>
</div>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end, a <strong>complete mail server stack</strong> runs on your server, reachable as webmail at <code>https://mail.YOUR_DOMAIN</code> with a valid Let&rsquo;s Encrypt certificate. Included are:</p>
<ul>
<li><strong>Postfix</strong> as the SMTP server (send/receive),</li>
<li><strong>Dovecot</strong> for IMAP/POP3 (retrieval by mail programs),</li>
<li><strong>Rspamd</strong> as a modern spam and signature filter,</li>
<li><strong>SOGo</strong> as webmail including calendar and contacts (CalDAV/CardDAV),</li>
<li>an <strong>admin interface</strong> to create domains, mailboxes and aliases.</li>
</ul>
<p>The whole thing comes as <strong>mailcow-dockerized</strong> – a ready-bundled Compose project of <strong>18 containers</strong> that makes the notoriously complicated mail server setup manageable. We build on <strong>Debian 13</strong> with <strong>Docker 29.6</strong> and <strong>Compose v5.3</strong>; mailcow is a <em>rolling release</em> maintained through monthly tags (you update via <code>update.sh</code>, there&rsquo;s no classic version number). We tested against the <strong>2026-07b</strong> state with <strong>Postfix 3.10.12</strong>, <strong>Dovecot 2.3.21.1</strong>, <strong>Rspamd 4.1.4</strong> and <strong>SOGo 5.12.10</strong>.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-paprika-400 bg-paprika-50 dark:border-paprika-700 dark:bg-paprika-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🔥</span>Mailcow needs its own server
  </p>
  <div class="prose-kitchen text-sm">Unlike most recipes here, mailcow does <strong>not</strong> run behind the shared <a href="/en/tutorials/traefik-reverse-proxy/">Traefik reverse proxy</a>. It brings its <strong>own</strong> web server and occupies ports <strong>80/443</strong> itself, plus the mail ports <strong>25, 465, 587, 143, 993</strong> (and more). So run mailcow on a <strong>dedicated server</strong> where nothing else needs these ports – not on the same host as your Traefik stack.</div>
</div>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A <strong>dedicated server</strong> with <strong>Debian 13</strong> on which <a href="/en/tutorials/install-docker/">Docker is installed</a> and that is <strong>only</strong> for mailcow (ports 80/443 and the mail ports free).</li>
<li><strong>At least 6 GB RAM</strong> – the spam filter (Rspamd) and the optional virus scanner (ClamAV) need room. With less it gets sluggish or ClamAV has to go.</li>
<li>A <strong>domain</strong> whose <a href="/en/tutorials/connect-domain-to-server/">DNS you control yourself</a>. Without matching records (especially <strong>MX</strong> and <strong>reverse DNS</strong>), no other mail server accepts your mail.</li>
<li><strong>Outbound port 25 must be open.</strong> Many providers block it by default against spam – at netcup you enable it via a support ticket. Without an open port 25 you can deliver <strong>no</strong> mail to other servers.</li>
<li>A <strong>PTR/reverse-DNS entry</strong> for the server IP, which you set in the provider panel (at netcup in the SCP). It must point to <code>mail.YOUR_DOMAIN</code>.</li>
</ul>
<p>Mailcow is RAM-hungry, so we recommend at least the VPS 2000. What your specific need is, the <a href="/en/server-calculator/">server calculator</a> estimates for you.</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"
     data-track-content data-content-name="Affiliate-Box · /en/tutorials/mailcow-mail-server/" data-content-piece="VPS 2000 G12">
  <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</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">8 vCores · 16 GB RAM · 512 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">from €19.24/month</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">For mailcow including spam and virus filter it should be 8 GB RAM.</p>
    </div>
    <a href="https://www.netcup.com/en/server/vps/vps-2000-g12-iv-12m?ref=44083" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 2000 G12 · {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"><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 data-track-voucher="36nc17844976032"
        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-records">Step 1: Create the DNS records</h3>
<p>Before you install anything, set up the DNS entries – they need time to propagate, and mailcow fetches the TLS certificate for the hostname at startup. At your DNS provider (see <a href="/en/tutorials/connect-domain-to-server/">connecting a domain to your server</a>), create these entries. <code>YOUR_DOMAIN</code> is your mail domain (e.g. <code>example.com</code>), <code>YOUR_SERVER_IPV4</code> the server&rsquo;s IPv4 address:</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">mail.YOUR_DOMAIN.   A      YOUR_SERVER_IPV4
</span></span><span class="line"><span class="cl">mail.YOUR_DOMAIN.   AAAA   YOUR_SERVER_IPV6      (if you use IPv6)
</span></span><span class="line"><span class="cl">YOUR_DOMAIN.        MX     10 mail.YOUR_DOMAIN.</span></span></code></pre></div>
</div>
<p>The <strong>MX record</strong> tells other mail servers: &ldquo;Mail for <code>@YOUR_DOMAIN</code> is accepted by <code>mail.YOUR_DOMAIN</code>.&rdquo; The hostname <code>mail.YOUR_DOMAIN</code> is the <strong>FQDN of the mail server</strong> – not to be confused with the mail domain itself. The further records for deliverability (SPF, DKIM, DMARC) and the <strong>PTR entry</strong> we cover in detail in the follow-up tutorial on deliverability; the PTR entry should be set already now, though, because many servers otherwise don&rsquo;t accept at all.</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>Tip
  </p>
  <div class="prose-kitchen text-sm">Set the <strong>TTL</strong> of the records low before the migration (e.g. 300 seconds). Then later corrections take effect quickly while you test. After the fine-tuning you can raise them again.</div>
</div>
<h3 id="step-2-install-prerequisites-on-the-server">Step 2: Install prerequisites on the server</h3>
<p>Log in via SSH to the <a href="/en/tutorials/harden-ssh/">hardened server</a>. Mailcow&rsquo;s config generator needs <code>git</code> and <code>jq</code> besides Docker – the latter is easily forgotten, and the generator otherwise aborts with <code>Cannot find command 'jq'</code>. Install both:</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">sudo apt update <span class="o">&amp;&amp;</span> sudo apt install -y git jq</span></span></code></pre></div>
</div>
<p>Check that Docker and the Compose plugin 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 --version <span class="o">&amp;&amp;</span> docker compose version</span></span></code></pre></div>
</div>
<p>You should see two version lines – for us <code>Docker version 29.6.1</code> and <code>Docker Compose version v5.3.1</code>. Mailcow requires at least <strong>Docker 24</strong>; below that the config generator aborts. If something&rsquo;s missing, catch up on the <a href="/en/tutorials/install-docker/">Docker setup</a> before continuing.</p>
<h3 id="step-3-clone-and-configure-mailcow">Step 3: Clone and configure Mailcow</h3>
<p>Mailcow is delivered as a Git repository and <strong>not</strong> maintained via a single image tag. Clone it to <code>/opt</code> and change into it. The <code>umask 0022</code> is important so the generated files get the right permissions:</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">sudo su
</span></span><span class="line"><span class="cl"><span class="nb">umask</span> <span class="m">0022</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /opt
</span></span><span class="line"><span class="cl">git clone https://github.com/mailcow/mailcow-dockerized
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> mailcow-dockerized</span></span></code></pre></div>
</div>
<p>Now you generate the central configuration file <code>mailcow.conf</code> with the interactive generator:</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">./generate_config.sh</span></span></code></pre></div>
</div>
<p>It first checks whether your server&rsquo;s IP is on the <strong>Spamhaus bad ASN list</strong> – a useful early warning, because hardly any mail server accepts post from some networks:</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">Detecting if your IP is listed on Spamhaus Bad ASN List...
</span></span><span class="line"><span class="cl">Check completed! Your IP is clean</span></span></code></pre></div>
</div>
<p>Then it asks you these questions in turn:</p>
<ul>
<li><strong>Mail server hostname (FQDN):</strong> Enter <code>mail.YOUR_DOMAIN</code> here – the server&rsquo;s <strong>hostname</strong>, not your mail domain. The value must match the A record from step 1.</li>
<li><strong>Timezone:</strong> usually already correctly pre-filled (e.g. <code>Europe/Berlin</code>) – confirm with Enter.</li>
<li><strong>Choose the Branch with it&rsquo;s number [1/2/3]:</strong> Choose <strong><code>1</code></strong> for the <code>master</code> branch – that&rsquo;s the stable, recommended state. <code>2</code> is <code>nightly</code> (testing), <code>3</code> the deprecated <code>legacy</code> branch.</li>
</ul>
<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>You only see the ClamAV question on small servers
  </p>
  <div class="prose-kitchen text-sm">The generator asks about the virus scanner (<em>Do you want to disable ClamAV now?</em>) <strong>only</strong> if the server has <strong>at most 2.5 GiB of RAM</strong>. On a machine with 6–8 GB it quietly sets <code>SKIP_CLAMD=n</code>, so ClamAV runs from the start. You can switch it off any time by setting <code>SKIP_CLAMD=y</code> in <code>mailcow.conf</code> and restarting the stack.</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>IPv6 hosts: Docker gets restarted
  </p>
  <div class="prose-kitchen text-sm"><p>If the generator finds a working <strong>IPv6 connection</strong> (default route plus a reachable peer), Docker has to speak IPv6 too – otherwise mailcow could become an <strong>open relay</strong>. If <code>/etc/docker/daemon.json</code> is missing, it therefore asks:</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">/etc/docker/daemon.json not found. Create it with IPv6 settings? [Y/n]</span></span></code></pre></div>
</div>
<p>Confirm with Enter and it creates the file itself (on Docker ≥ 28 <code>{&quot;ipv6&quot;: true}</code> is enough) and restarts Docker <strong>right away</strong>. This is exactly why mailcow belongs on its <strong>own</strong> server: that restart takes every other container on the host down with it. Answer <code>n</code> and the generator aborts, leaving you to add the setting by hand. If you deliberately want to run without IPv6, set <code>ENABLE_IPV6=false</code> in <code>mailcow.conf</code> later.</p>
</div>
</div>
<p>Afterwards take a look at the generated <code>mailcow.conf</code>. The most important values are at the top:</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">grep -E <span class="s2">&#34;^(MAILCOW_HOSTNAME|TZ|HTTP_PORT|HTTPS_PORT|SKIP_CLAMD|ENABLE_IPV6)=&#34;</span> mailcow.conf</span></span></code></pre></div>
</div>
<p>On our test server it looked like this – <code>SKIP_CLAMD=n</code> means &ldquo;ClamAV is running&rdquo;:</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">MAILCOW_HOSTNAME=mail.YOUR_DOMAIN
</span></span><span class="line"><span class="cl">HTTP_PORT=80
</span></span><span class="line"><span class="cl">HTTPS_PORT=443
</span></span><span class="line"><span class="cl">TZ=Europe/Berlin
</span></span><span class="line"><span class="cl">SKIP_CLAMD=n
</span></span><span class="line"><span class="cl">ENABLE_IPV6=true</span></span></code></pre></div>
</div>
<p>The file also contains all <strong>port assignments</strong>. In the standard configuration mailcow claims these host ports:</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">25    SMTP     (server-to-server delivery)
</span></span><span class="line"><span class="cl">465   SMTPS    (send with implicit TLS)
</span></span><span class="line"><span class="cl">587   Submission (send by mail programs, STARTTLS)
</span></span><span class="line"><span class="cl">143   IMAP  /  993 IMAPS   (retrieval by mail programs)
</span></span><span class="line"><span class="cl">110   POP3  /  995 POP3S
</span></span><span class="line"><span class="cl">4190  Sieve    (server-side filter rules)
</span></span><span class="line"><span class="cl">80    HTTP  /  443 HTTPS   (webmail + admin + ACME)</span></span></code></pre></div>
</div>
<p>These ports must be <strong>free</strong> on the host – hence the dedicated server. If a reverse proxy already runs on 80/443 there, mailcow collides with it.</p>
<h3 id="step-4-open-the-firewall">Step 4: Open the firewall</h3>
<p>Your <a href="/en/tutorials/firewall-ufw-setup/">UFW firewall</a> must let the mail and web ports through. Besides SSH, you allow:</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">sudo ufw allow 25/tcp
</span></span><span class="line"><span class="cl">sudo ufw allow 80,443/tcp
</span></span><span class="line"><span class="cl">sudo ufw allow 465,587/tcp
</span></span><span class="line"><span class="cl">sudo ufw allow 143,993/tcp
</span></span><span class="line"><span class="cl">sudo ufw allow 110,995/tcp
</span></span><span class="line"><span class="cl">sudo ufw allow 4190/tcp</span></span></code></pre></div>
</div>
<p>If you additionally use the <a href="/en/tutorials/netcup-firewall-setup/">netcup firewall in the SCP</a>, allow the same ports there too – otherwise the perimeter firewall blocks the traffic before it reaches the server.</p>
<h3 id="step-5-start-the-stack">Step 5: Start the stack</h3>
<p>Now you download the images and start the stack. Expect <strong>about 4.5 GB</strong> on disk – the stack consists of 18 services, the biggest being SOGo, Dovecot and Postfix:</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 pull
</span></span><span class="line"><span class="cl">docker compose up -d</span></span></code></pre></div>
</div>
<p>The first boot takes a few minutes – the database, search index and the individual services initialize. Watch the progress with:</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>The <code>STATUS</code> column shows <code>Up …</code> for every service; the containers with their own health check (such as <code>unbound-mailcow</code> and <code>clamd-mailcow</code>) additionally report <code>(healthy)</code>. During this time, mailcow fetches the <strong>Let&rsquo;s Encrypt certificate</strong> for <code>mail.YOUR_DOMAIN</code> in the background via its built-in ACME client. For that, the A record and port 80 must be reachable from outside – if that&rsquo;s not the case, the <code>acme-mailcow</code> container stays in a retry loop and you see it in its logs (<code>docker compose logs acme-mailcow</code>).</p>
<h3 id="step-6-first-login-to-the-admin-interface">Step 6: First login to the admin interface</h3>
<p>Open <code>https://mail.YOUR_DOMAIN</code> in the browser. You land in the mailcow UI. Log in with the <strong>default credentials</strong>:</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">User:      admin
</span></span><span class="line"><span class="cl">Password:  moohoo</span></span></code></pre></div>
</div>
<p>If the form shows &ldquo;Sign in as user&rdquo;, click <strong>&ldquo;Sign in as admin&rdquo;</strong> at the bottom – the login form on the home page otherwise logs you in as a mailbox user, not as an administrator.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-paprika-400 bg-paprika-50 dark:border-paprika-700 dark:bg-paprika-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🔥</span>Change the default password immediately
  </p>
  <div class="prose-kitchen text-sm"><code>admin</code> / <code>moohoo</code> is publicly known. <strong>Change the admin password immediately</strong> under <em>System → Configuration → Access</em>: in the <strong>Administrators</strong> table, click <strong>Edit</strong> next to the <code>admin</code> user. In the same tab, enable <strong>two-factor authentication</strong> (TOTP or WebAuthn) right away. As long as the default password applies, your mail server is wide open like a barn door.</div>
</div>
<p>After logging in you land in the mailcow dashboard with the overview of your services:</p>
<p><figure class="my-6"><img src="/en/tutorials/mailcow-mail-server/mailcow-dashboard_hu_8014b3cfa3617aca.webp" srcset="/en/tutorials/mailcow-mail-server/mailcow-dashboard_hu_6fe3e0a2a9604cdf.webp 480w, /en/tutorials/mailcow-mail-server/mailcow-dashboard_hu_8014b3cfa3617aca.webp 768w, /en/tutorials/mailcow-mail-server/mailcow-dashboard_hu_6a055ddf84b66fec.webp 1200w, /en/tutorials/mailcow-mail-server/mailcow-dashboard_hu_4d842187c17f7bfe.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/mailcow-mail-server/mailcow-dashboard_hu_c21d6d42804f4104.webp"
    alt="The mailcow dashboard after the first login with a system overview." title="The mailcow dashboard right after login."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The mailcow dashboard right after login.</figcaption></figure></p>
<h3 id="step-7-create-a-mail-domain-and-first-mailbox">Step 7: Create a mail domain and first mailbox</h3>
<p>In mailcow you separate <strong>domain</strong> (e.g. <code>example.com</code>) and <strong>mailbox</strong> (e.g. <code>contact@example.com</code>).</p>
<p>Via <strong>E-Mail → Configuration</strong> in the top navigation bar you manage domains and mailboxes in separate tabs:</p>
<p><figure class="my-6"><img src="/en/tutorials/mailcow-mail-server/mailcow-mail-setup_hu_ee7870251e7c4932.webp" srcset="/en/tutorials/mailcow-mail-server/mailcow-mail-setup_hu_7b0e5f56d73a2bd6.webp 480w, /en/tutorials/mailcow-mail-server/mailcow-mail-setup_hu_ee7870251e7c4932.webp 768w, /en/tutorials/mailcow-mail-server/mailcow-mail-setup_hu_bbf8b0ff34a1ea4e.webp 1200w, /en/tutorials/mailcow-mail-server/mailcow-mail-setup_hu_d3d79d04b8538cb5.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/mailcow-mail-server/mailcow-mail-setup_hu_18a26bfea89c5845.webp"
    alt="The mail setup interface of mailcow with tabs for domains and mailboxes." title="E-Mail → Configuration: manage domains and mailboxes."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">E-Mail → Configuration: manage domains and mailboxes.</figcaption></figure></p>
<ol>
<li>
<p>On the <strong>Domains</strong> tab, click <strong>Add domain</strong> and enter your domain <code>YOUR_DOMAIN</code>. The default values (mailbox and alias quotas) are fine for the start. Save with <strong>&ldquo;Add domain and restart SOGo&rdquo;</strong> at the bottom – not with &ldquo;Add domain only&rdquo;. Why, see the box below.</p>
<p><figure class="my-6"><img src="/en/tutorials/mailcow-mail-server/mailcow-domain-anlegen_hu_b4e814a01e088b7a.webp" srcset="/en/tutorials/mailcow-mail-server/mailcow-domain-anlegen_hu_6797ae0d500fc55f.webp 480w, /en/tutorials/mailcow-mail-server/mailcow-domain-anlegen_hu_b4e814a01e088b7a.webp 768w, /en/tutorials/mailcow-mail-server/mailcow-domain-anlegen_hu_1c0cf036d4519ce6.webp 1200w, /en/tutorials/mailcow-mail-server/mailcow-domain-anlegen_hu_1dfe7c2671827c6d.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/mailcow-mail-server/mailcow-domain-anlegen_hu_7f3e36bf7ca0ce0b.webp"
    alt="The dialog for adding a mail domain in mailcow." title="Create a new mail domain."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Create a new mail domain.</figcaption></figure></p>
</li>
<li>
<p>Switch to the <strong>Mailboxes</strong> tab and click <strong>Add mailbox</strong> – username, domain, full name and a <strong>strong</strong> password (password manager!). This mailbox can then log in to the webmail and via IMAP/SMTP.</p>
<p><figure class="my-6"><img src="/en/tutorials/mailcow-mail-server/mailcow-mailbox-anlegen_hu_d5141bd3c844c32b.webp" srcset="/en/tutorials/mailcow-mail-server/mailcow-mailbox-anlegen_hu_dc36f0654a230a63.webp 480w, /en/tutorials/mailcow-mail-server/mailcow-mailbox-anlegen_hu_d5141bd3c844c32b.webp 768w, /en/tutorials/mailcow-mail-server/mailcow-mailbox-anlegen_hu_8cdbd935d19994f7.webp 1200w, /en/tutorials/mailcow-mail-server/mailcow-mailbox-anlegen_hu_3393c2a56dbfed30.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/mailcow-mail-server/mailcow-mailbox-anlegen_hu_28dc86b765b98908.webp"
    alt="The dialog for creating a new mailbox in mailcow." title="Create a new mailbox."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Create a new mailbox.</figcaption></figure></p>
</li>
</ol>
<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>Without a SOGo restart the webmail stays shut
  </p>
  <div class="prose-kitchen text-sm">SOGo reads the list of mail domains <strong>only when its container starts</strong>. If you create the first domain with &ldquo;Add domain only&rdquo;, SOGo logs <code>No authentication sources defined - nobody will be able to login</code>, and every webmail login ends on a bare <strong><code>Unauthorized</code></strong> page – even though password and mailbox are correct. Hence the second button; after the fact you get there via <strong>E-Mail → Restart SOGo</strong>. If a failed login has already been cached, restarting memcached as well helps (see &ldquo;When things go wrong&rdquo;).</div>
</div>
<p>After creating the domain, mailcow shows you the <strong>DKIM key</strong> and the recommended DNS records under the domain entry. You need those for deliverability – more on that shortly.</p>
<h3 id="step-8-test-the-webmail">Step 8: Test the webmail</h3>
<p>Open <code>https://mail.YOUR_DOMAIN/SOGo/</code> (or click <strong>Apps → Webmail</strong> at the top right in the mailcow UI). If you aren&rsquo;t signed in yet, you first land on the mailcow &ldquo;User Login&rdquo; mask – log in there with your <strong>full mail address</strong> and the mailbox password and you are passed straight through to the SOGo webmailer with inbox, calendar and contacts. A test mail from you to yourself should be in the inbox immediately – that proves that <strong>local delivery</strong>, IMAP and webmail work.</p>
<p>The real acid test, however, is sending <strong>to the outside</strong> and receiving <strong>from the outside</strong> – and that&rsquo;s exactly where it&rsquo;s decided whether your mails land in the recipient&rsquo;s inbox or spam folder. That depends on SPF, DKIM, DMARC and the PTR entry and is substantial enough for its <a href="/en/tutorials/email-deliverability/" data-track-event="Navigation|Tutorial-Link|/en/tutorials/email-deliverability/ ← /en/tutorials/mailcow-mail-server/">own recipe</a>.</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>Note
  </p>
  <div class="prose-kitchen text-sm">If you want to check your deliverability right away, send a mail after the setup to a test service like <a href="https://www.mail-tester.com">mail-tester.com</a> – it rates SPF, DKIM, DMARC, reverse DNS and block lists. Below 10/10 there&rsquo;s room for improvement; how you reach the full score is the topic of the deliverability tutorial.</div>
</div>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong><code>generate_config.sh</code> aborts with <code>Cannot find command 'jq'</code>.</strong> the <code>jq</code> package is missing. <code>sudo apt install -y jq</code> and start the generator again.</p>
<p><strong>The generator reports &ldquo;User declined to create daemon.json&rdquo; and aborts.</strong> On hosts with active
IPv6 you answered the question about the IPv6-capable Docker configuration with <code>n</code> – without it
mailcow won&rsquo;t run there, and an open relay looms. Create <code>/etc/docker/daemon.json</code> containing
<code>{&quot;ipv6&quot;: true}</code> (Docker 28 and newer), restart Docker with <code>sudo systemctl restart docker</code> and run
<code>./generate_config.sh</code> again – or simply confirm the question with Enter on the second attempt, then
the generator does both itself.</p>
<p><strong>The webmail only answers <code>Unauthorized</code>.</strong> SOGo doesn&rsquo;t know your mail domain: it reads the domain
list exclusively at startup and wasn&rsquo;t restarted after the domain was created – its logs say <code>No authentication sources defined - nobody will be able to login</code>. Restart SOGo via <strong>E-Mail → Restart
SOGo</strong>. If that doesn&rsquo;t help, a failed login is still stuck in the cache: <code>docker compose restart memcached-mailcow sogo-mailcow</code>.</p>
<p><strong>No certificate, <code>acme-mailcow</code> keeps restarting.</strong> Let&rsquo;s Encrypt can&rsquo;t reach your server on port
80, or the A record of <code>mail.YOUR_DOMAIN</code> is wrong. check with <code>docker compose logs acme-mailcow</code>
whether the hostname and IP match, and that port 80 is open through UFW <strong>and</strong> the netcup firewall.</p>
<p><strong>Mails to the outside aren&rsquo;t delivered, logs show timeouts on port 25.</strong> Your provider blocks
outbound SMTP traffic on port 25 (common as spam protection). have port 25 enabled outbound at the
provider (at netcup via a support ticket). Without it you can&rsquo;t send mail to other servers –
receiving and local delivery still work.</p>
<p><strong>Ports 80/443 can&rsquo;t be bound (<code>address already in use</code>).</strong> A web server or reverse proxy (e.g.
Traefik) already runs on the host. mailcow belongs on its own server. At a pinch,
<code>HTTP_PORT</code>/<code>HTTPS_PORT</code> in <code>mailcow.conf</code> can be bent and mailcow put behind a proxy – but that&rsquo;s
advanced and no solution for the mail ports anyway.</p>
<p><strong>Containers start slowly or are killed by the kernel (OOM).</strong> Too little RAM. go to at least 6–8 GB
or disable ClamAV in <code>mailcow.conf</code> (<code>SKIP_CLAMD=y</code>) – the virus scanner is the biggest memory
eater.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<p>A mail server lives off maintenance – plan it firmly.</p>
<p><strong>Updates.</strong> Mailcow brings its own update script. Run it regularly (about monthly); it updates the Compose file and all images to the latest state:</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 automatically makes a backup of the configuration before the update and restarts the stack. Read the release notes shown – on bigger jumps, manual steps are occasionally listed there.</p>
<p><strong>Backups.</strong> Your mails, the database and the crypto keys (DKIM!) live in Docker volumes. Mailcow brings a ready-made backup script for this:</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/helper-scripts
</span></span><span class="line"><span class="cl">sudo <span class="nv">BACKUP_LOCATION</span><span class="o">=</span>/opt/mailcow-backups ./backup_and_restore.sh backup all</span></span></code></pre></div>
</div>
<p>Then back up the target directory <strong>away from the server</strong> – cleanest encrypted with <a href="/en/tutorials/restic-backups/">Restic</a>. Especially important are the <strong>DKIM keys</strong>: if they&rsquo;re lost, your signatures break after a restore until you reset the DNS records. A backup you&rsquo;ve never restored is just a hopeful guess – test the restoration once on a test system.</p>
<p><strong>Keep an eye on reputation.</strong> Occasionally check whether your server IP has landed on a block list (e.g. via one of the common block-list checkers) and take a look at the Rspamd dashboard in the mailcow UI now and then. A hacked or misconfigured mailbox that sends spam quickly ruins the reputation of your entire domain.</p>
]]></content:encoded></item></channel></rss>