Home FreeBSD FreeBSD: postfix, dovecot, MUA – virtual domains, virtual users

FreeBSD: postfix, dovecot, MUA – virtual domains, virtual users

by Kliment Andreev

In my previous post I’ve described how to configure postfix and dovecot to serve virtual domains and virtual users using POP3s, IMAPs, SMTP SSL/TLS and SASL under CentOS Linux. This time we’ll do the same for FreeBSD.

We’ll be using FreeBSD 9.2, postfix 2.10.0 and dovecot 2.2.5.

Pre install

FreeBSD comes with sendmail preinstalled, so we need to remove it first. Edit /etc/rc.conf and add these lines.

# Disable sendmail

Then do:

killall sendmail

Postfix and Dovecot install

First, install postfix.

cd /usr/ports/mail/postfix
make all install clean

Select BDB, PCRE, TLS and DOVECOT2. Do not select INST_BASE. If you get a build error code 2, just repeat make all install clean. If you don’t see a dialog to prompt what to install, type make rmconfig. Accept the defaults for perl and pcre when the dialogs show up. Accept the defaults for dovecot and libiconv as well. Since we are installing from the source, it might take about 20 minutes depending on your hardware. Unlike CentOS, postfix and dovecot will be installed at the same time, because we specified dovecot when installing postfix.

At the end of the install you’ll be prompted to activate postfix in /etc/mail/mailer.conf. Say yes. Finally, edit /etc/rc.conf and make sure that both postfix and dovecot start on boot.


The configuration files for postfix are in /usr/local/etc/postfix. There are two main files, main.cf and master.cf. Make a copy of both these files. Usually I do:

cd /usr/local/etc/postfix
cp main.cf main.cf.20131124
cp master.cf master.cf.20131124

20131124 represents the date the copy was taken.

Now, edit main.cf and change the following values:

myhostname = www.example.com
mydomain = example.com
myorigin = $mydomain
inet_interfaces = all
home_mailbox = Maildir/

Create a user that will have access to the mailboxes.

adduser vpostfix
tail /etc/passwd

Make sure to change the login shell to /usr/sbin/nologin using vipw.

Look at the UID:GUID for the vpostfix user. In my case it’s 1002:1002.

Add the following lines at the end of main.cf and replace the UID:GUID under virtual_minimum_uid, virtual_uid_maps and virtual_gid_maps with the values that you got from tail /etc/passwd or grep vpostfix /etc/passwd.

# Virtual domain config
virtual_mailbox_domains = /usr/local/etc/postfix/virtual_domains
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/usr/local/etc/postfix/vmailbox
# Make sure you replace these UID:GUID numbers
virtual_minimum_uid = 1002
virtual_uid_maps = static:1002
virtual_gid_maps = static:1002
virtual_alias_maps = hash:/usr/local/etc/postfix/virtual

Edit or create /usr/local/etc/postfix/virtual_domains. This is a file where all of your domains will be listed. Of course, you’ll have to make sure that MX records of your domains point to the IP of the FreeBSD box.

#  Put each domain in a separate line.

As you can see, we have three virtual domains, but the myhostname points to www.example.com. If you put one of these three domains as myhostname and mydomain values, you’ll get a warning that the local domain is already listed in the virtual domains list. In addition, if you keep e.g. domain-one.com as a local domain and remove it from the virtual domains list, then you can’t treat domain-one.com as a virtual domain and deliver its mail to /var/mail/vhosts. Don’t use myhostname = localhost either, because if some other mail server has the same config, you’ll get an error that the destination loops back and the mail won’t be delivered. Don’t use www.example.com either, because you won’t be able to send to www.example.com or any other server that have www.example.com as myhostname. So, pick something that’s unique even if you don’t own that domain or that domain is not registered. Mind that if you send an email from this server and analyze the headers, you’ll see that www.example.com is in the headers. Most e-mail servers won’t complain and treat this e-mail as spam, as long as you have a valid MX record for the virtual domains that point to that IP.

Create the mail folder, subfolders for the domains and assign the proper permissions.

mkdir /var/mail/vhosts
chgrp -R vpostfix /var/mail
cd /var/mail/vhosts
mkdir domain-one.com
mkdir domain-two.net
mkdir domain-three.org
cd ..
chown -R vpostfix:vpostfix vhosts

Once you do that, postfix will create the “Maildir” folders automatically and assign the proper permissions once an e-mail hits these detinations. Finally, create a file /usr/local/etc/postfix/vmailbox and add all of the users that will receive e-mails. Here is an example:

[email protected]        domain-one.com/joe/
[email protected]       domain-one.com/bill/
@domain-one.com           domain-one.com/catch-all/
[email protected]        domain-two.net/joe/

Make sure you end up each line with “/”, otherwise mail won’t be delivered.
Virtual user [email protected] (mind that there is no FreeBSD login for this user, these are all virtual users) will have his email delivered under /var/mail/vhosts/domain-one.com/joe folder. You don’t have to create these subfolders. Once everything is up and running, postfix will take care of creating the Maildir structure (cur, new, tmp).

If you want you can create a catch-all address, see the example above (catch-all). This line tells postfix to get all the emails for the non-existing users in that domain (domain-one.com), which means a lot of spam.

But what if you have a valid FreeBSD user named bill? Where that email goes? In this case, nowhere. If we want this OS user to receive an email, we’ll have to treat him as a virtual user and add him to a virtual domain. It’s much easier to maintain one list of virtual users and hosts than deal with separate configuration files.

Maybe you’ve noticed that the file with the e-mail addresses (vmailbox) has a hash: prefix in the config file. This is to speed-up lookups. Postfix can use hash: (Berkeley-DB), mySQL or PostgreSQL database to store the e-mail accounts. Check the postfix howto if you want to use mySQL or PostgreSQL. We’ll be dealing with Berkeley DB.

Create the virtual aliases file and create a local aliases file

touch /usr/local/etc/postfix/virtual
cd /etc
postalias aliases

Once we are done with the editing of these files, do the following to create the hashed files (extension .db). You should execute these lines anytime you make a change to these files.

postmap /usr/local/etc/postfix/virtual
postmap /usr/local/etc/postfix/vmailbox

Now, everything is ready for postfix to receive an email. Make sure that postfix is running and that it actually listens on port 25. Also make sure that port 25 is opened on the firewall.

ps -waux | grep postfix
sockstat -4 | grep :25
tail /var/log/maillog

You can restart postfix with postfix stop and postfix start or reload the configuration files with postfix reload.

From another domain (e.g. your hotmail or gmail account) send an e-mail to [email protected] or whatever your domain is. Do :

tail -f /var/log/maillog

You should see something like this.

If you check /var/mail/vhosts/domain-one/joe/new folder you’ll see a file with some gibberish name. This is your e-mail that you just sent to joe. But, how will this virtual user retrieve this e-mail? There is a login (the e-mail address), but what’s the password?


In order to retrieve the e-mails, we’ll configure Dovecot. Dovecot is an open-source POP and IMAP client.

As of version 2.0, there are multiple configuration files for Dovecot. The main file is /usr/local/etc/dovecot/dovecot.conf, but you’ll see a lot of include directives there that point to /usr/local/etc/dovecot/conf.d folder where we have about 22 configuration files. FreeBSD comes with these files under a different folder, so we’ll have to copy them to their proper location.

cd /usr/local/etc/dovecot
cp -R /usr/local/share/doc/dovecot/example-config/ .

Make a copy of dovecot.conf and change only one line.

protocols = imap pop3

Then, go to conf.d folder and change the following lines in the following files.


disable_plaintext_auth = no
#!include auth-system.conf.ext
!include auth-passwdfile.conf.ext


log_path = /var/log/dovecot.log
auth_verbose = no
auth_debug = no
verbose_ssl = no


mail_location = maildir:/var/mail/vhosts/%d/%n
mail_uid = 1002
mail_gid = 1002
mail_privileged_group = vpostfix


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


ssl = no
# ssl_cert = </etc/ssl/certs/dovecot.pem
# ssl_key = </etc/ssl/private/dovecot.pem

If you look at 10-auth.conf, we commented the line #!include auth-system.conf.ext and uncommented the !include auth-passwdfile.conf.ext. Take a look at this file and you’ll see:

passdb {
  driver = passwd-file
  args = scheme=CRYPT username_format=%u /usr/local/etc/dovecot/users

userdb {
  driver = passwd-file
  args = username_format=%u /usr/local/etc/dovecot/users

This describes how our username/password database will be in the file /usr/local/etc/dovecot/users. To generate a password with SHA512-CRYPT password scheme do:

doveadm pw -s SHA512-CRYPT

You’ll be prompted to enter a password twice and the output will be similar to this:

If you want to use a different password scheme, take a look at this link.

Now, create or open /usr/local/etc/dovecot/users and copy and paste the password after the username. In my case, I have [email protected] with some password that I just generated. So the line will be like this.

Don’t forget to add 4 colons after the password “::::”. Even if you use the same password for the users, they’ll be encrypted differently.

The problem with this scenario is that the end users won’t have the ability to change their passwords. So, you’ll have to provide them with the password and they won’t be able to reset them. But, there are plenty of perl scripts that can take care of this or you can write your own.

Start dovecot, by simply typing dovecot and check for any errors with tail -f /var/log/dovecot.log. At this point, we should have dovecot running and listening for pop and imap connections.

sockstat -4 | grep :110
sockstat -4 | grep :143

Now, let’s check our e-mail. You can do that from the server using the telnet command.

In the above example, I am testing POP3. For IMAP, do the following.

If you want you can test retrieving these emails with a mail client such as Outlook, Opera Mail or any MUA of your preference.

At this point the server can receive e-mails from others and you can retrieve those e-mails from outside using POP and IMAP. What we need to do now is to be able to reply to those e-mails from outside (using MUA of your choice). Nowadays port 25 is blocked at some major providers (Verizon, Comcast for example), so we’ll use SASL in Postfix and we’ll use Dovecot to authenticate the users using the same username/password combination. In addition, we’ll use certificates, so instead of POP3 and IMAP, we’ll use their secure equivalents, POP3s and IMAPs running on ports 995 and 993 respectively. Dovecot should already listen on these ports, so you can allow these ports on the firewall and close 110 and 143.


Edit /usr/local/etc/postfix/main.cf and add the following lines at the end.

smtpd_use_tls = yes
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_key_file = /usr/local/etc/postfix/myserver.key
smtpd_tls_cert_file = /usr/local/etc/postfix/server.crt
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom

smtpd_sasl_type = dovecot
broken_sasl_auth_clients = yes
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
smtpd_relay_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination

NOTE: As of postfix 2.10 the last line is needed. See this link.

Then, edit /usr/local/etc/postfix/master.cf and remove the comments from the submission part.

submission inet n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

Restart postfix after these changes. For information of what these values mean, check the links at the end of this post. If you do sockstat -4, you’ll see that postfix is also listening on port 587. Allow this port on the firewall if you have it enabled, but don’t close port 25. This port is used for server to server communication. If you do telnet localhost 587 and type EHLO something.com you should see that postfix replies with STARTTLS.


Edit 10-auth.conf and change disable_plaintext_auth = no to disable_plaintext_auth = yes. Then, edit 10-ssl.conf (you did a backup of this file, didn’t you?) and change the following values.

ssl = yes
ssl_cert = </usr/local/etc/postfix/server.crt
ssl_key = </usr/local/etc/postfix/myserver.key

We’ll use self-signed certificates, but check www.startssl.com for free certificates. Unlike virtual Apache domains, you don’t need multiple certificates for each virtual domain. Self-signed certificates are fake, so you’ll get a prompt to accept a fake certificate when you try to send/receive an email, but the goal is to show you how to use them, not to be a 100% compliant.

openssl genrsa -out myserver.key 1024
openssl req -new -key myserver.key -out myserver.csr
openssl x509 -req -days 3650 -in myserver.csr -signkey myserver.key -out server.crt

Coopy server.crt and myserver.key under /usr/local/etc/postfix and restart both postfix and dovecot.

You can test SMTP SSL/TLS on submission port 587.

openssl s_client -starttls smtp -connect localhost:587

Then type ehlo something.com , hit ENTER and then mail from:[email protected]. If these steps work, you should be OK. To test SASL with postfix and dovecot, type:

doveadm auth -a /var/spool/postfix/private/auth [email protected] joe'spasswd

You should receive passdb: [email protected] auth succeeded.

At this point, you should be able to send e-mails from your favorite MUA, but you’ll have to make some changes in order to send and receive. For example, in Outlook, you should use these settings.

So, no more port 110 and 143. Instead use 995 for POP3s, 587 for SMPT (SASL) and 993 for IMAPs. The username is your e-mail address and the password is the one that you generated with doveadm pw command.



Related Articles


glender September 7, 2014 - 8:19 AM

Great tutorial – very explicit and it worked great for me. Much thanks. Just one question – how would you recommend getting a local user’s email into a virtual user ? I ask as I use the periodic emails sent in FreeBSD. Googling for a solution has not provided an easy answer and I was wondering how you would do this. Take care and thanks again for the post !

Kliment Andreev September 7, 2014 - 1:46 PM

See if this link works for you. If not, I’ll take a look.


mbeichorn November 6, 2014 - 12:11 AM

I had the same problem. That periodic emails were not getting to the email address specified in /etc/aliases for root. What was happening was that emails sent to root were sent off to [email protected] by postfix.

To fix this use ‘localhost’ instead of ‘example.com’ for myhostname, mydomain and myorigin in the postfix main.cf

Gminfly January 20, 2018 - 10:37 AM

I followed your blog and get an “Error: Internal auth failure”
as well as an “Error: userdb lookup failed: Internal error occurred.”

log just says
auth: Error: Master requested auth for nonexistent client …

Somethings amiss here

Kliment Andreev January 20, 2018 - 11:41 AM

If you are using FreeBSD 11+ and newer versions of postfix, this tutorial might not work.

Gminfly January 20, 2018 - 12:10 PM

Yes I am using FreeBSD 11.0, but everything seems to work yet dovecot just refuses… Apache is so simple, I don’t get why mail is still so complicated.

marcel-plouf August 20, 2018 - 6:31 AM

Thanks for this tutorial. It worked great for me with FreeBSD 11.1, I just had to uncomment ssl_dh parameter in ssl.conf dovecot file and generate the dh.pem file with `openssl dhparam -out /usr/local/etc/dovecot/dh.pem 4096`

Paul P. September 19, 2019 - 8:09 AM

Thank you for this great step-by-step tutorial! The Postfix part worked for me on Postfix 2.9.4.
Is there any way to get the solution for Dovecot 1.2.16?
Seems there is no way to upgrade to Dovecot 2.x, but the version 1.2.x has another set of files, not mentioned in your tutorial.

Kliment Andreev September 19, 2019 - 9:51 AM

Dovecot v1 and v2 differ a lot. I don’t have any plans to work on a version for v1. Sorry…

Paul P. September 23, 2019 - 8:00 AM

Not a problem at all! But I have another question: is there a way to *forward* all messages coming to a virtual box, to the email box of a real user in the system? Could this be configured in postfix/main.cf or somewhere else? For example, now I have a real user named ‘office’ and its mail is in /var/mail/office. I have thought that I could code a crontab task, which collects and copies all incoming virtual mail to the end of the /var/mail/office file (so then this file is worked out by Dovecot and it’s accessible by my regular email client), but may be there is some easier way… thanks a lot!

Kliment Andreev September 23, 2019 - 12:16 PM

See if this works.
You’ll have to configure the destination user as a local user. It might not work, just a guess.
Or, as you said, you can use cron to copy from one /var/mail/office to /var/spool/.
Mind that local users might use mailbox instead of maildir.

Paul P. September 24, 2019 - 8:27 AM

Thanks!! I’ve caught the logics and found another article: https://medium.com/mudita-misra/postfix-forward-email-to-another-email-account-a8d027fa18eb
The most important thing is that besides of editing virtual_alias_maps, in postfix main.cf there should be installed an option named `virtual_alias_domains ` and it should match all domains provided for the virtual folders. I.e.

virtual_alias_domains = domain-one.com domain-two.net domain-three.org

Then we should update the postmap and reload the postfix – and voila! Emails are forwarded to the system’s user account :)

Thanks for you help and tips!

Leave a Comment

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More