GuidesChat and emailMail server with Stalwart

Run your own mail server with Stalwart and rootless Podman

Stalwart on Debian 13, a modern all-in-one mail server with IMAP, POP3, JMAP, spam filtering and DKIM, in rootless Podman under a user of its own, with its web admin behind Caddy and Let's Encrypt certificates.

Tested on Stalwart 0.16 on Debian 13 (trixie) on a Melonslab server Updated September 25, 2026

Recommended server for this guide

VC-S Micro · 2 vCPU · 8 GB Memory · 250 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€7.99/mo

Deploy now
On this page

What you will set up

Stalwart is a mail server in a single program: it receives and sends mail, lets mail apps read it over IMAP, POP3 and JMAP, filters spam, signs outgoing mail with DKIM, and has a web interface for managing domains and mailboxes. It can also serve calendars and contacts.

Here it runs as one container, under a user of its own called stalwart. The mail ports are open to the internet directly, so Stalwart sees every sender's real address, which its spam checks depend on. Its web interface sits behind Caddy from the Podman guide.

Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13. From the internet, attempts to relay mail through the server were refused. A test message passed SPF, reverse DNS and DKIM at an external checker, whose reply arrived over IPv6. IMAP, POP3 and sending all used a Let's Encrypt certificate, and the backup and a reboot were checked. Stalwart used about 160 MB of memory.

If you would rather put a mail server together from Postfix and Dovecot yourself, see the Postfix guide.

Before you start

You need:

  • a server set up as in the Podman guide, with Caddy running;
  • a domain, such as example.com, with an A record and an AAAA record for mail.example.com pointing at your server;
  • reverse DNS for both of the server's addresses set to mail.example.com, as in step 2 of the Postfix guide.

Port 25 is open on every Melonslab server from the first boot. The examples use example.com, mail.example.com and 203.0.113.10 for your server's IPv4 address. Replace them with your own throughout.

1. Allow the mail ports

As root, let ordinary users use ports from 25 upwards, which covers the mail ports and Caddy's 80 and 443:

sed -i 's/ip_unprivileged_port_start = .*/ip_unprivileged_port_start = 25/' /etc/sysctl.d/50-unprivileged-ports.conf
sysctl --system

If you use ufw as in the security guide, open the mail ports too:

ufw allow 25,465,993,995/tcp

2. Create the user

useradd -m -s /bin/bash stalwart
loginctl enable-linger stalwart
machinectl shell stalwart@

3. Describe the container

mkdir -p ~/.config/containers/systemd

Create ~/.config/containers/systemd/stalwart.container:

[Unit]
Description=Stalwart, a mail server

[Container]
ContainerName=stalwart
Image=docker.io/stalwartlabs/stalwart:v0.16
Volume=stalwart-etc:/etc/stalwart
Volume=stalwart-data:/var/lib/stalwart
# Mail ports, open to the internet
PublishPort=25:25
PublishPort=465:465
PublishPort=993:993
PublishPort=995:995
# Web interface, reached through Caddy only
PublishPort=127.0.0.1:8085:8080
Environment=STALWART_PUBLIC_URL=https://mail.example.com
AutoUpdate=registry

[Service]
Restart=always

[Install]
WantedBy=default.target

Port 25 receives mail from other servers. Your mail apps use 465 to send and 993 (IMAP) or 995 (POP3) to read, all encrypted from the start. The web interface is only published on the server's loopback address, for Caddy.

Start it, and show its log:

systemctl --user daemon-reload
systemctl --user start stalwart
podman logs stalwart

The log shows a temporary admin password for the setup in step 5.

4. Put Caddy in front

Go back to root with exit, and switch to Caddy's user with machinectl shell caddy@. Add this block to the end of ~/Caddyfile:

mail.example.com {
    reverse_proxy 127.0.0.1:8085
}

And restart Caddy, which briefly interrupts every site it serves:

systemctl --user restart caddy

5. Run the setup wizard

Open https://mail.example.com/admin and log in as admin with the temporary password. The wizard has five steps:

  1. Server identity. Enter mail.example.com and example.com. Turn off "Automatically obtain TLS certificate": it would use port 443, which Caddy has. Step 8 gets the certificate another way. Leave "Generate email signing keys" on.
  2. Storage. Keep the defaults.
  3. Directory. Keep the internal directory.
  4. Logging. Change the log destination to Console, so the log appears in podman logs.
  5. DNS. Keep manual DNS management, then choose Finish setup.

The last page shows your administrator's address and password once. Note them, then restart Stalwart as the stalwart user:

systemctl --user restart stalwart

Log in at https://mail.example.com/admin with the new administrator account.

6. Tell Stalwart about Caddy

Do this straight away. Stalwart bans addresses that fail too many logins or probe for pages such as WordPress logins, and every web request reaches it through Caddy, from the server's own IPv4 address. Until Stalwart knows that, a single scanner on the internet can get the server's own address banned, and with it every visitor to the web interface. In our test, it happened within minutes.

  • Under Settings → HTTP Server, turn on Obtain remote IP from Forwarded header and save. Stalwart then bans the visitor's real address, which Caddy passes on.
  • Under Settings → Security → Allowed IPs, choose Create address, enter 203.0.113.10 and create it. The server's own address can then never be banned.
  • Under Actions, run Server settings in the Reload section.

7. Publish the DNS records

Under Management → Domains, open the menu of example.com and choose View Zone File. It lists every record your domain needs: MX, SPF, two DKIM keys, DMARC, and the records mail apps use to find their settings. Create them at your DNS provider. If you open the zone file again after step 8, it also lists CAA records and a _validation-persist record: leave those out. CAA records like these would let only Stalwart's certificate account issue certificates for your whole domain, so Caddy could no longer renew certificates for your other sites.

The DMARC record asks receivers to reject mail that fails SPF and DKIM, which suits a domain whose mail only comes from this server.

Then give Caddy the other names from the zone file. As caddy, change the block from step 4 to:

mail.example.com, autoconfig.example.com, autodiscover.example.com, mta-sts.example.com, ua-auto-config.example.com {
    reverse_proxy 127.0.0.1:8085
}

And restart Caddy. Mail apps fetch their settings from autoconfig and autodiscover, and other mail servers read your MTA-STS policy from mta-sts.

8. Get a certificate

Stalwart needs its own certificate for the mail ports. It gets one from Let's Encrypt through Caddy, which passes the check on port 80 on to it.

  • Under Settings → TLS → ACME Providers, choose Create provider. Set the challenge type to HTTP-01, type a contact address such as postmaster@example.com and press Enter to add it, then create the provider.
  • Under Management → Domains, open example.com. Set Certificate Management to ACME TLS certificate management, pick the provider you just created, and save.

Within a minute, podman logs stalwart shows ACME order completed, and the mail ports use the new certificate. Stalwart renews it by itself.

9. Create a mailbox

Under Directory → Accounts, choose Create user. Enter a user name such as anna, pick example.com as the domain, and under Credentials choose Add item and set a password. Create the account: its address is anna@example.com.

10. Connect your mail app

Most mail apps set themselves up from just the address and password. If yours asks:

ServerPortSecurity
Incoming (IMAP)mail.example.com993SSL/TLS
Outgoing (SMTP)mail.example.com465SSL/TLS

The user name is the full address, anna@example.com.

11. Check that it works

From your mail app, send a message to check-auth@verifier.port25.com. Its report arrives within a minute, and should show SPF check: pass, "iprev" check: pass and DKIM check: pass. It may also list a DKIM permerror with "unsupported signature algorithm": Stalwart signs with two keys, and that checker cannot read the newer Ed25519 one. Receivers use whichever signature they can check.

12. Keep it up to date

Switch on Podman's daily updates for this user:

systemctl --user enable --now podman-auto-update.timer

The v0.16 tag gets every fix within 0.16. Moving to a newer version, such as 0.17, is a change to the tag that you make yourself, after reading Stalwart's upgrade notes: its versions before 1.0 have changed how it stores its settings.

13. Back up

mkdir -p ~/backup
systemctl --user stop stalwart
podman volume export stalwart-data --output ~/backup/stalwart-data.tar
podman volume export stalwart-etc --output ~/backup/stalwart-etc.tar
systemctl --user start stalwart

That saves every mailbox, the settings and the DKIM keys. Copy ~/backup to another machine afterwards, and keep it private.

Troubleshooting

The web interface answers "403 Forbidden" for everyone. The server's own address was banned before step 6. As the stalwart user, this removes every ban; b is the ID Stalwart gives the administrator account on a new server:

podman exec stalwart curl -s -u 'admin@example.com:ADMIN_PASSWORD' http://127.0.0.1:8080/jmap/ -H 'Content-Type: application/json' -d '{"using":["urn:ietf:params:jmap:core","urn:stalwart:jmap"],"methodCalls":[["x:BlockedIp/query",{"accountId":"b"},"0"],["x:BlockedIp/set",{"accountId":"b","#destroy":{"resultOf":"0","name":"x:BlockedIp/query","path":"/ids"}},"1"],["x:Action/set",{"accountId":"b","create":{"r":{"@type":"ReloadBlockedIps"}}},"2"]]}'

Then do step 6.

The certificate is not issued. Check that every name in the Caddy block has a DNS record pointing at the server, and read podman logs stalwart for lines starting with ACME.

Other mail servers reject your mail. Check reverse DNS for both addresses, and that the DNS records from step 7 are published, with dig TXT example.com and dig MX example.com.

Sending in bulk?

These steps are for your own mail, your company's mail and transactional mail from your applications. Newsletters and other high-volume sending need an arrangement with our sales team first, so we can protect the reputation of the addresses every customer relies on.

Run it on your own server

VC-S Micro

€7.99/mo

vCPU
2
Memory
8 GB
Storage
250 GB
Transfer
10 TB
Standard
HDD · RAID 10
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides