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 complete 2025-08-14 except there is no page for setting up postscreen.

This is version 2 and everthing works.

###############################
# 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

After upgrading ports and doing a pkg update pkg upgrade it replaced the compiled postfix dovecot with pkg. I had to reinstall postfix and dovecot from ports, if you go into dovecot-fts-flatcurve and to make rmconfig it doesn't remove the config in /ports/dovecot, so go to ports dovecot.

Additionally when doing pkg update pkg upgrade, if the pkg version of software is going to reinstall over the ports compiled version, use pkg lock pkgname to prevent just these packages from being replaced or upgrade. Then use ports to manage only those packages.

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

cd /usr/ports/mail/dovecot-pigeonhole
make rmconfig
make clean

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

Then go back to flatcurve and pigeonhole and make and make install again

cd /usr/ports/mail/dovecot

../dovecot-fts-flatcurve
make
make install

Prevent pkg from overwriting compiled ports version of dovecot

pkg lock dovecot

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 rmconfig
make clean

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

Compile dovecot-flatcurve

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

Prevent pkg from overwriting compiled ports version of dovecot

pkg lock dovecot

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
--- setup with default certs and multidomain selection ---
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 into master.cf

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 = subscribe special_use = \Drafts } mailbox Junk { auto = subscribe special_use = \Junk autoexpunge = 30d } mailbox Trash { auto = subscribe special_use = \Trash autoexpunge = 30d } # 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 = subscribe 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