Setup Email Server From Scratch On FreeBSD #2 - 04 Dovecot IMAP

03 Postfix SMTPD <- Intro -> 05 PostfixAdmin

We believe in data independence, and support others who want data independence.
This tutorial is partially complete 2025-08-07

This is version 2 and everthing works up to and including Roundcube.

###############################
# INSTALL DOVECOT IMAP SERVER #
###############################

# If you have not install ports Update ports tree, ignore the Device busy, zfs needs to be unmounted to remove the

# actual directory, but we don't need to remove it.
rm -rf /usr/ports
rm: /usr/ports: Device busy

ls -la /usr/ports
drwxr-xr-x   2 root wheel  2 Aug  6 09:19 ./
drwxr-xr-x  15 root wheel 15 Jun  6 01:34 ../

pkg install portsnap
mkdir -p /var/db/portsnap

cd /usr/ports
portsnap fetch
portsnap extract

# For ports update use 'portsnap fetch update'.

# If you plan to use ARGON2I scheme compile as follows.

# Missing ARGONI

/usr/local/bin/doveadm pw -l
SHA1 SSHA512 SCRAM-SHA-256 BLF-CRYPT PLAIN HMAC-MD5 OTP SHA512 SHA DES-CRYPT 
CRYPT SSHA MD5-CRYPT PLAIN-MD4 PLAIN-MD5 SCRAM-SHA-1 SHA512-CRYPT CLEAR 
CLEARTEXT SSHA256 MD5 PBKDF2 SHA256 CRAM-MD5 PLAIN-TRUNC SHA256-CRYPT SMD5 
DIGEST-MD5 LDAP-MD5

# Clean the dovecot ports if previously compiled

cd /usr/ports/mail/dovecot-fts-flatcurve
make deinstall
make clean
make rmconfig

cd /usr/ports/mail/dovecot
make deinstall
make clean
make rmconfig

# Compile dovecot-flatcurve

cd /usr/ports/mail/dovecot-fts-flatcurve
make

# Choose these options

X DOCS
X EXAMPLES
X LIBSODIUM - you will need this for ARGON2I (postfixadmin)
X LIBWRAP
X LUA
X LZ4
X CDB
X LDAP
X MYSQL
  ICU - NO IT FAILED EVERY TIME
X SOLR
X TEXTCAT
X GSSAPI-NONE

# There are a lot of dependencies and compiling will take some time. Several 
# screens will popup with options to select, continue with default options 
# already selected.

make install
libtool --finish /usr/local/lib/dovecot/doveadm
libtool --finish /usr/local/lib/dovecot

# Check if user and groups were created, if not create them.

grep dovecot /etc/passwd /etc/group
/etc/passwd:dovecot:*:143:143:Dovecot User:/var/empty:/usr/sbin/nologin
/etc/group:dovecot:*:143:

# Enable dovecot

sysrc dovecot_enable="YES"

# To avoid a risk of mailbox corruption, do not set the security.bsd.see_other_uids or 
# .see_other_gids sysctls to 0 if Dovecot is storing mail for multiple concurrent users (PR 
# 218392).

# Similarly, setting sysctls security.bsd.hardlink_check_uid or security.bsd.hardlink_check_gid 
# to 1 might result in non-working mailboxes, depending on what mailbox locking mechanism is 
# used (PR 242223).

# If you want to be able to search within attachments using the decode2text plugin, you'll need 
# to install textproc/catdoc, and one of graphics/xpdf or graphics/poppler-utils.

# Both xpf and poppler-utils have security or dependency issues and fail to compile so use pkg.

pkg install catdoc
pkg install xpdf
pkg install poppler-utils

echo 'DEFAULT_VERSIONS+=ssl=openssl' >> /etc/make.conf

# Generate the dh.pem for Dovecot

openssl dhparam -out /usr/local/etc/dovecot/dh.pem 4096

# Copy example configuation files

cp -R /usr/local/etc/dovecot/example-config/* /usr/local/etc/dovecot

# Configure Dovecot, Dovecot won't start without the certs and vmail configured.

# Edit 10-ssl.conf

nano /usr/local/etc/dovecot/conf.d/10-ssl.conf
# ---
ssl = required
#ssl_cert = </etc/ssl/certs/dovecot.pem
#ssl_key = </etc/ssl/private/dovecot.pem
ssl_cert = </usr/local/etc/letsencrypt/live/mail.okbsd.com/fullchain.pem
ssl_key = </usr/local/etc/letsencrypt/live/mail.okbsd.com/privkey.pem
local_name mail.okbsd.com {
    ssl_cert = </usr/local/etc/letsencrypt/live/mail.okbsd.com/fullchain.pem
    ssl_key = </usr/local/etc/letsencrypt/live/mail.okbsd.com/privkey.pem
}
local_name imap.okbsd.com {
   ssl_cert = </usr/local/etc/letsencrypt/live/mail.okbsd.com/fullchain.pem
   ssl_key = </usr/local/etc/letsencrypt/live/mail.okbsd.com/privkey.pem
}

ssl_dh = </usr/local/etc/dovecot/dh.pem
ssl_min_protocol = TLSv1.2
ssl_prefer_server_ciphers = yes
# ---

# Check if vmail user and group exist.

grep vmail /etc/passwd /etc/group

# Add the vmail user and group

pw useradd -c "" -n vmail -s /usr/bin/nologin -d /nonexistent -u 2000

grep vmail /etc/passwd /etc/group
/etc/passwd:vmail:*:2000:2000::/nonexistent:/usr/bin/nologin
/etc/group:vmail:*:2000:

service dovecot start
service dovecot status
dovecot is running as pid 22413.

dovecot --version 
2.3.21.1 (d492236fa0)

# Check for LIBSODIUM/ARGON2I support if you plan to use it.

/usr/local/bin/doveadm pw -l
SHA1 SSHA512 SCRAM-SHA-256 BLF-CRYPT PLAIN HMAC-MD5 OTP SHA512 SHA DES-CRYPT 
CRYPT SSHA MD5-CRYPT PLAIN-MD4 PLAIN-MD5 SCRAM-SHA-1 SHA512-CRYPT CLEAR 
CLEARTEXT ARGON2I ARGON2ID SSHA256 MD5 PBKDF2 SHA256 CRAM-MD5 PLAIN-TRUNC 
SHA256-CRYPT SMD5 DIGEST-MD5 LDAP-MD5

# Test ARGON2I

/usr/local/bin/doveadm pw -s ARGON2I -r 5 -p hello 
{ARGON2I}$argon2i$v=19$m=32768,t=5,p=1$RyC0o8I14UBuFpYHC61HEg$i7BQp0F4 ... etc.

openssl version
OpenSSL 3.0.16 11 Feb 2025 (Library: OpenSSL 3.0.16 11 Feb 2025)

# Comment out the following in openssl, not compatible with dovecot lmtp

nano /etc/ssl/openssl.cnf
# ---
# providers = provider_sect

# Add dovecot user to mail group so it can read the maildir
pw groupmod mail -m dovecot

grep dovecot /etc/group
mail:*:6:postfix,dovecot
dovecot:*:143:

# Enable Submission in Postfix

# I modified for FreeBSD but shamelessly copied parts from linuxbabe.com. He 
# deserves a coffee or 100 coffee's! His tutorial is for Debian and a little
# out of date but still a fantastic guide!

# Backup Postfix configs
cd /usr/local/etc
cp -R postfix postfix_backup

# Backup Dovecot configs
cd /usr/local/etc
cp -R dovecot dovecot_backup

# Paste the following
nano /usr/local/etc/postfix/master.cf
# ---
submission     inet     n    -    y    -    -    smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_tls_wrappermode=no
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
smtps     inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth

# If you want to compare what is configured between 2 machines useful commands
postconf | grep <value>

# Modify or add the following values
nano /usr/local/etc/postfix/main.cf
# ---
myhostname = smtp.okbsd.com
mydomain = okbsd.com
myorigin = $mydomain
inet_interfaces = all
# mydestination = $myhostname, localhost.$mydomain, localhost
mydestination = $mydomain, $myhostname, localhost.$mydomain, localhost
local_recipient_maps = unix:passwd.byname $alias_maps
#mynetworks_style = host
mynetworks_style = ${{$compatibility_level} <level {2} ? {subnet} : {host}}
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128                
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
relay_domains = ${{$compatibility_level} <level {2} ? {$mydestination} : {}}
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
home_mailbox = Maildir/
mail_spool_directory = /var/mail
smtpd_banner = $myhostname ESMTP
setgid_group = maildrop
inet_protocols = all
smtp_tls_CApath = /etc/ssl/certs
shlib_directory = /usr/local/lib/postfix
meta_directory = /usr/loc/libexec/postfix
mailbox_size_limit = 0
message_size_limit = 52428800

# Enable TLS Encryption when Postfix receives incoming emails
smtpd_tls_cert_file=/usr/local/etc/letsencrypt/live/mail.okbsd.com/fullchain.pem
smtpd_tls_key_file=/usr/local/etc/letsencrypt/live/mail.okbsd.com/privkey.pem
smtpd_tls_security_level=may
smtpd_tls_loglevel = 1
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
tls_server_sni_maps = hash:/usr/local/etc/postfix/sni_maps

# Enable TLS Encryption when Postfix sends outgoing emails
smtp_tls_security_level = may
smtp_tls_loglevel = 1
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# Enforce TLSv1.3 or TLSv1.2
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
# ---

# Create Postfix Server Name Indication SNI Maps

nano /usr/local/etc/postfix/sni_maps
# ---
mail.okbsd.com /usr/local/etc/letsencrypt/live/mail.okbsd.com/privkey.pem /usr/local/etc/letsencrypt/live/mail.okbsd.com/fullchain.pem
smtp.okbsd.com /usr/local/etc/letsencrypt/live/mail.okbsd.com/privkey.pem /usr/local/etc/letsencrypt/live/mail.okbsd.com/fullchain.pem
# ---

postmap -F /usr/local/etc/postfix/sni_maps

# Restart Postfix

service postfix restart
postfix/postfix-script: stopping the Postfix mail system
postfix/postfix-script: starting the Postfix mail system

# Check if postfix is running

service postfix status
postfix is running as pid 22606.

# Finish Configuring Dovecot

# To view Dovecot configuration

doveadm config

# Edit dovecot.conf to enable lmtp

nano /usr/local/etc/dovecot/dovecot.conf
# ---
protocols = imap lmtp
# ---

# To find the mail_spool_dir

postconf mail_spool_directory
mail_spool_directory = /var/mail

# Edit 10-mail.conf - Use <control>-w to search for values to change

nano /usr/local/etc/dovecot/conf.d/10-mail.conf
# ---
mail_location = maildir:~/Maildir
# later mail_home = /var/vmail/%d/%n
mail_privileged_group = mail
# ---

# Edit 10-master.conf

nano /usr/local/etc/dovecot/conf.d/10-master.conf
# ---
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0600
    user = postfix
    group = postfix
  }

  # Create inet listener only if you can't use the above UNIX socket
  #inet_listener lmtp {
    # Avoid making LMTP visible for the entire internet
    #address =
    #port =
  #}
}

service auth {
  unix_listener auth-userdb {
    #mode = 0666
    #user =
    #group =
  }
  
  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
      mode = 0660
      user = postfix
      group = postfix
  }

  # Auth process is run as this user.
  #user = $default_internal_user
}
# ---

# Edit 20-lmtp.conf

nano /usr/local/etc/dovecot/conf.d/20-lmtp.conf
# ---
protocol lmtp {
  # Space separated list of plugins to load (default is global mail_plugins).
  mail_plugins = $mail_plugins
}
# ---

# Edit 10-auth.conf

nano /usr/local/etc/dovecot/conf.d/10-auth.conf
# ---
disable_plaintext_auth = yes

auth_default_realm = okbsd.com 
auth_username_format = %n
auth_mechanisms = plain login
!include auth-system.conf.ext
# !include auth-sql.conf.ext
auth_debug = yes
auth_debug_passwords = yes
# ---

# Edit 15-mailboxes.conf

nano /usr/local/etc/dovecot/conf.d/15-mailboxes.conf
# ---
  mailbox Drafts {
    auto = create
    special_use = \Drafts
  }
  mailbox Junk {
    auto = create
    special_use = \Junk
  }
  mailbox Trash {
    auto = create
    special_use = \Trash
  }

  # For \Sent mailboxes there are two widely used names. We'll mark both of
  # them as \Sent. User typically deletes one of them if duplicates are created.
  mailbox Sent {
    auto = create
    special_use = \Sent
  }
  mailbox "Sent Messages" {
    special_use = \Sent
  }
# ---

# Modify /usr/local/etc/postfix/main.cf

nano /usr/local/etc/postfix/main.cf
# --- add at end of file
mailbox_transport = lmtp:unix:private/dovecot-lmtp
smtputf8_enable = no
# ---

# Restart and Postfix

service dovecot restart
service dovecot status
dovecot is running as pid 22680

service postfix restart
service postfix status
dovecot is running as pid 22791

# Check if Dovecot is listening on ports
sockstat -l | grep dovecot | egrep -E "143|993"
root     dovecot    22680 36  tcp4   *:143                 *:*
root     dovecot    22680 37  tcp6   *:143                 *:*
root     dovecot    22680 38  tcp4   *:993                 *:*
root     dovecot    22680 39  tcp6   *:993                 *:*

# Autoconfig and Autodiscover

pkg install git
cd /tmp
git clone https://github.com/smartlyway/email-autoconfig-php
cd /tmp/email-autoconfig-php/mail
mkdir -p /usr/local/www/okbsd/mail
cp /tmp/email-autoconfig-php/mail/config-v1.1.xml /usr/local/www/okbsd/mail

# Edit these files and change to suit your needs, this is tested and works with 
# Thunderbird. Make sure change the SMTP settings to 587 with STARTTLS in 
# config-v1.1.xml.

nano /usr/local/www/okbsd/mail/config-v1.1.xml
# ---
<?xml version="1.0"?>
<clientConfig version="1.1">
    <emailProvider id="okbsd.com">
      <domain>okbsd.com</domain>
      <displayName>okbsd.com</displayName>
      <displayShortName>okbsd.com</displayShortName>
      <incomingServer type="imap">
         <hostname>imap.okbsd.com</hostname>
         <port>993</port>
         <socketType>SSL</socketType>
         <authentication>password-cleartext</authentication>
         <username>%EMAILADDRESS%</username>
      </incomingServer>
      <outgoingServer type="smtp">
         <hostname>smtp.okbsd.com</hostname>
         <port>587</port>
         <socketType>STARTTLS</socketType>
         <username>%EMAILADDRESS%</username>
         <authentication>password-cleartext</authentication>
      </outgoingServer>
    </emailProvider>
</clientConfig>
# ---

# Configure Autodiscover for Outlook clients.

cp -r /tmp/email-autoconfig-php/Autodiscover/Autodiscover.xml /usr/local/www/okbsd/mail

nano /usr/local/www/okbsd/mail/Autodiscover.xml/index.php
# ---
<?php
$raw = file_get_contents('php://input');
$matches = array();
preg_match('/<EMailAddress>(.*)<\/EMailAddress>/', $raw, $matches);
header('Content-Type: application/xml');
?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
  <Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
    <User>
      <DisplayName>OK Biz Mail</DisplayName>
    </User>
    <Account>
      <AccountType>email</AccountType>
      <Action>settings</Action>
      <Protocol>
        <Type>IMAP</Type>
        <Server>imap.okbsd.com</Server>
        <Port>993</Port>
        <DomainRequired>off</DomainRequired>
        <SPA>off</SPA>
        <SSL>on</SSL>
        <AuthRequired>on</AuthRequired>
        <LoginName><?php echo $matches[1]; ?></LoginName>
      </Protocol>
      <Protocol>
        <Type>SMTP</Type>
        <Server>smtp.okbsd.com</Server>
        <Port>587</Port>
        <DomainRequired>off</DomainRequired>
        <SPA>off</SPA>
        <SSL>on</SSL>
        <AuthRequired>on</AuthRequired>
        <LoginName><?php echo $matches[1]; ?></LoginName>
      </Protocol>
    </Account>
  </Response>
</Autodiscover>
# ---

# You will need to add a SV records for Autodiscover for NameCheap I added ...
# Namecheap Domain List -> DNS -> Advanced DNS

Type            Service         Protocol  Priority  Weight  Port  Target
SRV Record      _autodiscover   _tcp      5         0       443   mail.okbsd.com

# Autoconfig needs the path http://autoconfig.okbsd.com/mail/config-v1.1.xml

nano /usr/local/etc/apache24/Includes/mail.okbsd.conf
# ---
	ServerAlias autoconfig.okbsd.com
	ServerAlias autodiscover.okbsd.com

	# autoconfig
	Alias /mail "/var/www/okbsd/mail"
        <Directory /usr/local/www/okbsd/mail/>
                Options FollowSymLinks
                AllowOverride All
                Require all granted
        </Directory>
# ---

# Autodiscover uses the host the SRV record points to which is mail.okbsd.com and it
# already has a cert. Autodiscover needs path https://okbsd.com/Autodiscover/Autodiscover.xml.

nano /usr/local/etc/apache24/Includes/ssl-mail.okbsd.conf
# ---
<IfModule mod_ssl.c>
  SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
  <VirtualHost *:443>
        ServerName mail.okbsd.com
        #ServerAlias smtp.okbsd.com
        #ServerAlias imap.okbsd.com
        #ServerAlias autoconfig.okbsd.com
        #ServerAlias autodiscover.okbsd.com
        ServerAdmin postmaster@okbsd.com
        DirectoryIndex index.php index.html

        DocumentRoot /usr/local/www/okbsd/html/
        <Directory /usr/local/www/okbsd/html/>
                Options FollowSymLinks
                AllowOverride All
                Require all granted
        </Directory>


        Alias /mail "/usr/local/www/okbsd/mail/"
        Alias "/Autodiscover/Autodiscover.xml" "/usr/local/www/okbsd/mail/Autodiscover.xml/index.php"
        <Directory /usr/local/www/okbsd/mail/>
                DirectorySlash Off
                # Options FollowSymLinks
                Options -Indexes +FollowSymLinks
                AllowOverride All
                Require all granted
        </Directory>
        Include /usr/local/etc/letsencrypt/options-ssl-apache.conf
        Header always set Strict-Transport-Security "max-age=31536000"
        SSLUseStapling on
        SSLCertificateFile /usr/local/etc/letsencrypt/live/mail.okbsd.com/fullchain.pem
        SSLCertificateKeyFile /usr/local/etc/letsencrypt/live/mail.okbsd.com/privkey.pem
  </VirtualHost>
</IfModule>
# ---

apachectl restart

# Confirm these files can be accessed by http and https with a web browser and that they output xml.

http://mail.okbsd.com/mail/config-v1.1.xml
https://mail.okbsd.com/Autodiscover/Autodiscover.xml

# Setup Thunderbird email client - use advanced settings

# If you don't have thunderbird search download thunderbird mail with your web browser and install it.

Settings -> Account Settings -> Account Actions -> Add Mail Account
Incoming / IMAP
Full Name: Jack Pumpkin
Email address: jack@okbsd.com
Password: <secretpassword>
Click Configure Manually
Protocol: IMAP
Hostname: imap.okbsd.com
Port: 993
Security: SSL/TLS

Outgoing / SMTP
Username: jack@okbsd.com
Protocol: Outgoing
Hostname: smtp.okbsd.com
Port: 587
Security: STARTTLS
Username: jack@okbsd.com
<Re-test>

# Thunderbird Errors

# If you encounter the following Thunderbird error when sending mail change 
# SSL/TLS to STARTTLS for the Outgoing Server.

Sending of the message failed. The message could not be sent because the connection to Outgoing server (SMTP) mx.coragarden.com
was lost in the middle of the transaction. Try again.

# This seems to occur when Thunderbird caches bad autoconfig data from the server and 
# doesn't set the encryption method properly.

# Go to Account Settings / Outgoing Server and delete the relevant smtp server 
# entries. Then delete the account(s). Then go to Tools / Clear Recent History / 
# Delete Everything. Restart Thunderbird. Run a fresh test.

# I tried SNI, server name indication wasn't working.

Sending of the message failed. An error occurred while sending mail: Outgoing
server (SMTP) error. The server responded: TLS not available due to local
problem.

# SNI configuration requires default certs in both Postfix and Dovecot. After 
# SNI was fixed it was necessary to stop and restart Thunderbird to test if it 
# was fixed.

# At first I could not authenticate because I used auth_username_format = %u. At
# this stage in the setup use auth_username_format = %n, and switch to %u later on.

# To troubleshoot use check /var/log/maillog

cat /var/log/maillog

# If you can't find the error try clearing the log and tail it from one terminal
# while sending and recieving mail with the mail client.

# clear the log
echo '' > /var/log/maillog

# tail it so you can watch the output
tail -f -300 /var/log/maillog

# Troubleshooting 

# Caution! Thunderbird caches data! When testing, delete the relevant outgoing 
# server(s), delete the relevant mail account(s) in Thunderbired, go to Tools -> 
# Clear Recent History and delete everything, close and restart Thunderbird.

# Then add the account(s) back as new to test again.

# Test autoconfig by removing the account in Thunderbird and adding it back. If it is 
# working properly it should configure quickly with imap.okbsd.com. If 
# Thunderbird said it is testing well known server names, takes a while, and 
# comes up with mail.okdeb.com then autoconfig is not working properly and check
# your apache configuration and paths. See the readme in /tmp/email-autoconfig-php.

# If you get the following error in /var/log/maillog, you forgot postmap -F
# /usr/local/etc/postfix/sni_maps or the path in /usr/local/etc/postfix/main.cf is wrong.

warning: table hash:/usr/local/etc/postfix/sni_maps.db: key smtp.okbsd.com: malformed BASE64 value:

# Next we will install PostfixAdmin

03 Postfix SMTPD <- Intro -> 05 PostfixAdmin