Kennisbank › Email › Installing Stalwart Mail Server on a VPS
email gevorderd 04 October 2026

Installing Stalwart Mail Server on a VPS

Installing Stalwart Mail Server on a VPS Stalwart is a single-binary mail server written in Rust that speaks SMTP, IMAP, JMAP, and ManageSieve without a

The reason to pick Stalwart over Postfix plus Dovecot plus a sieve daemon plus a spam filter is not that it does more. It is that it does the same work with one config file and one systemd unit. The cost is that you are betting on a younger project with a smaller community. If something breaks at 2 a.m., the search results are thinner than they are for Postfix. That trade is worth knowing about before you start, not after.

Requirements

  • A VPS with at least 2 GB RAM and 20 GB disk. Stalwart itself is light, but the full-text search index and RocksDB grow with your mail volume.
  • Debian 12 (Bookworm) or Ubuntu 24.04 LTS. The commands below assume `apt` and systemd.
  • Root access or a user in the `sudo` group. If you are not in it yet, add yourself:
```bash sudo adduser "$USER" sudo ```

Log out and back in for the group change to take effect. Without this, every command below fails with a permission error and you will spend twenty minutes wondering why.

  • A domain you control, with the ability to add DNS records. You need an `A` record for `mail.example.com` pointing at your VPS IP, and later `MX`, `SPF`, `DKIM`, and `DMARC` records.
  • Ports 25, 465, 587, 143, 993 open in your VPS firewall. Port 25 outbound is blocked by most budget providers. Check before you build anything; if it is blocked, you can receive mail but not send it directly, and you will need a relay.

Step 1 — Update the system and install dependencies

Start from a patched system. The packages below cover TLS certificates, DNS lookups, and the tools Stalwart's install script expects.

```bash sudo apt update && sudo apt full-upgrade -y sudo apt install -y curl wget tar ca-certificates gnupg ufw ```

`ca-certificates` matters more than it looks. Without it, Stalwart cannot validate the TLS certificates of upstream servers when it fetches ACME challenges or talks to external services. The failure mode is a vague TLS error, not a clear "missing CA bundle".

Step 2 — Configure the firewall

Open only the ports mail needs. Everything else stays closed.

```bash sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 25/tcp sudo ufw allow 465/tcp sudo ufw allow 587/tcp sudo ufw allow 143/tcp sudo ufw allow 993/tcp sudo ufw enable ```

Port 25 is for server-to-server delivery, 465 and 587 are submission with TLS, 143 is IMAP with STARTTLS, 993 is IMAP over TLS. You can drop 143 if you only want implicit TLS, but old clients will complain. Leaving 22 open is obvious until you forget it and lock yourself out.

Step 3 — Create the Stalwart system user and directories

Stalwart should not run as root. Create a dedicated user and give it one directory tree to own.

```bash sudo useradd --system --home /opt/stalwart --shell /usr/sbin/nologin stalwart sudo mkdir -p /opt/stalwart/etc /opt/stalwart/data /opt/stalwart/log sudo chown -R stalwart:stalwart /opt/stalwart ```

The `etc` directory holds the config, `data` holds the RocksDB store and blobs, `log` holds logs. Keeping them separate means you can back up `etc` and `data` independently, which you will want the first time you upgrade and something goes sideways.

Step 4 — Download the Stalwart binary

Grab the current release for your architecture. As of this writing that is 0.10.x.

```bash cd /tmp curl -L -o stalwart.tar.gz https://github.com/stalwartlabs/mail-server/releases/download/v0.10.7/stalwart-x86_64-unknown-linux-gnu.tar.gz tar -xzf stalwart.tar.gz sudo mv stalwart /opt/stalwart/stalwart sudo chown stalwart:stalwart /opt/stalwart/stalwart sudo chmod 755 /opt/stalwart/stalwart ```

Check the release page for the current version before you paste the URL. The project moves fast and pinned URLs go stale. If `curl` returns a 404, the version number is wrong, not your network.

Step 5 — Write the configuration file

Stalwart 0.10 uses a JSON config. This is the minimum that gives you working SMTP, IMAP, and automatic TLS via Let's Encrypt.

```bash sudo tee /opt/stalwart/etc/config.json > /dev/null <<'EOF' { "@type": "Application", "server": { "listener": { "smtp": { "bind": ["0.0.0.0:25"], "protocol": "smtp" }, "submissions": { "bind": ["0.0.0.0:465"], "protocol": "smtp", "tls": { "@type": "AcmeTls" } }, "imaptls": { "bind": ["0.0.0.0:993"], "protocol": "imap", "tls": { "@type": "AcmeTls" } } }, "hostname": "mail.example.com" }, "storage": { "directory": "/opt/stalwart/data" }, "tracer": { "stdout": { "@type": "Stdout", "level": "info", "ansi": false } } } EOF sudo chown stalwart:stalwart /opt/stalwart/etc/config.json sudo chmod 600 /opt/stalwart/etc/config.json ```

Replace `mail.example.com` with your actual hostname. The `AcmeTls` block tells Stalwart to request a certificate from Let's Encrypt on first start. That only works if the DNS record already points at this server and port 80 is reachable for the HTTP-01 challenge. If you use a different ACME provider or a manual certificate, swap that block out — the rest of the config does not care.

Step 6 — Create the systemd unit

Run Stalwart under systemd so it restarts on crash and starts on boot.

```bash sudo tee /etc/systemd/system/stalwart.service > /dev/null <<'EOF' [Unit] Description=Stalwart Mail Server After=network-online.target Wants=network-online.target

[Service] Type=simple User=stalwart Group=stalwart ExecStart=/opt/stalwart/stalwart --config /opt/stalwart/etc/config.json Restart=on-failure RestartSec=5 LimitNOFILE=65535 AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE NoNewPrivileges=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/opt/stalwart/data /opt/stalwart/log

[Install] WantedBy=multi-user.target EOF ```

`AmbientCapabilities=CAP_NET_BIND_SERVICE` is what lets a non-root user bind port 25. Without it, systemd refuses to start the unit and the error mentions permission denied on the socket, which sends people looking in the wrong place.

Step 7 — Start and enable the service

```bash sudo systemctl daemon-reload sudo systemctl enable --now stalwart sudo systemctl status stalwart ```

The first start takes a few seconds while it provisions the data directory and requests a certificate. If `status` shows `active (running)`, you are past the hard part.

Step 8 — Add a domain and a mailbox

Stalwart ships with a CLI for administrative tasks. Create your domain and first account.

```bash sudo -u stalwart /opt/stalwart/stalwart --config /opt/stalwart/etc/config.json \ --command create-domain example.com

sudo -u stalwart /opt/stalwart/stalwart --config /opt/stalwart/etc/config.json \ --command create-account --domain example.com --name you --password 'ChangeThisNow' ```

Use a real password, not the placeholder. The account name becomes `you@example.com`. You can add more accounts the same way; there is no web UI in the base install, so this CLI is your admin console until you put something in front of it.

Verification

Confirm the process is up and the ports are listening:

```bash sudo systemctl is-active stalwart sudo ss -tlnp | grep -E ':(25|465|993)\b' ```

You should see `active` and three listening sockets owned by the `stalwart` process. Then test SMTP by hand:

```bash openssl s_client -connect mail.example.com:465 -quiet ```

Type `EHLO test` and press enter. A working server replies with a `250` line listing the extensions it supports. If you get a certificate error, the ACME request failed — check `journalctl -u stalwart -n 100` for the reason.

Finally, point an email client at `mail.example.com`, port 993 for IMAP and 465 for SMTP, and log in with the account you created. If the client syncs an empty inbox, everything is wired correctly.

Common problems

Certificate never issues. Let's Encrypt needs to reach your server on port 80 for the HTTP-01 challenge. If you closed it in the firewall step, open it temporarily:

```bash sudo ufw allow 80/tcp sudo systemctl restart stalwart ```

You can close it again once the certificate lands. Renewals happen automatically but still need the port.

Outbound mail is rejected or silently dropped. Most VPS providers block port 25 outbound by default to fight spam. Test with `nc -zv gmail-smtp-in.l.google.com 25`. If it hangs, contact your provider or route outbound through a smarthost. There is no configuration trick that fixes a blocked port.

The service restarts in a loop. Almost always a config error. Read the last lines before the restart:

```bash sudo journalctl -u stalwart -n 50 --no-pager ```

A JSON syntax error points at the line number. A permissions error on `/opt/stalwart/data` means the `chown` in step 3 did not take — rerun it.

What you have now, and what you do not

You have a mail server that receives, stores, and delivers mail for one domain. You do not have spam filtering, DKIM signing, or a queue monitor. Those are separate config blocks, and each one is a decision about how much complexity you want in exchange for how much control.

The honest comparison: a managed mail host gives you all of this for a few euros a month and someone else's on-call rotation. Running Stalwart yourself gives you the data, the config, and the responsibility. Both are legitimate. The one you pick should depend on whether you want to be the person who fixes it when it breaks — because sooner or later, you will be.

← Terug naar kennisbank
← Terug naar kennisbank