Table of Contents
Was this article helpful?

36  out of  49 found this helpful

Try Vultr Today with

$50 Free on Us!

Want to contribute?

You could earn up to $600 by adding new articles.

An OpenBSD E-Mail Server Using OpenSMTPD, Dovecot, Rspamd, and RainLoop

Last Updated: Mon, Feb 28, 2022
BSD Email System Admin


This tutorial demonstrates a full-featured email server running on OpenBSD using OpenSMTPD, Dovecot, Rspamd, and RainLoop. OpenSMTPD is the default mail server for OpenBSD. Choose a Vultr Compute Cloud instance with plenty of storage for the expected number of users.

Preliminary Steps

Verify the server's outbound port status.

Set up your user account to perform tasks as root.

su -

usermod -G wheel <username>

echo "permit nopass keepenv :wheel" > /etc/doas.conf


Set up the package repository for OpenBSD.

doas su

echo "" > /etc/installurl


Add the required packages.

doas pkg_add opensmtpd-extras opensmtpd-filter-rspamd dovecot dovecot-pigeonhole rspamd redis

Configure OpenSMTPD

By default, OpenSMTPD only listens on localhost. It must be explicitly configured to listen on external interfaces. It should be configured to use virtual users instead of system users for security.

Backup the default smtpd.conf file and create a new one from scratch.

cd /etc/mail

mv smtpd.conf smtpd.conf.default

Create a new smtpd.conf as shown below. Replace with your domain. This initial configuration does not activate the rspamd filter while testing OpenSMTP. The spam filter will be activated later.

pki "mail" cert "/etc/ssl/mail.crt"

pki "mail" key "/etc/ssl/private/mail.key"

table aliases file:/etc/mail/aliases

table credentials passwd:/etc/mail/credentials

table virtuals file:/etc/mail/virtuals

filter "rspamd" proc-exec "/usr/local/libexec/smtpd/filter-rspamd"

# To accept external mail, replace with: listen on all

# listen on all tls pki "mail" hostname ""

listen on egress port submission tls-require pki "mail" hostname "" auth <credentials>

action "local_mail" mbox alias <aliases>

action "domain_mail" maildir "/var/vmail/{dest.user:lowercase}" virtual <virtuals>

action "outbound" relay

# Uncomment the following to accept external mail for domain ""

# match from any for domain "" action "domain_mail"

match from local for local action "local_mail"

match from local for any action "outbound"

match auth from any for any action "outbound"

Create the Credentials File

OpenSMTPD and Dovecot can share an authentication database. This database resembles the system password file in format, with two extra fields for Dovecot. The two special fields define the virtual home directory and the mail location. Passwords are in blowfish format. This tutorial creates three example users.

Generate the passwords and concatenate them to the /etc/mail/credentials file.

doas su

smtpctl encrypt example_password1 >> /etc/mail/credentials

smtpctl encrypt example_password2 >> /etc/mail/credentials

smtpctl encrypt example_password3 >> /etc/mail/credentials


The output looks similar to this:




Edit /etc/mail/credentials to add the required fields. Each line maps to a system account, vmail, with UID and GID of 2000. Replace with your domain. Replace the example passwords with the passwords you generated in the previous step. The virtual user name is the complete email address.$2b$10$_EXAMPLE_PASSWORD1_C3JbO4Ns2jJNZQfTS45MAnKi.IPrkKITyTa6:vmail:2000:2000:/var/vmail/$2b$10$_EXAMPLE_PASSWORD2_YKD.K0kQ2oylOmQ9SBUb0hIopBsmNxYPb4e:vmail:2000:2000:/var/vmail/$2b$10$_EXAMPLE_PASSWORD3_IvRu4xbeOqOJJXlgEAKuS5sIrBvfdPvEzeq:vmail:2000:2000:/var/vmail/

Create Virtual Mail Account and Set Security

  • Set /etc/mail/credentials permissions to read-only for _smtpd and _dovecot system users.

  • Create the vmail system user, group, and home directory.

  • When you create the vmail system user, you will receive the following warning: useradd: Warning: home directory '/var/vmail' doesn't exist, and -m was not specified. This is expected. This avoids cluttering the directory with dot files from /etc/skel. They are not required because the vmail account does not allow login.

    doas chmod 0440 /etc/mail/credentials
    doas chown _smtpd:_dovecot /etc/mail/credentials
    doas useradd -c "Virtual Mail Account" -d /var/vmail -s /sbin/nologin -u 2000 -g =uid -L staff vmail
    doas mkdir /var/vmail
    doas chown vmail:vmail /var/vmail

Create the Virtual User Mapping

Create /etc/mail/virtuals to define the valid email addresses.

  • The first four lines assign aliases for abuse, hostmaster, postmaster, and webmaster.

  • The last three lines map the email addresses to the vmail account. OpenSMTPD will deliver the messages to /var/vmail/<user>.

  • Mail delivery attempted for addresses not defined in this file will be bounced with a Delivery Status Notification. vmail vmail vmail

Create Public/Private Keys for OpenSMTPD

This example uses a self-signed certificate. Use a valid signed certificate if you have one. When prompted for the common name, be sure it matches the FQDN of the server. This example uses

doas su

cd /etc/ssl

openssl genrsa -out private/mail.key 4096

openssl req -x509 -new -nodes -key private/mail.key -out mail.crt -days 3650 -sha256

chmod 0400 /etc/ssl/private/mail.key


Test the Server

Use the OpenSMTPD configuration syntax checker. If no problems are found, restart the smtpd daemon.

doas smtpd -n

doas rcctl restart smtpd

From an outside mail account, send a test email to one of the users.

  • OpenSMTPD will create the maildir folder structure below /var/vmail and deliver the mail to /var/vmail/<username>/new.

  • As the root user, browse to this location and verifiy you have a file named similar to this:,.

  • Review the contents of the file, including all of the mail headers, to verify the email delivery works properly.

    Return-Path: <>
    Received: from ( [])
        by (OpenSMTPD) with ESMTPS id 75b514d3 (TLSv1.2:ECDHE-RSA-AES256-GCM-    SHA384:256:NO)
        for <>;
        Sat, 14 Dec 2019 11:10:40 -0500 (EST)
    DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;;
        s=dkim-201406; t=1576339839;
    X-MSFBL: DM7pSZns+YDRgNEmGNre9aPjZTtc1tDlN97w5rQDBts=|eyJ1IjoibWF0dEBnb2J
    Date: Sat, 14 Dec 2019 16:10:39 +0000

Configure Dovecot IMAP

Set the Login Class

Dovecot requires the ability to have a larger number of files open for reading and writing than the default class allows. Failing to do this will cause errors that are difficult to troubleshoot.

Define a login class for the Dovecot daemon. At the bottom of /etc/login.conf add the following lines.





Create the Dovecot Configuration File

Create /etc/dovecot/local.conf.

auth_mechanisms = plain

first_valid_uid = 2000

first_valid_gid = 2000

mail_location = maildir:/var/vmail/%d/%n

mail_plugin_dir = /usr/local/lib/dovecot

managesieve_notify_capability = mailto

managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex  imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext imapsieve vnd.dovecot.imapsieve

mbox_write_locks = fcntl

mmap_disable = yes

namespace inbox {

  inbox = yes

  location =

  mailbox Archive {

  auto = subscribe

  special_use = \Archive


  mailbox Drafts {

  auto = subscribe

  special_use = \Drafts


  mailbox Junk {

  auto = subscribe

  special_use = \Junk


  mailbox Sent {

  auto = subscribe

  special_use = \Sent


  mailbox Trash {

  auto = subscribe

  special_use = \Trash


  prefix =


passdb {

  args = scheme=CRYPT username_format=%u /etc/mail/credentials

  driver = passwd-file

  name =


plugin {

  imapsieve_mailbox1_before = file:/usr/local/lib/dovecot/sieve/report-spam.sieve

  imapsieve_mailbox1_causes = COPY

  imapsieve_mailbox1_name = Junk

  imapsieve_mailbox2_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve

  imapsieve_mailbox2_causes = COPY

  imapsieve_mailbox2_from = Junk

  imapsieve_mailbox2_name = *

  sieve = file:~/sieve;active=~/.dovecot.sieve

  sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment

  sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve

  sieve_plugins = sieve_imapsieve sieve_extprograms


protocols = imap sieve

service imap-login {

  inet_listener imap {

  port = 0



service managesieve-login {

  inet_listener sieve {

  port = 4190


  inet_listener sieve_deprecated {

  port = 2000



ssl_cert = </etc/ssl/mail.crt

ssl_key = </etc/ssl/private/mail.key

userdb {

  args = username_format=%u /etc/mail/credentials

  driver = passwd-file

  name =


protocol imap {

  mail_plugins = " imap_sieve"


Dovecot Bug Fix

There is a bug in Dovecot where the ssl_cert and ssl_key settings do not get overridden in the local.conf file so we have to comment them out. If you miss this step, Dovecot will fail to start correctly.

Edit /etc/dovecot/conf.d/10-ssl.conf as shown.


# PEM encoded X.509 SSL/TLS certificate and private key. They're opened before

# dropping root privileges, so keep the key file unreadable by anyone but

# root. Included doc/ can be used to easily generate self-signed

# certificate, just make sure to update the domains in dovecot-openssl.cnf

#ssl_cert = </etc/ssl/dovecotcert.pem

#ssl_key = </etc/ssl/private/dovecot.pem


Create the Sieve Scripts

Sieve scripts train Rspamd on spam and ham. Moving email into and out of the junk folder triggers an event to train Rspamd.

These files are located at /usr/local/lib/dovecot/sieve.

Create the report-ham.sieve file.

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.mailbox" "*" {

  set "mailbox" "${1}";


if string "${mailbox}" "Trash" {



if environment :matches "imap.user" "*" {

  set "username" "${1}";


pipe :copy "" [ "${username}" ];

Create the report-spam.sieve file.

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.user" "*" {

  set "username" "${1}";


pipe :copy "" [ "${username}" ];

Compile the files.

sievec report-ham.sieve

sievec report-spam.sieve 

Create the following two shell scripts in /usr/local/lib/dovecot/sieve

Add the following to


exec /usr/local/bin/rspamc -d "${1}" learn_ham

Add the following to


exec /usr/local/bin/rspamc -d "${1}" learn_spam

Make the files executable.

chmod 0755

chmod 0755

Enable and start Dovecot.

rcctl enable dovecot

rcctl start dovecot

Check that Dovecot started properly.

ps ax | grep dovecot

88005 ??  I        0:00.11 /usr/local/sbin/dovecot

69640 ??  I        0:00.03 dovecot/anvil

91207 ??  I        0:00.03 dovecot/log

98178 ??  I        0:00.19 dovecot/config

34712 ??  I        0:00.06 dovecot/stats

96674 ??  I        0:00.03 dovecot/imap-login

 8891 ??  S        0:00.02 dovecot/imap

Verify Dovecot can correctly read /etc/mail/credentials

doveadm user

field    value

uid      2000

gid      2000

home /var/vmail/

mail maildir:/var/vmail/

Verify a mail user can log in.

doveadm auth login

Password: ********

passdb: auth succeeded

extra fields:

userdb extra fields:






Set up Rspamd

This is a basic Rspamd configuration, refer to the offical documentation for more details. This example creates a definition for our domain to enable DKIM signing.

Create a public/private keypair in /etc/mail/dkim and set the correct permissions.

doas su

mkdir /etc/mail/dkim

cd /etc/mail/dkim

openssl genrsa -out 2048

openssl rsa -in -pubout -out public.key

chmod 0440

chown root:_rspamd

Create a DNS record for DKIM containing the public key. Refer to your DNS provider for details of how to create a DKIM record. Copy the contents from /etc/mail/dkim/public.key and paste it after the p= part of the DKIM record as shown below. Note this example also creates an SPF record. IN TXT "v=DKIM1;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClcuK3FV3Ug64li8iFsuJ2ykgb7FMZsujk9uG79ppPUp57vCfjzO7F+HBfx5qIwvlGxv2IJXK86FZwhpUX+HFCDUtfB2z0ZNGerWcZfNzM1w1Bru/fdMd2tCYkiHEa5RWIkLfs/Fm+neXxRZfAG2UDWmAghNbgzYv7xViwgufDIQIDAQAB" IN TXT "v=spf1 a ip4: mx ~all"

Create a DMARC record. IN TXT "v=DMARC1;p=none;pct=100;"

Create the /etc/rspamd/local.d/dkim_signing.conf configuration file.

  • The selector="default"; line is derived from the first part of the DKIM DNS record (default._domainkey....) created above.

    domain {
            path = "/etc/mail/dkim/";
            selector = "default";

Enable and start Rspamd.

doas rcctl enable redis rspamd

doas rcctl start redis rspamd

Change the lines below in /etc/mail/smtpd.conf and restart OpenSMTPD to enable Rspamd.


listen on all tls pki "mail" hostname "" filter "rspamd"

listen on egress port submission tls-require pki "mail" hostname "" \

  auth <credentials> filter "rspamd"


rcctl restart smtpd

Test the mail server with POP3 or IMAP email client. If you do not require webmail, stop here.

(Optional) Configure RainLoop Webmail

Install Prerequisite Packages

When prompted, please choose the most current version of PHP.

pkg_add php php-curl php-pdo_sqlite php-zip pecl73-mcrypt zip unzip wget curl

Fetch the RainLoop webmail tarball and extract it to /var/www/htdocs/.

Use the Standard Edition which includes an automatic updater.

cd /tmp


unzip -d /var/www/htdocs/rainloop

chown -R www:www /var/www/htdocs

Create Let's Encrypt SSL Certificate

  • Copy /etc/examples/acme-client.conf to /etc

  • Add the following lines at the bottom of the file:

    domain {
    domain key "/etc/ssl/private/"
    domain full chain certificate "/etc/ssl/"
    sign with letsencrypt

Configure httpd

  • Create a DNS entry (either CNAME or A record) for subdomain

  • Edit /etc/httpd.conf following the example below.

    prefork 3
    types { include "/usr/share/misc/mime.types" }
    server "default" {
        listen on egress port 80 
        root "/htdocs"
        directory index index.html
        location "/.well-known/acme-challenge/*" {
                root "/acme"
                request strip 2

Run the httpd syntax check.

httpd -n

Enable and start httpd.

rcctl enable httpd

rcctl start httpd

Request the Let's Encrypt certificate.

acme-client -v

Add the server definitions for RainLoop to /etc/httpd.conf

server "" {

    listen on egress port 80

    block return 302 "https://$SERVER_NAME$REQUEST_URI"


server "" {

    listen on egress tls port 443

    root "/htdocs/rainloop"

    directory index "index.php"

    tcp { nodelay, backlog 10 }

    tls {

            certificate "/etc/ssl/"

            key "/etc/ssl/private/"


    hsts {

            max-age 31556952



    # Value below is 25MB in bytes. 1MB = 1048576 bytes

    connection max request body 26214400

    location "/data*" {

            block return 403


    location "*.php*" {

            fastcgi socket "/run/php-fpm.sock"



Configure PHP to allow attachments up to 25 megabytes. Make the following changes in /etc/php-7.3.ini:

; Maximum allowed size for uploaded files.


upload_max_filesize = 25M


; Maximum size of POST data that PHP will accept.

; Its value may be 0 to disable the limit. It is ignored if POST data reading

; is disabled through enable_post_data_reading.


post_max_size = 29M

Enable the necessary PHP modules by copying their configuration files to /etc/php-7.3/

cp /etc/php-7.3.sample/* /etc/php-7.3/.

Check the syntax of /etc/httpd.conf

httpd -n

Enable and start httpd and php-fpm.

rcctl reload httpd

rcctl enable php73_fpm

rcctl start php73_fpm

Final Test

Browse to

The default username is: admin

The default password is: 12345.

Browse to each of the configuration sections and make your desired changes. Once you have finished with the configuration, log into webmail at

Want to contribute?

You could earn up to $600 by adding new articles.