This documentation is for Dovecot v1.x, see wiki2 for v2.x documentation.


Howto Setup a Mail Server with Virtual Users and Domains


This document describes how to setup a Mail Server with Virtual Domains (Aliases and Mailboxes) with Dovecot POP3 and IMAP Server, Postfix MTA with Dovecot LDA transport and Dovecot SASL Authentication, MySQL, Postfix Admin and SquirrelMail. We use CentOS as the base system, so it's easily usable on various Red Hat Enterprice Linux (RHEL) versions and on Fedora, but probably also usable on all other system too.

A few words about the component selection. To avoid a religous war, we have not explained why we chose CentOS and MySQL. Postfix was a natural choice since it's not sendmail, many distributions support it, it's easy to configure and fast and secure. We have used Dovecot since 2002. With Dovecot LDA, the most resource intensive task (the indexing) is distributed as mail is received, not as the users open the Inbox. This can be very important in a situation with many mailboxes. With Dovecot SASL we can avoid Cyrus SASL which is more complicated, more heavy weight and much more complicated to configure. What's more, with Dovecot Auth we can use not just the same datasource but the same service for authentication both in the MTA (Postfix), POP3 and IMAP (Dovecot). Postfix Admin is almost the only usable solution to manage multiple domains, assign admins to different domains, manage alias and quotas for domains and for users and multilanguage. The most feature rich and multilanguage PHP based webmail system.

This document is based on the following documents:


This document assumes that you have some knowledge on server setup all of the required softwares. At least enough to get everything installed. Installing and basic configuration of the software is outside the scope of this document.


At the time of writing this document we use these versions of the required software (probably newer versions also usable):

How to Build the required packages

In some cases the packages shipped with the distribution (including the latest updates) are suitable; if not, we list how to obtain them and build them, but in case you're too lazy you can download all of these packages from here:-)

PHP 5.1.6

Probably CentOS 4 default 4.3.9 also usable. If you still would like to update to this version you can get this version from centosplus repository with:

yum --enablerepo=centosplus install php-mysql

MySQL 5.0.22

Probably CentOS 4 default 4.1.20 is usable also. If you still would like to update to this version you can get it from centosplus repository with:

yum --enablerepo=centosplus install mysql-server

Postfix 2.3.5

Maybe CentOS 4 default 2.2.10 is usable too, but 2.3.x series contains a few extension so it's advice to update. Get the latest source rpm fromFedora Core Developmentand install it (Note: the src.rpm not the i386.rpm) with:

rpm -Uvh postfix-2.3.x-1.src.rpm

If this is not the latest, you can get the latest source from any of thePostfix Download Siteand put int into rpm/SOURCES/ directory. In postfix.spec modify the version and add MySQL support (%define MYSQL 1) and build it:

rpmbuild -ba postfix.spec

Dovecot 1.0rc15

Warning: Do not use version 1.0.7 available in yum (Dovecot.x86_64 0:1.0.7-7.el5). This version contains a bug (file_dotlock_open() failed with file .. Operation not permitted) that does not create directories for new accounts.

Get the latest source rpm fromFedora Core Developmentand install it (Note: the src.rpm not the i386.rpm) with:

Test that postfix is compiled to support mysql and dovecot

hostname ~ # postconf -m | grep mysql
hostname ~ # postconf -a | grep dovecot

rpm -Uvh dovecot-1.0-1.rc15.fc7.src.rpm

In dovecot.spec remove PostgreSQL support (%define build_postgres 0) if you wouldn't like to install postgresql-lib and build it:

rpmbuild -ba dovecot.spec

Make sure that dovecot has been compiled with database support hostname ~ # dovecot --build-options | grep mysql SQL drivers: mysql postgresql sqlite 

Postfix Admin 2.1.1 or 2.2 or the latest from SVN (2.1.0 has a few bug:-()

If 2.1.1 or 2.2 is not released (not now) I suggest to use the latest from SVN:

svn co postfixadmin

If you receive this error when you try to start postfixadmin:

Warning: Unknown: failed to open stream: Permission denied in Unknown on line 0  Warning: Unknown: Failed opening '/var/www/postfixadmin/index.php' for inclusion (include_path='.:/usr/share/pear') in Unknown on line

It may be necessary to execute following 3 commands:

chown -R root:root /var/www/postfixadmin/

find /var/www/postfixadmin/ -type f -exec chmod u=rw,go=r {} \;

find /var/www/postfixadmin/ -type d -exec chmod u=rwx,go=rx {} \;


First of all we assume you install everything which is required and you do the basic configuration (e.g. Apache, PHP). Now lets configure our mail server components.


It does not require any kind of special configuration, but I suggest using the large configuration default file:

cp /usr/share/doc/mysql-server-*/my-large.cnf /etc/my.cnf

and use UTF-8 character encoding as a default for you server (to avoid later character problems). Add to your /etc/my.cnf :

default-character-set = utf8
default-collation = utf8_general_ci

If this is a new installation it will need to be initalized by running

mysql_install_db --user=mysql

Start your mysql server:

service mysqld start

Optionally you can secure the installation by running the wizard application


Now we can install the database required for Postfix Admin.

mysql -u root -p

If you created a password for root use it now otherwise just press enter.

mysql> CREATE DATABASE postfix;
mysql> CREATE USER postfix@localhost IDENTIFIED BY 'your_password';
mysql> GRANT ALL PRIVILEGES ON postfix.* TO postfix;
mysql> exit

Now add access rights to mysql for dovecot. We also add a "backup" user to backup the whole database (you can change password here too):

mysql> grant SELECT ON postfix.*  to 'dovecot'@'localhost' IDENTIFIED by 'dovecot';
mysql> grant SELECT, RELOAD, LOCK TABLES ON *.*  to 'backup'@'localhost' IDENTIFIED by 'dump';
mysql> flush privileges;
mysql> exit

That's all for MySQL.


At this point you have to decide a few things.

Now configure Dovecot itself. We list only where we change something in the default config. We useCACert certificates for SSL (that's outside of the scope of this docs), but you can use the default self-signed certs also. Here is our /etc/dovecot.conf everything else is commented out:

ssl_cert_file = /etc/pki/dovecot/certs/
ssl_key_file = /etc/pki/dovecot/private/
ssl_ca_file = /etc/pki/dovecot/certs/ca-bundle.crt
mail_location = maildir:/var/vmail/%d/%u
first_valid_uid = 101
last_valid_uid = 101
maildir_copy_with_hardlinks = yes
protocol imap {
  mail_plugins = quota imap_quota
  imap_client_workarounds = outlook-idle delay-newmail
protocol pop3 {
  mail_plugins = quota
  pop3_client_workarounds = outlook-no-nuls oe-ns-eoh
protocol lda {
  postmaster_address =
  mail_plugins = quota
  log_path = /var/log/dovecot-deliver.log
  info_log_path = /var/log/dovecot-deliver.log
auth default {
# Having "login" also as a mechanism make sure outlook can use the auth smtpd as well
  mechanisms = plain login
  passdb sql {
    args = /etc/dovecot/sql.conf
  userdb sql {
    args = /etc/dovecot/sql.conf
  userdb prefetch {
  user = nobody
  socket listen {
    master {
      path = /var/run/dovecot/auth-master
      mode = 0660
      user = vmail
      group = mail
    client {
      path = /var/spool/postfix/private/auth
      mode = 0660
      user = postfix
      group = mail
dict {
plugin {
  quota = maildir:storage=10240:messages=1000
  acl = vfile:/etc/dovecot/acls
  trash = /etc/dovecot/trash.conf

and two more config files /etc/dovecot/sql.conf (don't forget to fix the password here to the one you use in MySQL for Dovecot), more info here (the second query always use a static maildir and home which can be also used in our case) When copy & paste this text make sure you use one line for the query statements :

driver = mysql
connect = host=localhost dbname=postfix user=dovecot password=dovecot
user_query = SELECT concat('/var/vmail/', maildir) as home, concat('maildir:/var/vmail/', maildir) as mail, 101 AS uid, 12 AS gid, concat('maildir:storage=', quota) AS quota FROM mailbox WHERE username = '%u' AND active = '1'
# fast but now so nice:-)
#user_query = SELECT '/var/vmail/%d/%n' as home, 'maildir:/var/vmail/%d/%n' as mail, 101 AS uid, 12 AS gid, concat('dirsize:storage=', quota) AS quota FROM mailbox WHERE username = '%u' AND active = '1'
# Just in case you are using postfix the delimiter char "+", the above query will probably fail for the username '%n' or '%u' and result in a "5.5.1 user unknown" error
#in this case, you will probalby want to use a separate user and domain part, whilst searching only for the destination user part (user_query only):
# SELECT ... WHERE username = substring_index('%n','+',1) AND userrealm = '%d'
# OR: use "-d ${user}@${domain} -n -m ${extension}" (w/o quotes) for parameters for deliver in postfix/ (instead of "-d ${recipient}")

password_query = SELECT username as user, password, concat('/var/vmail/', maildir) as userdb_home, concat('maildir:/var/vmail/', maildir) as userdb_mail, 101 as userdb_uid, 12 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'
# fast but now so nice:-)
#password_query = SELECT username as user, password, '/var/vmail/%d/%n' as userdb_home, 'maildir:/var/vmail/%d/%n' as userdb_mail, 101 as userdb_uid, 12 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'

and /etc/dovecot/trash.conf (you may want to add Sent too to this file, but we wouldn't like), more info here:

1 Spam
2 Trash

You may also wish to configure the master ACL in /etc/dovecot/acls.

That's all for Dovecot.


First add this to the end of your /etc/postfix/ for LDA delivery:

# Dovecot LDA
dovecot   unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail:mail argv=/usr/libexec/dovecot/deliver -d ${recipient}

then the end of /etc/postfix/ (we collect all of our changes at the end of this file):

# --------------- local settings ------------------
myhostname                      =
inet_interfaces                 = localhost, $myhostname
mynetworks                      = $config_directory/mynetworks
mydestination                   = localhost.$mydomain, localhost, $myhostname
#uncomment if you need relay_domains... do not list domains in both relay and virtual
#relay_domains                   = proxy:mysql:$config_directory/
# ---------------------- VIRTUAL DOMAINS START ----------------------
virtual_mailbox_domains         = proxy:mysql:$config_directory/
virtual_mailbox_base            = /var/vmail
virtual_mailbox_maps            = proxy:mysql:$config_directory/
virtual_alias_maps              = proxy:mysql:$config_directory/
virtual_mailbox_limit_maps      = proxy:mysql:/etc/postfix/
virtual_minimum_uid             = 101
virtual_uid_maps                = static:101
virtual_gid_maps                = static:12
virtual_transport               = dovecot
dovecot_destination_recipient_limit = 1
# ---------------------- VIRTUAL DOMAINS END ----------------------
# ---------------------- SASL PART START ----------------------
smtpd_sasl_auth_enable          = yes
#smtpd_sasl_local_domain        = $myhostname
smtpd_sasl_exceptions_networks  = $mynetworks
smtpd_sasl_security_options     = noanonymous
broken_sasl_auth_clients        = yes
smtpd_sasl_type                 = dovecot
# Can be an absolute path, or relative to $queue_directory
smtpd_sasl_path                 = private/auth
# ---------------------- SASL PART END ----------------------
# ---------------------- TLS PART START ----------------------
smtp_tls_CAfile                 = /etc/pki/tls/certs/cert.pem
smtp_tls_cert_file              = /etc/pki/tls/certs/
smtp_tls_key_file               = /etc/pki/tls/private/
#Postfix 2.5 or greater must use:
#smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache
smtp_tls_session_cache_database = btree:/var/spool/postfix/smtp_tls_session_cache
smtp_tls_security_level = may
smtpd_tls_CAfile                = /etc/pki/tls/certs/cert.pem
smtpd_tls_cert_file             = /etc/pki/tls/certs/
smtpd_tls_key_file              = /etc/pki/tls/private/
#Postfix 2.5 or greater must use:
#smtpd_tls_session_cache_database = btree:$data_directory/smtpd_tls_session_cache
smtpd_tls_session_cache_database = btree:/var/spool/postfix/smtpd_tls_session_cache
smtpd_tls_dh1024_param_file     = $config_directory/dh_1024.pem
smtpd_tls_dh512_param_file      = $config_directory/dh_512.pem
smtpd_tls_security_level        = may
smtpd_tls_received_header       = yes
smtpd_tls_ask_ccert             = yes
smtpd_tls_loglevel              = 1
tls_random_source               = dev:/dev/urandom
# ---------------------- TLS PART END ----------------------
smtpd_helo_required             = yes
disable_vrfy_command            = yes
non_fqdn_reject_code            = 450
invalid_hostname_reject_code    = 450
maps_rbl_reject_code            = 450
#unverified_sender_reject_code  = 550
#header_checks                  = pcre:$config_directory/header_checks
#body_checks                    = pcre:$config_directory/body_checks
#warning: the restrictions reject_unknown_(sender|recipient)_domain
#will trigger if your DNS becomes unavailable
smtpd_recipient_restrictions =
        warn_if_reject reject_non_fqdn_helo_hostname
        warn_if_reject reject_unknown_helo_hostname
        warn_if_reject reject_unknown_client
        warn_if_reject reject_rhsbl_sender
        warn_if_reject reject_rhsbl_sender
        warn_if_reject reject_rhsbl_sender
        warn_if_reject reject_rhsbl_sender
        warn_if_reject reject_rhsbl_sender
smtpd_data_restrictions =

This may seems to be a little bit complicated, but most of it is not relevant to you. If you like to start with a minimal set delete all rbl, rhsbl, check, reject and permit lines.

Comment from a user: I found out, that I had to add the line, local_transport=virtual; without this line, the mail was not delivered to the created virtual users.

Comment from another user: it would be very nice, if anybody could resolve this ambiguity - THANKS!

The other important files /etc/postfix/ (don't forget to change the passwords here):

user            = postfix
password        = postfix
hosts           = localhost
dbname          = postfix
query           = SELECT goto FROM alias WHERE address='%s' AND active = '1'


user            = postfix
password        = postfix
hosts           = localhost
dbname          = postfix
#query          = SELECT domain FROM domain WHERE domain='%s'
#optional query to use when relaying for backup MX
query           = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '0' AND active = '1'


user            = postfix
password        = postfix
hosts           = localhost
dbname          = postfix
query           = SELECT domain FROM domain WHERE domain='%s' and backupmx = '1'


user            = postfix
password        = postfix
hosts           = localhost
dbname          = postfix
query           = SELECT quota FROM mailbox WHERE username='%s' AND active = '1'


user            = postfix
password        = postfix
hosts           = localhost
dbname          = postfix
query           = SELECT CONCAT(domain,'/',maildir) FROM mailbox WHERE username='%s' AND active = '1'

Comment from a user: I found out that the query string in did not work for me together with the query string from sql.conf for dovecot. That is because the domain is added for postfix, but for dovecot it is not. Therefore I changed the query string in to query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'

Comment from another user: it would be very nice, if anybody could resolve this ambiguity - THANKS!


# This specifies the list of subnets that Postfix considers as
# "trusted" SMTP clients that have more privileges than "strangers".
# In particular, "trusted" SMTP clients are allowed to relay mail
# through Postfix.
# Moja Mreza Moji Linkovi

That's all for Postfix.

Postfix Admin

Warning: currently postfixadmin fux up with mysql, if you use utf-8 tables - so be careful and use latin1 encoding. Authors of postfixadmin: meanwhile please go and study the last posting of Jason Urrich here:

If you have more virtual domains in you Web Server than uncomment everything in/etc/httpd/conf.d/postfixadmin.confand put it into your VirtualHost in Apache. Edit local settings in/etc/postfixadmin/'s well documented), here is our setting (comments dropped):

if (ereg ("", $_SERVER['PHP_SELF']))
   header ("Location: login.php");
#$CONF['postfix_admin_url'] = '';
$CONF['postfix_admin_url'] = '';
$CONF['postfix_admin_path'] = '';
$CONF['default_language'] = 'en';
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'postfixadmin';
$CONF['database_password'] = 'postfixadmin';
$CONF['database_name'] = 'postfix';
$CONF['database_prefix'] = '';
$CONF['database_prefix'] = '';
$CONF['database_tables'] = array (
   'admin' => 'admin',
   'alias' => 'alias',
   'domain' => 'domain',
   'domain_admins' => 'domain_admins',
   'log' => 'log',
   'mailbox' => 'mailbox',
   'vacation' => 'vacation'
$CONF['admin_email'] = '';
$CONF['smtp_server'] = 'localhost';
$CONF['smtp_port'] = '25';
$CONF['encrypt'] = 'md5';
$CONF['generate_password'] = 'YES';
$CONF['show_password'] = 'NO';
$CONF['page_size'] = '10';
$CONF['default_aliases'] = array (
        'abuse' => '',
        'hostmaster' => '',
        'postmaster' => '',
        'webmaster' => ''
$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';
$CONF['aliases'] = '10';
$CONF['mailboxes'] = '10';
$CONF['maxquota'] = '10';
$CONF['quota'] = 'YES';
$CONF['quota_multiplier'] = '1024000';
$CONF['transport'] = 'NO';
$CONF['transport_options'] = array (
   'virtual',  // for virtual accounts
   'local',    // for system accounts
   'relay'     // for backup mx
$CONF['transport_default'] = 'virtual';
$CONF['vacation'] = 'NO';
$CONF['vacation_domain'] = '';
$CONF['vacation_control'] ='YES';
$CONF['vacation_control_admin'] = 'YES';
$CONF['alias_control'] = 'NO';
$CONF['alias_control_admin'] = 'YES';
$CONF['special_alias_control'] = 'YES';
$CONF['alias_goto_limit'] = '0';
$CONF['backup'] = 'YES';
$CONF['sendmail'] = 'YES';
$CONF['logging'] = 'YES';
$CONF['show_header_text'] = 'NO';
$CONF['header_text'] = ':: Postfix Admin ::';
$CONF['show_footer_text'] = 'YES';
$CONF['footer_text'] = 'Return to';
$CONF['footer_link'] = '';
$CONF['welcome_text'] = <<<EOM
Welcome to your new account.

WARNING: the original author did not want to tell us which version of postfixadmin he is talking about here - I recommend not to path the current version!

Unfortunately the current SVN of Postfix Admin has a few bugs but it's still usable and better than the latest release. Apply this patch to SVN code (thanks to Stefan Rubner):

--- postfixadmin/  2006-12-27 14:31:35.000000000 +0100
+++ postfixadmin/      2006-12-27 14:32:35.000000000 +0100
@@ -887,6 +887,10 @@
       $password = md5crypt ($pw, $salt);
+   if ($CONF['encrypt'] == 'md5')
+   {
+      $password = md5($pw);
+   }
    if ($CONF['encrypt'] == 'system')


patch -p1 < postfixadmin-md5.patch

That's all!


File <$config_directory/mynetworks> is not in the postfix installation. But it is referenced in this (great) howto, resulting in errors in /var/log/maillog. Can you add it to the howto please?

Comment from user: 2 things: - Instead of creating the vmail user's home dir in /var/ it is better to do that in /home. -> Also note that the logpath /var/log/dovecot-deliver.log is most likely not writable by dovecot since /var/log is by default only writeable by root. - Also I would rather choose a more memorable and not to be confused with other user's GUI and UID for the vmail user, since this id is referenced throughout Something like 5000 is fine.


if you are facing issues increase the logging


Test the postfix config

postfix check

Test the users storage path

postmap -q  mysql:/etc/postfix/

test the SMTP port is open, you type lines marked as you>

you> telnet localhost 25
Connected to localhost.localdomain (
Escape character is '^]'.
220 mail.acme.local ESMTP Postfix
you> ehlo host
250-SIZE 10240000
250 DSN
you> mail from: johndoe
250 2.1.0 Ok
you> rcpt to: johndoe
250 2.1.5 Ok
you> data
354 End data with <CR><LF>.<CR><LF>
you> test
you> .
250 2.0.0 Ok: queued as 9729067C17
you> quit
221 2.0.0 Bye
Connection closed by foreign host.

HowTo/DovecotLDAPostfixAdminMySQL (last edited 2013-03-23 18:17:46 by speedy)