Stalwart Mail Server
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 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 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 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:<external-server-ip>:8080 and login with the credentials above, then immediately head to http:<external-server-ip>:8080/account/password and change the password to something you will remember.
Configuration
How to configure Stalwart can be found here and 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:
- stalwart.conf
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 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:
- 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