OpenBSD server: mail server
Check out Part 1, Part 2 and Part 2.5 of this series, if you haven’t already.
Is self-hosting your own mail server in 2026 still a good idea? Big providers like Google or Microsoft sometimes do block residential IPs, VPS providers and unestablished domains. However, running your own mail server should be part of your digital sovereignty and independence, just as having your own personal website.
There are solutions that make self-hosting email way easier than configuring everything by hand, like Mailcow, or Stalwart. If you like to understand how things work and are not afraid of troubleshooting, I think building a mail server “from scratch” can be a great learning experience.
This article will cover how to configure OpenSMTPD for delivering and receiving mail on the server, Dovecot for mail clients to connect, and Rspamd for spam/DMARC/DKIM checks and DKIM signing. Optionally Redis can be used as a caching backend.
Make sure to check if your provider allows outbound mail on port 25. To check run the following command:
nc -vz gmail-smtp-in.l.google.com 25
DNS records
You need the following public DNS records to make the mail server work correctly.
A
The mail server hostname must resolve to the public IPv4 address of the server.
mail.domain.net. A YOUR_IPV4
MX
The Mail Exchange record tells other mail servers where mail for your domain should be delivered.
domain.net. MX 10 mail.kullbach.net.
The target of an MX record should be a real hostname.
PTR/rDNS
A PTR record, or reverse DNS, maps an IP address back to a hostname.
YOUR_IPV4 -> mail.domain.net
SPF
The Sender Policy Framework specifies exactly which IP addresses or servers are allowed to send mails for your domain.
domain.net. TXT "v=spf1 mx -all"
This SPF record allows all mail servers listed in the MX record of the domain to send mails for domain.net. -all asks receiving mail servers to reject any mail, where SPF check fails.
DKIM
DomainKeys Identified Mail allows your mail server to cryptographically sign outgoing mail. A receiving mail server can verify the signature by comparing it to the public DKIM key, published in DNS.
DKIM helps to prove that the signed parts of a message were not modified in transit. Take a look here on what to configure.
DMARC
DMARC builds on SPF and DKIM. It lets the receiving mail server know what to do if a message, claiming to come from your domain, does not pass DMARC checks.
A message passes DMARC if either SPF or DKIM passes and the passing result aligns with the visible From: domain in the header of the message.
Notice that either SPF or DKIM has to pass, not both. That is because SPF and DKIM do solve different problems and can be failing, even though the mail is legitimate.
SPF can fail if a mail sent gets forwarded to a different address on a different server. The final receiving server checks the last sending server, so can SPF fail.
DKIM can fail, if a mailing list or service provider changes parts of the message, for example adding a footer. That can break DKIM because parts of the message have changed.
It is also possible to enable reporting, to see who is sending mail that claims to come from your domain.
_dmarc.domain.net. TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@kullbach.net"
This DMARC record asks a receiving mail server to quarantine mail, where DMARC has failed. Reports are sent to postmaster@domain.net.
Install required packages
doas pkg_add dovecot rspamd opensmtpd-filter-rspamd
Enable services
Enable the services, so they autostart on boot.
doas rcctl enable smtpd
doas rcctl enable dovecot
doas rcctl enable rspamd
The following assumes you have configured httpd already. If not, take a look at Part 2 of this series.
Request an SSL certificate with acme-client
Add this to /etc/acme-client.conf:
domain mail.domain.net {
domain key "/etc/ssl/private/mail.domain.net.key"
domain full chain certificate "/etc/ssl/mail.domain.net.fullchain.pem"
sign with letsencrypt
}
Add this to /etc/httpd.conf:
server "mail.domain.net" {
listen on * port 80
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location * {
block return 301 "https://domain.net$REQUEST_URI"
}
}
Request the certificate:
doas acme-client -v mail.domain.net
Check the created certificates:
ls -l /etc/ssl/mail.domain.net.fullchain.pem
ls -l /etc/ssl/private/mail.domain.net.key
Reload smtpd and dovecot:
doas rcctl reload smtpd
doas rcctl reload dovecot
Setup certificate renewal:
Add this to crontab with doas crontab -e:
0 3 * * * acme-client mail.domain.net && rcctl reload smtpd && rcctl reload dovecot
Create a user and Maildir
Create the mail group:
grep '^mail:' /etc/group || doas groupadd mail
Create the user and set a password:
doas useradd -m -G mail -s /sbin/nologin philipp
doas passwd philipp
Create the Maildir for user philipp:
doas install -d -m 700 -o philipp -g philipp /home/philipp/Maildir
doas install -d -m 700 -o philipp -g philipp /home/philipp/Maildir/cur
doas install -d -m 700 -o philipp -g philipp /home/philipp/Maildir/new
doas install -d -m 700 -o philipp -g philipp /home/philipp/Maildir/tmp
for folder in Drafts Sent Junk Trash; do
doas install -d -m 700 -o philipp -g philipp /home/philipp/Maildir/.$folder
doas install -d -m 700 -o philipp -g philipp /home/philipp/Maildir/.$folder/cur
doas install -d -m 700 -o philipp -g philipp /home/philipp/Maildir/.$folder/new
doas install -d -m 700 -o philipp -g philipp /home/philipp/Maildir/.$folder/tmp
done
doas chmod 700 /home/philipp
Domains list
/etc/mail/domains
domain.net
Recipient allow-list
It is important to set this, as we don’t want mail to be accepted for every unix account.
Create /etc/mail/recipients:
philipp@domain.net
postmaster@domain.net
abuse@domain.net
hostmaster@domain.net
Aliases
The file /etc/mail/aliases is used to assign aliases to a mailbox.
hostmaster: root
webmaster: root
root: philipp
abuse already points to root by default on OpenBSD.
Apply the new aliases:
doas newaliases
Hostname
Fix your hostname:
echo mail.domain.net | doas tee /etc/myname
doas hostname mail.domain.net
Also restart the syslog daemon after the hostname change:
doas rcctl restart syslogd
OpenSMTPD config
The Open SMTPD is located in /etc/mail/smtpd.conf:
## Certificates
pki mail.domain.net cert "/etc/ssl/mail.kullbach.net.fullchain.pem"
pki mail.domain.net key "/etc/ssl/private/mail.kullbach.net.key"
## Tables
table <aliases> file:/etc/mail/aliases
table <recipients> file:/etc/mail/recipients
table <domains> file:/etc/mail/domains
## Filters
filter "rspamd" proc-exec "filter-rspamd"
## Listeners
# Public inbound SMTP, server-to-server.
listen on egress port 25 tls pki mail.domain.net filter "rspamd"
# Authenticated submission for your mail clients.
listen on egress port 587 tls-require pki mail.domain.net auth filter "rspamd"
# Local system mail.
listen on lo0
# Actions
# Local delivery into /home/$USER/Maildir.
action "local_maildir" maildir "~/Maildir" alias <aliases>
# Outbound delivery to the internet.
action "outbound" relay
# Match rules
# Accept inbound mail for domain.net only if recipient is listed.
match from any for domain <domains> rcpt-to <recipients> action "local_maildir"
# Authenticated users may send outbound mail.
match from auth for any action "outbound"
# Local machine may send outbound mail.
match from local for any action "outbound"
# Local system mail, e.g. root -> philipp via aliases.
match for local action "local_maildir"
Validate the configuration with doas smtpd -n.
Be sure to check the match rules carefully when editing them. You don’t want your server to become an open relay
Dovecot
Edit /etc/dovecot/conf.d/10-ssl.conf and set these lines:
ssl = required
ssl_cert = </etc/ssl/mail.domain.net.fullchain.pem
ssl_key = </etc/ssl/private/mail.domain.net.key
ssl_dh = </etc/dovecot/dh.pem
Generate the DH keys:
doas openssl dhparam -out /etc/dovecot/dh.pem 4096
doas chown root:_dovecot /etc/dovecot/dh.pem
doas chmod 440 /etc/dovecot/dh.pem
On my Hetzner cx22 Server it took about 30 minutes to generate those keys.
Edit /etc/dovecot/conf.d/10-mail.conf and make sure these are set:
mail_location = maildir:~/Maildir
first_valid_uid = 1000
mmap_disable = yes
mbox_write_locks = fcntl
mail_plugin_dir = /usr/local/lib/dovecot
Edit /etc/dovecot/conf.d/20-imap.conf and set these lines:
protocol imap {
mail_max_userip_connections = 25
}
To only use IMAPs, edit /etc/dovecot/conf.d/10-master.conf:
service imap-login {
inet_listener imap {
port = 0
}
inet_listener imaps {
port = 993
ssl = yes
}
}
Check /etc/dovecot/conf.d/15-mailboxes.conf and make sure to remove the duplicate “Sent Messages” line.
Check /etc/dovecot/dovecot.conf and make sure the following is set:
protocols = imap
listen = *
Validate the configuration with doas doveconf -n.
Optional: Rspamd web interface
Rspamd comes with a fancy web interface. To configure that, first generate a password hash:
doas rspamadm pw
Create the config directory:
doas mkdir -p /etc/rspamd/local.d
Copy the output and add it to /etc/rspamd/local.d/worker-controller.inc
password = "$2$REPLACE_WITH_HASH_FROM_RSPAMADM_PW";
bind_socket = "127.0.0.1:11334";
Optional: Redis for Rspamd
Redis can be used as a caching backend for Rspamd. Install redis:
doas pkg_add redis
Enable the service:
doas rcctl enable redis
Create the Redis config for Rspamd in /etc/rspamd/local.d/redis.conf:
servers = "127.0.0.1";
Start and test:
doas rcctl start redis
redis-cli ping
Rspamd DKIM signing
Create the DKIM directory:
doas install -d -m 750 -o _rspamd -g _rspamd /etc/rspamd/dkim
Generate the key:
doas rspamadm dkim_keygen -s default -d domain.net \
-k /etc/rspamd/dkim/default.domain.net.key \
> /tmp/default.domain.net.txt
Move it to the correct place:
doas mv /tmp/default.domain.net.txt /etc/rspamd/dkim/default.kullbach.net.txt
Fix the permissions:
doas chown _rspamd:_rspamd /etc/rspamd/dkim/default.domain.net.key
doas chmod 640 /etc/rspamd/dkim/default.domain.net.key
Create the configuration file /etc/rspamd/local.d/dkim_signing.conf:
enabled = true;
domain {
domain.net {
selector = "default";
path = "/etc/rspamd/dkim/default.domain.net.key";
}
}
use_domain = "header";
sign_authenticated = true;
sign_local = true;
sign_inbound = false;
allow_username_mismatch = true;
allow_hdrfrom_mismatch = true;
allow_envfrom_empty = true;
sign_networks = "/etc/rspamd/local.d/sign_networks.map";
Add your servers IP to /etc/rspamd/local.d/sign_networks.map:
127.0.0.1
::1
YOUR_SERVER_IPV4
Also create /etc/rspamd/local.d/arc.conf:
path = "/etc/rspamd/dkim/default.domain.net.key";
selector = "default";
Check the config syntax:
doas rspamadm configtest
Print the DNS record:
doas cat /etc/rspamd/dkim/default.domain.net.txt
You can now add this as a DNS record.
Firewall
Make sure to add the rules for SMTP and IMAP:
pass in on vio0 proto tcp to port { 22 25 80 443 587 993 }
Test and reload the config:
doas pfctl -nf /etc/pf.conf
doas pfctl -f /etc/pf.conf
Start the services
Start Rspamd:
doas rcctl start rspamd
Start Dovecot:
doas rcctl start dovecot
Start OpenSMTPD:
doas rcctl start smtpd
Check listeners:
netstat -na -f inet | grep LISTEN
You should see listeners for 25, 587 and 993 that are not on localhost.
Test local delivery
Send a local test message on the server:
echo "hello" | mail -s "domain user test" philipp@domain.net
Check if the mail has arrived in the Maildir:
doas find /home/philipp/Maildir/new -type f -ls
If nothing is there, check the maillog:
doas tail -f /var/log/maillog
Check DNS
Check from a different machine:
dig MX domain.net +short
dig A mail.domain.net +short
dig TXT domain.net +short
dig TXT _dmarc.domain.net +short
dig TXT default._domainkey.domain.net +short
dig -x YOUR_IPV4 +short
Test external delivery
Try sending mail from an external address. Then check the maildir for new items:
doas find /home/philipp/Maildir/new -type f -ls
Test with a mail client
Finally, you can test the connection with a mail client like Thunderbird or mutt. Use these settings:
Email address: philipp@domain.net
Username: philipp
Password: UNIX password of that account
Authentication type: normal password
Incoming server settings:
Protocol: IMAP
Server: mail.domain.net
Port: 993
Security: SSL/TLS
Outgoing server settings:
Protocol: SMTP
Server: mail.domain.net
Port: 587
Security: STARTTLS
Use the mail client to send a test mail to an external account or mail-tester.com.
Troubleshooting
If you run into any problems you can use the following commands to troubleshoot:
doas tail -f /var/log/maillog
doas tail -f /var/log/rspamd/rspamd.log
doas doveadm log errors
If you configured the Rspamd web interface, you can access it with an ssh tunnel:
ssh -L 11334:127.0.0.1:11334 mail.domain.net
Useful tools
Check if your IP is blacklisted with MXtoolbox.
Check for PTR, SPF, DKIM and DMARC with mailtester by sending a mail to them.
Useful commands
Show the message count per folder:
doas doveadm mailbox status -u philipp messages '*'
Backup
The most important things to backup are:
/home/<USERS>/Maildir
/etc/mail
/etc/dovecot
/etc/rspamd
/etc/ssl/private/mail.domain.net.key
/etc/acme
/etc/acme-client.conf
Anything else can be recovered by referencing this tutorial :-)
The END
That was quite an extensive one! Thanks for reading.