Tutorials: How to set up a mail server on Ubuntu with Postfix, Dovecot 2, MySQL and PostfixAdmin

Index
Introduction
Step #1: Create databases
Step #2: Configure DNS
Step #3: Add user and create directory
Step #4: Configure PostfixAdmin
Step #5: Install and configure Postfix
Step #6: Install and configure Dovecot
Step #7: Configure Roundcube
Step #8: Start the services
Step #9: Initial settings with PostfixAdmin
Postscripts: Debugging
References

 

Introduction
What services do we want:
▪ IMAP(S)
▪ SMTP(S)
▪ POP3(S)

What features do we want:
▪ Let’s Encrypt SSL
▪ Dovecot SASL
▪ Quota
▪ Mail sender address restrictions
▪ Web Mail (Roundcube)
▪ Web Administratiron (PostfixAdmin)

What do we need:
▪ Postfix (3.1.0-3)
▪ Dovecot (2.2.22-1ubuntu2)
▪ MySQL (5.7.15-1ubuntu16.04)
▪ php7 (7.0+44+deb.sury.org~xenial+1)
▪ nginx (1.11.3-0+xenial0)
▪ Roundcube (1.2.1)
▪ PostfixAdmin (2.93)
You don’t have to download the vary listed versions, they are only examples.

 

Step #1: Create databases
Here we need two databases: one for PostfixAdmin and the other for roundcube.

Database	User		Password
mail_auth	mail_auth	mail_auth_example
roundcube	roundcube	roundcube_example

You’d better choose utf8_unicode_ci in order to avoid problems.
It’s a good choice to use phpMyAdmin if you’re not familiar to MySQL commands. It’s one of my favorite tools which is of great convenience.

 

Step #2: Configure DNS

(Sub) Domain	Type	Data
@				MX		10 smtp.example.com.
mail			A		111.111.111.111
smtp			A		111.111.111.111
imap			A		111.111.111.111
pop3			A		111.111.111.111

Please edit the records based on your own environment.

 

Step #3: Add user and create directory
Run:

useradd -r -u 150 -g mail -d /var/vmail -s /sbin/nologin -c "Virtual mailbox" vmail
mkdir /var/vmail
chmod 770 /var/vmail/
chown vmail:mail /var/vmail/

 

Step #4: Configure PostfixAdmin
Download PostfixAdmin and extract it to /var/www/PostfixAdmin

Edit /var/www/PostfixAdmin/config.inc.php

// Only lines to edit listed here
// DO NOT copy the text directly to your configuration document
$CONF['configured'] = true;
$CONF['default_language'] = 'en';

$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'mail_auth';
$CONF['database_password'] = 'mail_auth_example';
$CONF['database_name'] = 'mail_auth';

$CONF['encrypt'] = 'md5crypt';

$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';
$CONF['maildir_name_hook'] = 'NO';

$CONF['aliases'] = '0';
$CONF['mailboxes'] = '0';
$CONF['maxquota'] = '0';
$CONF['domain_quota_default'] = '2048';

$CONF['quota'] = 'YES';
$CONF['domain_quota'] = 'YES';
$CONF['quota_multiplier'] = '1048576';

$CONF['used_quotas'] = 'YES';
$CONF['new_quota_table'] = 'YES';

$CONF['mailbox_postdeletion_script'] = 'sudo -u vmail -g mail /var/vmail/Bin/postfixadmin-mailbox-postdeletion.sh';
$CONF['domain_postdeletion_script'] = 'sudo -u vmail -g mail /var/vmail/Bin/postfixadmin-domain-pos deletion.sh';

And then, we should create the Post-deletion scripts.

#!/bin/sh

# Example script for removing a Maildir domain top-level folder
# from a Courier-IMAP virtual mail hierarchy.

# The script only looks at argument 1, assuming that it 
# indicates the relative name of a domain, such as
# "somedomain.com". If $basedir/somedomain.com exists, it will
# be removed.

# The script will not actually delete the directory. I moves it
# to a special directory which may once in a while be cleaned up
# by the system administrator.

# This script should be run as the user which owns the maildirs. If 
# the script is actually run by the apache user (e.g. through PHP),
# then you could use "sudo" to grant apache the rights to run
# this script as the relevant user.
# Assume this script has been saved as
# /usr/local/bin/postfixadmin-domain-postdeletion.sh and has been
# made executable. Now, an example /etc/sudoers line:
# apache ALL=(courier) NOPASSWD: /usr/local/bin/postfixadmin-domain-postdeletion.sh
# The line states that the apache user may run the script as the
# user "courier" without providing a password.


# Change this to where you keep your virtual mail users' maildirs.
basedir=/var/vmail

# Change this to where you would like deleted maildirs to reside.
trashbase=/var/vmail/Trash

if [ `echo $1 | fgrep '..'` ]; then
    echo "First argument contained a double-dot sequence; bailing out."
    exit 1
fi

if [ ! -e "$trashbase" ]; then
    echo "trashbase '$trashbase' does not exist; bailing out."
    exit 1
fi

trashdir="${trashbase}/`date +%F_%T`_$1"
domaindir="${basedir}/$1"

if [ ! -e "$domaindir" ]; then
    echo "Directory '$domaindir' does not exits; nothing to do."
    exit 0;
fi
if [ ! -d "$domaindir" ]; then
    echo "'$domaindir' is not a directory; bailing out."
    exit 1
fi
if [ -e "$trashdir" ]; then
    echo "Directory '$trashdir' already exits; bailing out."
    exit 1;
fi

mv $domaindir $trashdir

exit $?
#!/bin/sh

# Example script for removing a Maildir from a Courier-IMAP virtual mail
# hierarchy.

# The script looks at arguments 1 and 2, assuming that they 
# indicate username and domain, respectively.

# The script will not actually delete the maildir. I moves it
# to a special directory which may once in a while be cleaned up
# by the system administrator.

# This script should be run as the user which owns the maildirs. If 
# the script is actually run by the apache user (e.g. through PHP),
# then you could use "sudo" to grant apache the rights to run
# this script as the relevant user.
# Assume this script has been saved as
# /usr/local/bin/postfixadmin-mailbox-postdeletion.sh and has been
# made executable. Now, an example /etc/sudoers line:
# apache ALL=(courier) NOPASSWD: /usr/local/bin/postfixadmin-mailbox-postdeletion.sh
# The line states that the apache user may run the script as the
# user "courier" without providing a password.


# Change this to where you keep your virtual mail users' maildirs.
basedir=/var/vmail

# Change this to where you would like deleted maildirs to reside.
trashbase=/var/vmail/Trash


if [ ! -e "$trashbase" ]; then
    echo "trashbase '$trashbase' does not exist; bailing out."
    exit 1
fi

if [ `echo $1 | fgrep '..'` ]; then
    echo "First argument contained a double-dot sequence; bailing out."
    exit 1
fi
if [ `echo $2 | fgrep '..'` ]; then
    echo "First argument contained a double-dot sequence; bailing out."
    exit 1
fi

subdir=`echo "$1" | sed 's/@.*//'`

maildir="${basedir}/$2/${subdir}"
trashdir="${trashbase}/$2/`date +%F_%T`_${subdir}"

parent=`dirname "$trashdir"`
if [ ! -d "$parent" ]; then
    if [ -e "$parent" ]; then
        echo "Strainge - directory '$parent' exists, but is not a directory."
        echo "Bailing out."
        exit 1
    else
        mkdir -p "$parent"
        if [ $? -ne 0 ]; then
            echo "mkdir -p '$parent' returned non-zero; bailing out."
            exit 1
        fi
    fi
fi

if [ ! -e "$maildir" ]; then
    echo "maildir '$maildir' does not exist; nothing to do."
    exit 0
fi
if [ -e "$trashdir" ]; then
    echo "trashdir '$trashdir' already exists; bailing out."
    exit 1
fi

mv $maildir $trashdir

exit $?

Of course, we should also grant the permissions to the web server to run the scripts as vmail.

yourname:~# sudo su
root:/home/yourname# cd /etc

Defaults    requiretty
#Comment the line out ↓ notice that if you're running Ubuntu this may be not necessary
#Defaults    requiretty

root:/etc# cd sudoers.d
root:/etc/sudoers.d# touch nginx
root:/etc/sudoers.d# vim nginx

www-data ALL=(vmail:mail) NOPASSWD: ALL
#this will grant www-data to run anything as vmail in mail group without passwords
#you can also change the NOPASSWD: ALL to NOPASSWD: /absolute/path/to/script to make it safer

root:/etc/sudoers.d# service nginx restart
root:/etc/sudoers.d# exit

Next, let’s go to http://mail.example.com/PostfixAdmin/
Set a password and copy the return hashed value to /var/www/PostfixAdmin/config.inc.php

$CONF['setup_password'] = 'Your Hash Here';

Finally, follow the web guidance to add an administrator account. Here we use:
Account: admin@example.com
Password: admin_example

 

Step #5: Install and configure Postfix

apt-get install postfix postfix-mysql postfix-pcre

When prompted, choose Internet Site and then type example.com for your domain.

After the installation, navigate to /etc/postfix and edit /etc/postfix/main.cf

# See /usr/share/postfix/main.cf.dist for a commented, more complete version


# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# TLS parameters
tls_medium_cipherlist = AES128+EECDH:AES128+EDH
smtpd_tls_cert_file = /etc/letsencrypt/live/example.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/example.com/privkey.pem
smtpd_use_tls = yes
smtpd_tls_security_level = may
smtpd_tls_auth_only = no
smtpd_tls_mandatory_protocols = !SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_protocols = !SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_mandatory_ciphers = medium
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache

smtp_use_tls = yes
smtp_tls_security_level = may
smtp_tls_mandatory_protocols = !SSLv3,!TLSv1,!TLSv1.1
smtp_tls_protocols = !SSLv3,!TLSv1,!TLSv1.1
smtp_tls_mandatory_ciphers = medium
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.

myhostname = smtp.example.com
alias_maps = hash:/etc/postfix/aliases
alias_database = hash:/etc/postfix/aliases
myorigin = smtp.example.com
mydestination = localhost
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all

# User Define
virtual_uid_maps = static:150
virtual_gid_maps = static:8
virtual_mailbox_base = /var/vmail
virtual_gid_maps = static:8
virtual_mailbox_base = /var/vmail
virtual_mailbox_domains = mysql:/etc/postfix/mysql_virtual_mailbox_domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql_virtual_alias_maps.cf
#relay_domains = mysql:/etc/postfix/mysql_relay_domains.cf
virtual_transport = lmtp:unix:private/dovecot-lmtp
smtpd_recipient_limit = 10
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtpd_sender_login_maps = pcre:/etc/postfix/login_maps.pcre
smtpd_helo_restrictions =
 permit_mynetworks,
 permit_sasl_authenticated,
 reject_non_fqdn_helo_hostname,
 reject_invalid_helo_hostname,
 permit
smtpd_sender_restrictions =
 reject_sender_login_mismatch,
 reject_non_fqdn_sender,
 reject_unknown_sender_domain,
 permit_mynetworks,
 permit_sasl_authenticated,
 permit
smtpd_recipient_restrictions =
 reject_non_fqdn_recipient,
 reject_unknown_recipient_domain,
 permit_mynetworks,
 permit_sasl_authenticated,
 reject_unauth_destination,
 reject_unlisted_recipient,
 permit
smtpd_relay_restrictions =
 permit_mynetworks,
 permit_sasl_authenticated,
 defer_unauth_destination
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
#milter_default_action = accept

# Quota
virtual_create_maildirsize = yes
virtual_mailbox_extended = yes
virtual_mailbox_limit_maps = mysql:/etc/postfix/mysql_virtual_mailbox_limit_maps.cf
virtual_mailbox_limit_override = yes
virtual_maildir_limit_message = Sorry, the user's maildir has overdrawn his diskspace quota, please try again later.
virtual_overquota_bounce = yes

Here because the configuration is not that long so I pasted a full configuration file here. You can just replace example.com with your domain and then copy it to your server.

Nowadays, you can easily generate a certificate with the help of Let’s Encrypt. They have got a convenient online guide: Certbot. Open the link and select Nginx & Ubuntu, then add the following block to your Nginx configuration if needed,

    location ~ ^/\.well-known\/.* {
        allow all;
        log_not_found off;
    }

Finally just follow the guide to get your certificates. Note that appending --rsa-key-size 4096 to your command is highly recommended.

Next, we have to edit the /etc/postfix/master.cf

submission inet 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_client_restrictions=permit_sasl_authenticated,reject
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
#  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
smtps     inet  n       -       -       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
#  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING

#### Add the following line to the end of the file
dovecot unix - n n - - pipe flags=DRhu user=vmail:mail argv=/usr/lib/dovecot/deliver-lda -d ($recipient)

Finally, create other files under /etc/postfix

root@mail.example.com:/etc/postfix# cat login_maps.pcre
/^(.*)@example.com$/   ${1}@example.com

root@mail.example.com:/etc/postfix# cat mysql_virtual_alias_maps.cf
hosts = 127.0.0.1
user = mail_auth
password = mail_auth_example
dbname = mail_auth
query = SELECT goto FROM alias WHERE address='%s' AND active = 1

root@mail.example.com:/etc/postfix# cat mysql_virtual_mailbox_domains.cf
hosts = 127.0.0.1
user = mail_auth
password = mail_auth_example
dbname = mail_auth
query = SELECT domain FROM domain WHERE domain='%s' and backupmx = 0 and active = 1

root@mail.example.com:/etc/postfix# cat mysql_virtual_mailbox_limit_maps.cf
hosts = 127.0.0.1
user = mail_auth
password = mail_auth_example
dbname = mail_auth
query = SELECT quota FROM mailbox WHERE username = '%s' AND active = 1

root@mail.example.com:/etc/postfix# cat mysql_virtual_mailbox_maps.cf
hosts = 127.0.0.1
user = mail_auth
password = mail_auth_example
dbname = mail_auth
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = 1

 

Step #6: Install and configure Dovecot

apt-get install dovecot-core dovecot-imapd dovecot-lmtpd dovecot-mysql dovecot-pop3d

After the installation, navigate to /etc/dovecot/conf.d and modify the files. Take care of commenting and uncommenting.

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

# 10-mail.conf
mail_location = maildir:/var/vmail/%d/%n:INDEX=/var/vmail/%d/%n/indexes
first_valid_uid = 150
last_valid_uid = 150
mail_plugins = quota

# 10-master.conf
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0660
    user = postfix
    group = postfix
  }
}
service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
  }
}
service dict {
  unix_listener dict {
    mode = 0600
    user = vmail
    group = mail
  }
}

# 10-ssl.conf
ssl = yes
ssl_cert = </etc/letsencrypt/live/example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/example.com/privkey.pem

# 15-lda.conf
postmaster_address =postmaster@%d
protocol lda {
  mail_plugins = $mail_plugins
}

# 15-mailboxes.conf
namespace inbox {
  mailbox Drafts {
    special_use = \Drafts
  }
  mailbox Junk {
    auto = subscribe
    special_use = \Junk
  }
  mailbox Trash {
    auto = subscribe
    special_use = \Trash
  }
  mailbox Sent {
    special_use = \Sent
  }
  mailbox "Sent Messages" {
    special_use = \Sent
  }
}

# 20-imap.conf
imap_max_line_length = 64k
protocol imap {
  mail_plugins = $mail_plugins imap_quota
  mail_max_userip_connections = 10
}

# 20-pop3.conf
protocol pop3 {
  mail_plugins = $mail_plugins
}

# 90-quota.conf
plugin {
  #quota = dict:user::proxy::quota
  #quota2 = dict:domain:%d:proxy::quota_domain
  #quota_rule = *:storage=102400
  #quota2_rule = *:storage=1048576
  quota = dict:User quota::proxy::sqlquota
  quota2 = dict:Domain quota:%d:proxy::sqlquota
}
dict {
  sqlquota = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext
}

Finally, create the other required files under /etc/dovecot

driver = mysql
connect = host=localhost dbname=mail_auth user=mail_auth password=mail_auth_example
default_pass_scheme = MD5-CRYPT
password_query = SELECT password FROM mailbox WHERE username = '%u'
user_query = SELECT '/var/vmail/%d/%n' as home, CONCAT('*:storage=', quota , 'B') AS quota_rule, 150 AS uid, 8 AS gid FROM mailbox WHERE username = '%u'
connect = host=localhost dbname=mail_auth user=mail_auth password=mail_auth_example
map {
  pattern = priv/quota/storage
  table = quota2
  username_field = username
  value_field = bytes
}
map {
  pattern = priv/quota/messages
  table = quota2
  username_field = username
  value_field = messages
}
#map {
#  pattern = shared/expire/$user/$mailbox
#  table = expires
#  value_field = expire_stamp
#
#  fields {
#    username = $user
#    mailbox = $mailbox
#  }
#}

 

Step #7: Configure Roundcube
Download Roundcube complete version and extract it to /var/www/mail

Then visit http://mail.example.com/mail/installer/
Follow the guidance, check your dependencies and you’ll get a configuration file.
All of the hosts including MySQL, IMAP, SMTP are localhost, and just use the default ports as you do not need SSL for local communication. DO NOT forget to check the password authentication for SMTP.
Write the configuration you get to /var/www/mail/config/config.inc.php and add this line into the file to avoid abusing of the installer:

$config['enable_installer'] = false;

Finally, edit the /var/www/mail/config/defaults.inc.php

// Only lines to edit listed here
// DO NOT copy the text directly to your configuration document
$config['db_dsnw'] = 'mysql://roundcube:roundcube_example@localhost/roundcube';
$config['identities_level'] = 3;
$config['quota_zero_as_unlimited'] = true;

The file is quite long but with lots of useful configurations in it, you can read it if you’re interested.

 

Step #8: Start the services

service postfix restart
service dovecot restart
service mysql restart
service php5-fpm restart
service nginx restart

Many warning will be printed while starting Postfix, just ignore them.

 

Step #9: Initial settings with PostfixAdmin
Visit http://mail.example.com/PostfixAdmin

Click Domain List – New Domain (You can change the numbers according to your requirements)

Domain						example.com
Description					Example site
Aliases						200
Mailboxes					100
Max Mailbox Quota			2048
Domain Quota				819200
Mail server is backup MX	Unchecked
Active						Checked
Add default mail aliases	Checked

Then you can click Virtual List – Add Mailbox to add your users.

 

Postscripts: Debugging
Check the log files: /var/log/syslog, /var/log/mail.log

 

References
“Debian Wheezy Mail Server – Postfix Dovecot Sasl MySQL PostfixAdmin RoundCube SpamAssassin Clamav Greylist Nginx PHP5”. XenLens. Last modified August 24, 2014. Accessed August 29, 2014. http://www.xenlens.com/debian-wheezy-mail-server-postfix-dovecot-sasl-mysql-postfixadmin-roundcube-spamassassin-clamav-greylist-nginx-php5/.
“MAIL SERVER HOWTO – POSTFIX AND DOVECOT WITH MYSQL AND TLS/SSL, POSTGREY AND DSPAM”. Johnny Chadda. Last modified April 15, 2007. Accessed August 29, 2014. http://johnny.chadda.se/article/mail-server-howto-postfix-and-dovecot-with-mysql-and-tlsssl-postgrey-and-dspam/.

 

Updates
2014/09/02 Commented reject_unauth_destination from smtpd_recipient_restrictions out, this parameter should not be added here. It may cause problems on sending emails to external destinations.
2014/11/21 Support Post-deletion scripts for PostfixAdmin
2015/01/24 Add post master setting in 15-lda.conf
2015/05/11 Add permit_sasl_authenticated to smtpd_helo_restrictions to permit Outlook to log in.
2015/08/08 Fixed a mistake in “90-quota.conf” which may give clients a wrong quota; Trash directory is auto-subscribed in “15-mailboxes.conf” as some clients won’t create it; Minor changes in the bash commands where sudoers files are edited.
2015/08/14 Auto-subscribe Junk folder as the preparation of writing the tutorial for anti-spam and anti-virus.
2016/09/10 Use Let’s Encrypt instead of StartSSL.
2019/01/23 Use TLS to transfer emails to other servers if possible.
2019/03/13 Update smtpd_recipient_restrictions.

3コメント

コメントを残す