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.
How about using LetsEncrypt SSL rather than StartSSL ? 😀
Yes it seems to be a better plan for free SSL now. StartSSL has started to cooperate with a Chinese company which I don’t trust very much.
I’ll try to use LetsEncrypt later, not only for the mail server but the web as well.
Now using Let’s Encrypt instead of StartSSL, cheers! 🙂