Self-hosting email is not rocket science
Inspired by a recent Hacker News comment, I’m publishing the notes I took when I set up my email server two years ago. This covers all the steps to get from a fresh Debian install to a working email setup. My setup features a spam filter and SPF, DKIM and DMARC for reliable delivery but is otherwise pretty minimal. I’ve been using this with Thunderbird and K-9 Mail for the past two years and it’s been great.
I’ve left my notes largely unedited, only adding a few explanations here and there. If you want to understand everything in detail you will have to read up on the topics yourself. The building blocks are Postfix, Dovecot and Rspamd.
DNS
Start by configuring DNS as shown in the zone file below. You’ll be
generating a new DKIM key later, so leave that record blank for now. The
imap
and smtp
subdomains are not strictly
necessary but many email clients try them first when adding a new
account.
@ IN A 157.90.113.29
@ IN AAAA 2a01:4f8:c2c:ef29::1
@ IN MX 10 mail.tschumacher.net.
@ IN TXT "v=spf1 mx -all"
dkim._domainkey IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClpscaeydgCSiEdDTCU4YJxXGX7wK+xKCEMAvIGiq8XUo0pOA7K0rC02u15H97JV/JNpcAMUsHo5a4JePK2rufpL0o1/5X1bFhziyQkDHOPr4ZEYiE7EOp8Wo+nbyuR+0sdh+wLtyrVBlC6KRj1n2fqEM/58bN9dtnw6aY8cIY2QIDAQAB"
_dmarc IN TXT "v=DMARC1; p=reject"
mail IN CNAME tschumacher.net.
imap IN CNAME tschumacher.net.
smtp IN CNAME tschumacher.net.
rspamd IN CNAME tschumacher.net.
Make sure reverse DNS is set up and resolves to
tschumacher.net
.
Dependencies
Install the Debian packages listed below. Nginx is required as a
reverse proxy for the Rspamd web interface. Read here for why
Rspamd recommends setting up Unbound as your own recursive DNS resolver.
The resolvconf
package is used by Unbound to set itself as
the system DNS server in /etc/resolv.conf
. Redis is used by
Rspamd to cache ephemeral data.
nginx apache2-utils certbot python3-certbot-nginx postfix dovecot-imapd dovecot-lmtpd dovecot-sieve unbound resolvconf redis-server gpg
On the Rspamd downloads page the developers recommend against using the package from the official Debian repository and provide links to their own repository. First download their repository key:
curl https://rspamd.com/apt-stable/gpg.key | gpg --dearmor > /usr/share/keyrings/rspamd.gpg
Then add their repository to /etc/apt/sources.list
.
Change bookworm
to the current Debian version. Check at http://rspamd.com/apt-stable/dists/ if it’s available
first.
deb [signed-by=/usr/share/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ bookworm main
Finally run apt update
and install
rspamd
.
Dovecot
Start by running Certbot to get an SSL certificate—this will automatically set up a cron job to renew the certificate regularly.
certbot certonly --nginx -d tschumacher.net,mail.tschumacher.net,imap.tschumacher.net,smtp.tschumacher.net,rspamd.tschumacher.net
We will begin by configuring Dovecot. In
/etc/dovecot/conf.d/10-ssl.conf
set the Let’s Encrypt
certificate.
ssl_cert = </etc/letsencrypt/live/tschumacher.net/fullchain.pem
ssl_key = </etc/letsencrypt/live/tschumacher.net/privkey.pem
In /etc/dovecot/conf.d/10-master.conf
enable the LMTP
socket which will be used by Postfix to deliver incoming email to
Dovecot.
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0666
user = postfix
group = postfix
}
}
In the same file enable the SASL socket which is used by Postfix to authenticate users for sending outgoing email.
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = postfix
group = postfix
}
}
In /etc/dovecot/conf.d/10-auth.conf
configure the domain
name to be stripped from usernames before they are looked up in
/etc/passwd
.
auth_username_format = %Ln
In /etc/dovecot/conf.d/10-mail.conf
switch from mbox to
Maildir.
mail_location = maildir:~/Maildir
In /etc/dovecot/conf.d/20-lmtp.conf
add
sieve
to the list of loaded plugins.
mail_plugins = $mail_plugins sieve
Rspamd
We will move on to the configuration of Rspamd. Create
/etc/rspamd/local.d/redis.conf
to instruct Rspamd to use
the local Redis server:
servers = "127.0.0.1";
Create /etc/rspamd/local.d/dkim_signing.conf
to change
the DKIM signing policy so that usernames don’t need to contain the
domain name:
allow_username_mismatch = true;
Next, generate a DKIM key or copy the key from a previous
installation. Create the directory /var/lib/rspamd/dkim
if
it doesn’t exist. Make sure the resulting TXT record is correctly set in
the DNS zone.
rspamadm dkim_keygen -s dkim -d tschumacher.net -k /var/lib/rspamd/dkim/tschumacher.net.dkim.key
Add the Nginx site /etc/nginx/sites-available/rspamd
as
a reverse proxy for the Rspamd web interface and enable it:
server {
listen 80;
listen [::]:80;
server_name rspamd.tschumacher.net;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name rspamd.tschumacher.net;
ssl_certificate /etc/letsencrypt/live/tschumacher.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/tschumacher.net/privkey.pem;
add_header Strict-Transport-Security "max-age=31536000" always;
auth_basic "closed site";
auth_basic_user_file htpasswd;
location / {
root /usr/share/rspamd/www/;
try_files $uri @proxy;
}
location @proxy {
proxy_pass http://127.0.0.1:11334;
}
}
Then create the password file that is used to protect the Rspamd web interface.
htpasswd -c /etc/nginx/htpasswd tim
Postfix
Finally we will tend to the configuration of Postfix. In
/etc/postfix/main.cf
set the Let’s Encrypt certificate and
enable opportunistic TLS.
smtpd_tls_cert_file=/etc/letsencrypt/live/tschumacher.net/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/tschumacher.net/privkey.pem
smtpd_tls_security_level = may
smtp_tls_security_level = may
In the same file configure Postfix to talk to Dovecot via the previously configured LMTP and SASL sockets.
mailbox_transport = lmtp:unix:private/dovecot-lmtp
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
And in the same file configure Rspamd as a milter for Postfix.
smtpd_milters = inet:localhost:11332
milter_default_action = accept
milter_protocol = 6
In /etc/postfix/master.cf
uncomment the following lines
to start a separate Postfix worker on port 465. This worker will act as
a Message Submission Agent (MTA). SASL is only enabled for this
worker.
submissions inet n - y - - smtpd
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
Final steps
Now that Dovecot, Rspamd and Postfix are configured we can move on.
Next create the user tim
and set a password. This is the
password that will be used by the email client for both sending mail via
SMTPS and fetching mail via IMAPS.
In /etc/aliases
redirect mail destined for
postmaster
to tim
instead of
root
. Run newaliases
after making changes to
/etc/aliases
.
The file /home/tim/.dovecot.sieve
is a script in the Sieve language
for filtering and sorting incoming mail similar in capabilities to
procmail. At the very least mail classified as spam by Rspamd should be
placed in the Spam folder. Note that when adding new
fileinto
rules new folders have to be created by hand
e.g. mkdir ~/Maildir/.Spam
or Dovecot will produce an
error.
require ["fileinto"];
if header :is "X-Spam" "Yes" {
fileinto "Spam";
}
This completes the email setup. Proper functioning of opportunistic TLS and also SPF, DKIM and DMARC can be tested with mail-tester.com or Gmail.