====== Stalwart Mail Server ======
[[https://stalw.art/|Stalwart]] Step into the future with Stalwart, the open-source e-mail powerhouse blending modern features with unparalleled security, speed, and scalability.
I choose Stalwart because it's a new approach to serving mail. Instead of a bunch of interconnected tools, which are often a mess to setup, it's a one piece written from the ground up with a modern approach to email.
Please check [[selfhost:email|this page]] to understand the choices done in this page. I assume that you are installing the email server on your **external** server, and not on the home server.
===== Installation =====
Gentoo ships with a reasonably recent release of Stalwart, but i prefer to have finer control over it, so i prefer to install on bare-metal manually. The project also offer a bare-metal install approach which (see [[https://stalw.art/docs/install/linux/|here]]) which i don't like because it involve download and run an //install.sh// script, which is a **no way** for me.
I have downloaded the script (and i suggest you do the same) and inspected it, so the following instructions are directly taken from the install script, but adapted to my setup.
**Note:** //we are installing on the external server, not on the home server!//
As usual, first of all create the user:
useradd -m stalwart
Then download the latest release from [[https://github.com/stalwartlabs/mail-server/releases/|here]] for your architecture, be sure to download both the mail server and the cli executable:
su - stalwart
mkdir bin etc logs
chmod -R 755 /home/stalwart
wget 'https://github.com/stalwartlabs/mail-server/releases/download/vX.Y.Z/stalwart-mail-x86_64-unknown-linux-gnu.tar.gz'
wget 'https://github.com/stalwartlabs/mail-server/releases/download/vX.Y.Z/stalwart-cli-x86_64-unknown-linux-gnu.tar.gz'
cd bin
tar xvf ../stalwart-mail-x86_64-unknown-linux-gnu.tar.gz
tar xvf ../stalwart-cli-x86_64-unknown-linux-gnu.tar.gz
chmod +x stalwart-mail stalwart-cli
setcap 'cap_net_bind_service=ep' stalwart-mail
The **setcap** is necessary to let stalwart open ports in the reserved range (<1024).
Well, it's time to initialize Stalwart:
/home/stalwart/bin/stalwart-mail --init /home/stalwart
✅ Configuration file written to /home/stalwart/etc/config.toml
🔑 Your administrator account is 'admin' with password 'XxxXxXXxX'.
chmod 700 /home/stalwart/etc/config.toml
Note the //chmod// to ensure the config file is not readable by anybody
Take note of the password! You will never see it again.
Now, start the server for the first time:
su - stalwart # ensure you are stalwart user!
/home/stalwart/bin/stalwart-mail --config=/home/stalwart/etc/config.toml
Open up your browser and go to **http://:8080** and login with the credentials above, then immediately head to **http://:8080/account/password** and change the password to something you will remember.
===== Configuration =====
How to configure Stalwart can be found [[https://stalw.art/docs/install/linux|here]] and [[https://stalw.art/docs/category/configuration|here]]. I will highlight some important steps here.
You need to setup the proper **hostname** in the server configuration.
You should **disable** the listeners for services you don't need (like POP3!).
You need to create at least one domain. This will also provide you with a full DNS setup that you **must** setup in your DNS provider.
You need to create email accounts for the domain.
TLS (SSL) certificates are mandatory, and you have a few ways to get them from Let's Encrypt. I use the standard HTTP challenge, which only requires a webserver.
===== Reverse Proxy =====
Stalwart has a management web GUI. You should either disable it, or protect it behind a reverse proxy with a configuration like the following:
server {
server_name mail.mydomain.com;
listen 443 ssl;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_redirect default;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $scheme;
}
ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;
}
In Stalwart, disable the HTTPS listener (you will be using the reverse proxy anyway) and **make sure to bind the 8080 listener to 127.0.0.1!**
===== Certificates =====
TLS certificates are mandatory for correct email operations.
There are three challenges that you can use to generate your certs and none fit my bill:
* The DNS challenge cannot be used because my DNS provider does not support APIs
* The other two challenges both require a web server with open ports (80 + 443), which i do have, but it points to the home, internal, server and not the external server.
The solution is to generate the certificate on the internal server using the classic HTTP Challenge, then move the certs to the external server. For this i have edited the internal server crontab to tar&move the certs once generated, and the external server to upload those certs and unpack for Stalwart.
Internal server crontab:
47 5 * * * /etc/letsencrypt/certbot-renew.sh && (cd /etc && tar cJf /home/user/certs-copy.tar.xy letsencrypt) &>> /root/certbot.log
31 16 * * * /etc/letsencrypt/certbot-renew.sh && (cd /etc && tar cJf /home/user/certs-copy.tar.xy letsencrypt) &>> /root/certbot.log
External server crontab:
10 6 * * * sftp user@10.0.0.1:/home/user/certs-copy.tar.xy /root/certs-copy.tar.xy && cd /etc && tar xvf /root/certs-copy.tar.xy && chown stalwart:stalwart -R letsencrypt
50 16 * * * sftp user@10.0.0.1:/home/user/certs-copy.tar.xy /root/certs-copy.tar.xy && cd /etc && tar xvf /root/certs-copy.tar.xy && chown stalwart:stalwart -R letsencrypt
For this to work, you need user to be able to ssh into the wireguard tunnel without password. 10.0.0.1 is the wireguard tunnel between the external and the internal server. See [[networking:wireguard_redirects|here]] for more details.
In Stalwart, setup the certs like this:
%{file:/etc/letsencrypt/live/mydomain.com/fullchain.pem}%
%{file:/etc/letsencrypt/live/mydomain.com/privkey.pem}%
===== Autostart =====
Since i love the simplicity of OpenRC, create the following script under **/etc/init.d/stalwart**:
#!/sbin/openrc-run
# Copyright 2025 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
name="stalwart daemon"
description="email server"
pidfile="/run/silverbullet.pid"
command_background=true
command="/home/stalwart/bin/stalwart-mail"
command_args="--config=/home/stalwart/etc/config.toml"
command_user="stalwart:stalwart"
depend() {
need net
}
make it executable and add to default runlevel:
chmod +x /etc/init.d/stalwart
rc-update add stalwart default