Virtual Mail Hosting on Centos7
Postfix MariaDB Dovecot PostfixAdmin Amavisd-new Spamassassin and Clamav

Virtual Mail hosting provides the option of offering mail services to multiple domains on one server.

You will get the following features:

This guide was developed from lessons I learned from Campworld Centos6 Virtual Mail Hosting.  Plus lots of discussions and near flame wars on lists for many of the packages used here.

Further, this guide strives to only modify package config files, rather than replacing them.  This is a nod to those package authors on knowing what works best in their packages and easier adoption of new features.


NOTE: The services set up here should be run on a system with at least 1GB of memory with 2GB really being the lower limit.  Additionally, though it will run on a duo-core ARMv7 system, a quad core would be better with AMAVIS restricted to 2 core.  AMAVIS and CLAMAV can really tie up the system.

FURTHER NOTE: SELinux policies are enforced!  In some cases this took working with the SELinux experts to develop polices (e.g. Dovecot to Mysql).  No laid back additude of just running in SELinux permissive mode.

Building the Base Server

Follow the instructions at: Centos7 for armv7 SOC

to build your base Centos7 armv7 server.  Be sure to add the EPEL repo as many of the mailserver packages are there.

Installing the Mail packages

This guide needs the following packages installed:

yum install httpd mod_ssl php mariadb-server php-mysql telnet
yum install dovecot dovecot-mysql dovecot-pigeonhole policycoreutils
yum install perl-MIME-EncWords perl-MIME-Charset php-imap
yum install perl-Email-Valid perl-Test-Pod
yum install perl-Mail-Sender imapsync offlineimap
yum install roundcubemail php-mcrypt php-enchant aspell-en
yum install spamassassin amavisd-new clamav clamav-devel perl-Convert-BinHex
yum install clamav-server clamav-update clamav-server-systemd clamav-scanner

There is no rpm package for postfixadmin.  Get the most recent version at:
Let's assume that the current version you downloaded is: 3.2.  Place it in /usr/share.

cd /usr/share


tar -xzvf postfixadmin-3.2.tar.gz
mv postfixadmin-3.2/ postfixadmin
rm -f postfixadmin-3.2*
chown -R root:root postfixadmin
chmod 700 postfixadmin/templates/
chown apache:apache postfixadmin/templates/

Configuring the Mail packages

Throughout this guide, there are values unique to an installation that have to be provided.  For the most part, these can be handled by first setting some environment variables that will be used in cat and sed commands.  Or you can manually alter the variables.  Special characters (\, $, and / tested) MUST be proceeded by a \.  Spaces should work with quotes around value entered (but not really tested).

To set date_timezone value.  See

for valid timezones (use the same timezone name you used for timedatectl).  Set the variables by altering these commands.

Long passphrases may be more secure for the passwords, and not cause problems with the commands, than the use of special characters or spaces.


Three TLS certificates are created in this guide.  All should have the following values, but any MAY be left blank.  If any include a space, inclose that value in quotes.  Special characters can be included, preceeded by \.


Now there is a lot to configure from all these packages and perhaps you are testing from a temporary IP address that is not in DNS.  For this you need to add to your /etc/hosts file:

bind 'set disable-completion on'
cat <<EOF>>/etc/hosts || exit 1
$your_ipv4_address	$your_host_tld
bind 'set disable-completion off'

Next we have a lot of firewall rules to add:

firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --add-service=smtp
firewall-cmd --permanent --add-service=imaps
firewall-cmd --permanent --add-service=pop3s
firewall-cmd --permanent --add-port=587/tcp
firewall-cmd --permanent --add-port=143/tcp
firewall-cmd --permanent --add-port=110/tcp
firewall-cmd --permanent --add-port=4190/tcp
firewall-cmd --reload
firewall-cmd --list-all

Port 587 is used by client smtp to send mail to your mailserver.  Port 143 is used by IMAP.  Port 110 is for POP3.  Port 4190 is used by manageseive.  Amavis uses ports 10024 & 10025 but only to localhost so no firewall rule is needed there.

Now edit /etc/php.ini

sed -i -e "s';date.timezone ='date.timezone = $date_timezone'w /dev/stdout" /etc/php.ini

Now create the mail store directory.  Put it in the /home directory to make backups and other item easy.

mkdir /home/vmail
chmod 770 /home/vmail
useradd -r -u 101 -g mail -d /home/vmail -s /sbin/nologin -c "Virtual mailbox" vmail
chown vmail:mail /home/vmail
chcon -Rv --type=mail_home_rw_t /home/vmail/

Setting up MariaDB

Next start MariaDB and secure it.  You can manually run mysql_secure_installation, or use the following to auto answer the prompts.

systemctl enable mariadb
systemctl start mariadb
mysql_secure_installation <<EOF || exit 1


Now setup the mysql database for postfixadmin. The following creates the database and user. The setup URL will create the rest.

mysql -u root -p$Mysql_Root_Password <<EOF || exit 1
CREATE USER postfix@localhost IDENTIFIED BY "$Postfix_Database_Password";
GRANT ALL PRIVILEGES ON postfix.* TO postfix@localhost;

Next is the Roundcube database.

mysql -u root -p$Mysql_Root_Password <<EOF || exit 1
CREATE DATABASE roundcubemail;
CREATE USER roundcube@localhost IDENTIFIED BY "$Roundcube_Database_Password";
GRANT ALL PRIVILEGES ON roundcubemail.* TO roundcube@localhost;

Then the tables.

mysql -u root -p$Mysql_Root_Password roundcubemail < /usr/share/roundcubemail/SQL/mysql.initial.sql

Setting up Apache

This guide uses virtual hosting for the webmail service (Roundcubemail).  Virtual hosting requires a DNS entry for webmail.your domain as well as your host.  Once virtual hosting is configured, the first virtual host is the default host.  Additionally, once a virtual host is set to use TLS, this also becomes the default behavior.

This conf file will load first and force a polite behavior.

bind 'set disable-completion on'
cat <<EOF>/etc/httpd/conf.d/00-init.conf || exit 1
ServerAdmin $admin_email
ServerName $your_host_tld
<VirtualHost *:80>
	<Directory "/var/www/html">
		Options Indexes FollowSymLinks
		AllowOverride None
		Require all granted
<VirtualHost *:443>
	SSLEngine On
	SSLCertificateFile /etc/pki/tls/certs/$your_host_tld.crt
	SSLCertificateKeyFile /etc/pki/tls/private/$your_host_tld.key
	<Directory "/var/www/html">
		Options Indexes FollowSymLinks
		AllowOverride None
		Require all granted
bind 'set disable-completion off'

The default server access can be restricted with:

sed -i -e "s/all granted/ip\/16/gw /dev/stdout" /etc/httpd/conf.d/00-init.conf

Next is postfixadmin's Apache conf file. 

bind 'set disable-completion on'
cat <<EOF>/etc/httpd/conf.d/postfixadmin.conf || exit 1
alias /mailadmin /usr/share/postfixadmin/public
<Directory "/usr/share/postfixadmin/public">
	AllowOverride AuthConfig
	Require all granted
bind 'set disable-completion off'

Postfixadmin is a Security Risk; Postfixadmin access can be restricted to your IP addresses.  Finer control can be added into a /usr/share/postfixadmin/.htaccess file.

sed -i -e "s/all granted/ip\/16/gw /dev/stdout" /etc/httpd/conf.d/postfixadmin.conf

Postfixadmin access is via 'http://your_host_tld/mailadmin'.  You don't want the whole world accessing this link.

Roundcubemail httpd configuration is next.  This guide uses virtual hosting.  The conf can be edited to use your_host_tld/webmail.

Further, this guide forces users to https.  This is enhanced over the default roundcubemail.conf.  Also it secures the cookie used by Roundcubemail.  This is an important security deficiency in the default configuration.

cp -b /etc/httpd/conf.d/roundcubemail.conf /etc/httpd/conf.d/
bind 'set disable-completion on'
cat <<EOF>/etc/httpd/conf.d/roundcubemail.conf || exit 1
<VirtualHost *:80>

#	Alias /roundcubemail /usr/share/roundcubemail
#	Alias /webmail /usr/share/roundcubemail

	ServerName webmail.$your_domain_tld
	ServerAlias webmail

	Redirect permanent / https://webmail.$your_domain_tld/
	ExpiresDefault "access plus 1 years"
	php_admin_flag session.cookie_secure "1"


<VirtualHost *:443>

# Round Cube Webmail is a browser-based multilingual IMAP client

#	Alias /roundcubemail /usr/share/roundcubemail
#	Alias /webmail /usr/share/roundcubemail

	ServerName webmail.$your_domain_tld
	ServerAlias webmail

	SSLEngine On
	SSLCertificateFile /etc/pki/tls/certs/webmail.$your_domain_tld.crt
	SSLCertificateKeyFile /etc/pki/tls/private/webmail.$your_domain_tld.key

	DocumentRoot /usr/share/roundcubemail

<Directory /usr/share/roundcubemail/>
	Require ip
# You can enlarge permissions once configured
#	Require all granted
	php_admin_flag session.cookie_secure "1"


# Define who can access the installer
# keep this secured once configured

	Alias /roundcubemail /usr/share/roundcubemail

<Directory /usr/share/roundcubemail/installer/>
# You may want to restrict the installer to a single IP address
	Require ip

# Those directories should not be viewed by Web clients.
<Directory /usr/share/roundcubemail/bin/>
	Require all denied
<Directory /usr/share/roundcubemail/plugins/enigma/home/>
	Require all denied
bind 'set disable-completion off'

Last step for setting up Apache is creating the SSL certificates.  Three certificates are needed.  One for webmail.$your_domain_tld, another for $your_host_tld, and the third for localhost.  Follow how you setup the roundcubemail.conf and how your users will access your server.  The following commands will create the RSA 2048/SHA256 certificates with a 10 years life.

These are self-signed certificates.  You can purchase your certificates from a recognized CA.  You can find instructions on how to create your own CA and issue these certificates from it.  Future work will add a section on using Let's Encrypt certificates.

restore_mask=$(umask -p)
umask 077
cd /etc/pki/tls
openssl req -new -outform PEM -out certs/$commonName.crt -newkey rsa:2048 -nodes -keyout private/$commonName.key -keyform PEM -days 3650 -x509 -extensions v3_req -subj "/countryName=$countryName/stateOrProvinceName=$stateOrProvinceName/localityName=$localityName/organizationName=$organizationName/organizationalUnitName=$organizationalUnitName/commonName=$commonName/emailAddress=$emailAddress"
chmod 640 private/$commonName.key
openssl req -new -outform PEM -out certs/localhost.crt -newkey rsa:2048 -nodes -keyout private/localhost.key -keyform PEM -days 3650 -x509 -extensions v3_req -subj "/countryName=$countryName/stateOrProvinceName=$stateOrProvinceName/localityName=$localityName/organizationName=$organizationName/organizationalUnitName=$organizationalUnitName/commonName=$commonName/emailAddress=$emailAddress"
chmod 640 private/localhost.key
openssl req -new -outform PEM -out certs/$commonName.crt -newkey rsa:2048 -nodes -keyout private/$commonName.key -keyform PEM -days 3650 -x509 -extensions v3_req -subj "/countryName=$countryName/stateOrProvinceName=$stateOrProvinceName/localityName=$localityName/organizationName=$organizationName/organizationalUnitName=$organizationalUnitName/commonName=$commonName/emailAddress=$emailAddress"
chmod 640 private/$commonName.key

You can review the certificate content with:

openssl x509 -in certs/$commonName.crt -text -nameopt multiline -noout|more

If you get any of the certificates 'wrong' httpd will not start, and trouble-shooting can be a challenge. Check out /etc/httpd/logs/ssl_error_log for messages hinting at what is wrong with your certificates.

It is now time to enable and start Apache.

systemctl enable httpd
systemctl start httpd

Configuring Postfix

Now on to configure the actual packages starting with Postfix.

Postfix is a real task to configure.  It will be easier in versions of Postfix beyond 2.10 that is supplied in Centos 7.  The following modifies the existing config files, rather than replacing them.

This is a 'best effort' from reviewing a number of sources. 

The place to start is with  Note that message_size_limit limits a message to ~20Meg.  Change this as needed.

cp /etc/postfix/ /etc/postfix/
# postfix config file

# uncomment for debugging if needed
#postconf -e 'soft_bounce=yes'

# postfix main
postconf -e 'delay_warning_time = 4'

# network settings
postconf -e 'inet_interfaces = all'
postconf -e "mydomain = $your_domain_tld"
postconf -e "myhostname = $your_host_tld"
postconf -e 'mynetworks = $config_directory/mynetworks'
postconf -e 'relay_domains = proxy:mysql:/etc/postfix/'

# mail delivery
postconf -e 'recipient_delimiter = +'
postconf -e 'content_filter = amavisfeed:[]:10024'

# mappings
postconf -e 'alias_maps = hash:/etc/aliases'
postconf -e 'transport_maps = hash:/etc/postfix/transport'

# virtual setup
postconf -e 'virtual_alias_maps = proxy:mysql:/etc/postfix/, regexp:/etc/postfix/virtual_regexp'
postconf -e 'virtual_mailbox_base = /home/vmail'
postconf -e 'virtual_mailbox_domains = proxy:mysql:/etc/postfix/'
postconf -e 'virtual_mailbox_maps = proxy:mysql:/etc/postfix/'
postconf -e 'virtual_minimum_uid = 101'
postconf -e 'virtual_uid_maps = static:101'
postconf -e 'virtual_gid_maps = static:12'
postconf -e 'virtual_transport = dovecot'
postconf -e 'dovecot_destination_recipient_limit = 1'

# authentication
postconf -e 'smtpd_sasl_auth_enable = yes'
# postconf -e 'smtpd_sasl_security_options = noanonymous'
postconf -e 'smtpd_sasl_local_domain = $mydomain'
postconf -e 'broken_sasl_auth_clients = yes'
postconf -e 'smtpd_sasl_type = dovecot'
postconf -e 'smtpd_sasl_path = private/auth'

# tls config
postconf -e 'smtp_use_tls = yes'
postconf -e 'smtp_tls_exclude_ciphers = RC4, aNULL'
postconf -e 'smtp_tls_loglevel = 1'
postconf -e 'smtp_tls_note_starttls_offer = yes'
postconf -e 'smtp_tls_protocols = !SSLv2, !SSLv3'
postconf -e 'smtp_tls_security_level = may'
postconf -e 'smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache'
postconf -e 'smtpd_tls_cert_file = /etc/pki/tls/certs/$myhostname.crt'
postconf -e 'smtpd_tls_exclude_ciphers = RC4, aNULL'
postconf -e 'smtpd_tls_key_file = /etc/pki/tls/private/$myhostname.key'
postconf -e 'smtpd_tls_loglevel = 1'
postconf -e 'smtpd_tls_mandatory_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CDC3-SHA, KRB5-DE5, CBC3-SHA'
postconf -e 'smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3'
postconf -e 'smtpd_tls_protocols = !SSLv2, !SSLv3'
postconf -e 'smtpd_tls_received_header = yes'
postconf -e 'smtpd_tls_security_level = may'
postconf -e 'smtpd_tls_session_cache_database = btree:/var/lib/postfix/smtpd_scache'
postconf -e 'smtpd_use_tls = yes'

# rules restrictions
postconf -e 'smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination, reject_non_fqdn_sender, reject_non_fqdn_recipient, reject_unknown_recipient_domain'
postconf -e 'smtpd_sender_login_maps = mysql:/etc/postfix/'

# others
postconf -e 'smtpd_helo_required = yes'
postconf -e 'disable_vrfy_command = yes'
postconf -e 'message_size_limit = 20480000'
postconf -e 'smtpd_data_restrictions = reject_unauth_pipelining'
postconf -e 'smtpd_banner = $myhostname ESMTP' is next.  I have worked out how to just append needed changes to the end.

cp /etc/postfix/ /etc/postfix/

bind 'set disable-completion on'
cat <<\EOF>>/etc/postfix/ || exit 1
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
smtpd     pass  -       -       n       -       -       smtpd
submission inet n       -       n       -       -       smtpd
	-o smtpd_recipient_restrictions=
relay     unix  -       -       n       -       -       smtp
	-o fallback_relay=
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient}
uucp      unix  -       n       n       -       -       pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
ifmail    unix  -       n       n       -       -       pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp     unix  -       n       n       -       -       pipe
  flags=Fq. user=bsmtp argv=/usr/local/sbin/bsmtp -f $sender $nexthop $recipient
# spam/virus section
amavisfeed unix	-	-	y	-	2	lmtp
	-o lmtp_data_done_timeout=1200
	-o lmtp_send_xforward_command=yes
	-o disable_dns_lookups=yes
	-o max_use=20 inet n	-	n	-	-	smtpd
	-o content_filter=
	-o smtpd_delay_reject=no
	-o smtpd_client_restrictions=permit_mynetworks,reject
	-o smtpd_helo_restrictions=
	-o smtpd_sender_restrictions=
	-o smtpd_recipient_restrictions=permit_mynetworks,reject
	-o smtpd_data_restrictions=reject_unauth_pipelining
	-o smtpd_end_of_data_restrictions=
	-o smtpd_restriction_classes=
	-o mynetworks=
	-o smtpd_error_sleep_time=0
	-o smtpd_soft_error_limit=1001
	-o smtpd_hard_error_limit=1000
	-o smtpd_client_connection_count_limit=0
	-o smtpd_client_connection_rate_limit=0
	-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters
	-o local_header_rewrite_clients=
	-o smtpd_milters=
	-o local_recipient_maps=
	-o relay_recipient_maps=
# Dovecot LDA
dovecot	unix	-	n	n	-	-	pipe
  flags=DRhu user=vmail:mail argv=/usr/libexec/dovecot/deliver -d ${recipient}
# Vacation mail
vacation unix	-	n	n	-	-	pipe
  flags=Rq user=vacation argv=/var/spool/vacation/ -f ${sender} -- ${recipient}
bind 'set disable-completion off'

And if you have to support Outlook TLS connections, then

bind 'set disable-completion on'
cat <<EOF>>/etc/postfix/ || exit 1
# Enable only if you have to support Outlook TLS connections
smtps	inet	n	-	n	-	-	smtpd
bind 'set disable-completion off'

Next a number of files in /etc/postfix

cat <<EOF>/etc/postfix/mynetworks || exit 1
# 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.
# Be sure to add your public ip address block if needed.

cat <<EOF>/etc/postfix/ || exit 1
hosts = localhost
user = postfix
password = $Postfix_Database_Password
dbname = postfix
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'

cat <<EOF>/etc/postfix/ || exit 1
hosts = localhost
user = postfix
password = $Postfix_Database_Password
dbname = postfix
query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '0' AND active = '1'

cat <<EOF>/etc/postfix/ || exit 1
hosts = localhost
user = postfix
password = $Postfix_Database_Password
dbname = postfix
query = SELECT domain FROM domain WHERE domain='%s' and backupmx = '1'

cat <<EOF>/etc/postfix/ || exit 1
hosts = localhost
user = postfix
password = $Postfix_Database_Password
dbname = postfix
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'

touch /etc/postfix/virtual_regexp

Edit /etc/postfix/mynetworks as needed,

The last step for postfix is the Vacation Email Functionality

useradd -r -d /var/spool/vacation -s /sbin/nologin -c "Virtual vacation" vacation
mkdir /var/spool/vacation
chmod 770 /var/spool/vacation
cp /usr/share/postfixadmin/VIRTUAL_VACATION/ /var/spool/vacation/
echo "autoreply."$your_domain_tld" vacation:" > /etc/postfix/transport
postmap /etc/postfix/transport
chown -R vacation:vacation /var/spool/vacation
echo "    autoreply."$your_domain_tld >> /etc/hosts
mkdir /etc/postfixadmin

And finally create /etc/postfixadmin/vacation.conf

cat <<\EOF>/etc/postfixadmin/vacation.conf || exit 1
# ========== begin configuration ==========
$db_type = 'mysql';
$db_username = 'user';
$db_password = 'postfixsqlpassword';
$db_name = 'postfix';
$vacation_domain = 'autoreply.change-this-to-your.domain.tld';
sed -i -e "s/postfixsqlpassword/$Postfix_Database_Password/w /dev/stdout" /etc/postfixadmin/vacation.conf
sed -i -e "s/change-this-to-your.domain.tld/$your_domain_tld/w /dev/stdout" /etc/postfixadmin/vacation.conf

Finally restart postfix

systemctl restart postfix

Configuring Dovecot

Dovecot is next.  There are a number of Dovecot conf files to edit.  Instead, this guide uses the local.conf file which is loaded after the default conf files.  By using the default files and local.conf, these modifications should work in future versions of Dovecot.  The files altered by the local.conf are:


bind 'set disable-completion on'
cat <<EOF>/etc/dovecot/local.conf || exit 1
#	Developed on Dovecot 2.2.10

#	dovecot.conf
protocols = imap pop3 lmtp sieve
dict {
	sqlquota = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext

#	10-auth.conf
!include conf.d/auth-sql.conf.ext

#	auth-sql.conf.ext
userdb {
	driver = prefetch

#	10-mail.conf
mail_location = maildir:/home/vmail/%d/%n
first_valid_uid = 101
first_valid_gid = 12

#	10-master.conf
service auth {
	unix_listener auth-userdb {
		mode = 0666
		user = vmail
		group = mail
	unix_listener /var/spool/postfix/private/auth {
		mode = 0666
		user = postfix
		group = postfix
service dict {
	unix_listener dict {
		mode = 0666
		user = vmail
		group = mail

#	10-ssl.conf
ssl_cert = </etc/pki/tls/certs/$your_host_tld.crt
ssl_key = </etc/pki/tls/private/$your_host_tld.key

#	15-lda.conf
postmaster_address = postmaster@$your_domain_tld
protocol lda {
	mail_plugins = quota sieve
lda_mailbox_autocreate = yes
lda_mailbox_autosubscribe = yes

#	20-imap.conf
imap_client_workarounds = delay-newmail
protocol imap {
	mail_plugins = quota imap_quota trash

#	20-lmtp.conf
lmtp_save_to_detail_mailbox = yes
protocol lmtp {
	mail_plugins = sieve

#	20-managesieve.conf
service managesieve-login {
	inet_listener sieve {
		port = 4190
	service_count = 1
	process_min_avail = 0
	vsz_limit = 64M

#	20-pop3.conf
pop3_client_workarounds = outlook-no-nuls oe-ns-eoh
protocol pop3 {
	mail_plugins = quota

#	90-acl.conf
plugin {
	acl = vfile:/etc/dovecot/global-acls:cache_secs=300

#	90-quota.conf
plugin {
	quota = dict:user::proxy::sqlquota
	trash = /etc/dovecot/dovecot-trash.conf.ext

#	90-sieve.conf
plugin {
	sieve_before = /home/sieve/globalfilter.sieve
bind 'set disable-completion off'

Next the following files are added into /etc/dovecot
Note that the user_query limits an imap mailbox to 30,000 messages.  Change this as needed.

mv /etc/dovecot/dovecot-trash.conf.ext /etc/dovecot/
mv /etc/dovecot/dovecot-sql.conf.ext /etc/dovecot/
mv /etc/dovecot/dovecot-dict-sql.conf.ext /etc/dovecot/

bind 'set disable-completion on'
cat <<EOF>/etc/dovecot/dovecot-trash.conf.ext || exit 1
# Spam mailbox is emptied before Trash
1 Spam
# Trash mailbox is emptied before Sent
# 2 Trash
# If both Sent and "Sent Messages" mailboxes exist, the next oldest message
# to be deleted is looked up from both of the mailboxes.
# 3 Sent
# 3 Sent Messages

cat <<EOF>/etc/dovecot/dovecot-sql.conf.ext || exit 1
driver = mysql
connect = host=/var/lib/mysql/mysql.sock dbname=postfix user=postfix password=$Postfix_Database_Password
default_pass_scheme = MD5-CRYPT
# following should all be on one line.
password_query = SELECT username as user, password, concat('/home/vmail/', maildir) as userdb_home, concat('maildir:/home/vmail/', maildir) as userdb_mail, 101 as userdb_uid, 12 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'
# following should all be on one line
user_query = SELECT concat('/home/vmail/', maildir) as home, concat('maildir:/home/vmail/', maildir) as mail, 101 AS uid, 12 AS gid, CONCAT('*:messages=30000:bytes=', quota) as quota_rule FROM mailbox WHERE username = '%u' AND active = '1'

cat <<EOF>/etc/dovecot/dovecot-dict-sql.conf.ext || exit 1
connect = host=/var/lib/mysql/mysql.sock dbname=postfix user=postfix password=$Postfix_Database_Password
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
bind 'set disable-completion off'

Now create the sieve filter for SPAM filtering

bind 'set disable-completion on'
mkdir /home/sieve
cat <<EOF>/home/sieve/globalfilter.sieve || exit 1
require "fileinto";
if anyof
		header :contains "X-Spam-Flag" "YES",
		header :contains "subject" "***SPAM***"
	fileinto "Spam";
bind 'set disable-completion off'

sievec /home/sieve/globalfilter.sieve
chown -R vmail:mail /home/sieve
chcon -Rv --type=mail_home_rw_t /home/sieve/

SELinux needs a policy to allow Dovecot to access the mysql unix socket.  As of Feb 5, 2019, this policy has been rolled into Fedora 28 and up, and in time will be included in CentOS.

bind 'set disable-completion on'
cat <<\EOF>dovecot_mysql.te || exit 1
	type dovecot_t;
bind 'set disable-completion off'

make -f /usr/share/selinux/devel/Makefile dovecot_mysql.pp
semodule -i dovecot_mysql.pp

It is now time to enable and start Dovecot.

systemctl enable dovecot
systemctl start dovecot

Configuring Amavis and Clamav

These two packages work together, along with Spamassassin, to provide anti-spam and anti-virus protection.  Their configuration is simple.

With some guidance from
And more from
We can get clamav working with the following:

cp /etc/clamd.d/scan.conf /etc/clamd.d/
cp /etc/freshclam.conf /etc/
sed -i -e "s/^Example/#Example/w /dev/stdout" /etc/freshclam.conf
sed -i -e "s/^Example/#Example/w /dev/stdout" /etc/clamd.d/scan.conf
sed -i -e "s/clamd.scan /clamd.amavisd /w /dev/stdout" /etc/clamd.d/scan.conf
sed -i -e "s/^#LocalSocket /LocalSocket /w /dev/stdout" /etc/clamd.d/scan.conf
sed -i -e "s/^FRESHCLAM_DELAY/#FRESHCLAM_DELAY/w /dev/stdout" /etc/sysconfig/freshclam
setsebool -P antivirus_can_scan_system 1
setsebool -P clamd_use_jit 1

Next edit /etc/amavisd/amavisd.conf.

cp /etc/amavisd/amavisd.conf /etc/amavisd/
cat <<\EOF>>/etc/amavisd/amavisd.conf || exit 1
$mydomain = 'your_domain_tld';
$myhostname = 'your_host_tld'; # must be a fully-qualified domain name!
$log_level = 1; # set the log level to one
$sa_tag_level_deflt = -99; # I want to see the headers so change to -99
$sa_tag2_level_deflt = 5.0; # start with 5
$sa_kill_level_deflt = 9;
$sa_dsn_cutoff_level = 9;
$sa_quarantine_cutoff_level = 50;
$notify_method = 'smtp:[]:10025';
$forward_method = 'smtp:[]:10025';
$final_banned_destiny = D_BOUNCE;
$final_spam_destiny = D_PASS;
@mynetworks = qw( [::1] [FE80::]/10 [FEC0::]/10
@lookup_sql_dsn =
   ( ['DBI:mysql:database=postfix;host=localhost;mysql_socket=/var/lib/mysql/mysql.sock', 'postfix', 'postfixsqlpassword'] );
$sql_select_white_black_list = undef;
$sql_select_policy = 'SELECT "Y" as local, 1 as id FROM domain WHERE CONCAT("@",domain) IN (%k)';
1; # insure a defined return value
sed -i -e "s/your_domain_tld/$your_domain_tld/w /dev/stdout" /etc/amavisd/amavisd.conf
sed -i -e "s/your_host_tld/$your_host_tld/w /dev/stdout" /etc/amavisd/amavisd.conf
sed -i -e "s/your_ipv4_address_block/$your_ipv4_address_block/w /dev/stdout" /etc/amavisd/amavisd.conf
sed -i -e "s/postfixsqlpassword/$Postfix_Database_Password/w /dev/stdout" /etc/amavisd/amavisd.conf

Finally start it all:

gpasswd -a clamscan amavis
systemctl enable amavisd
systemctl enable spamassassin
systemctl stop postfix
systemctl start spamassassin
systemctl start amavisd # This will also run ClamAV
systemctl start postfix

A simple test of clamav is

clamdscan -c /etc/clamd.d/scan.conf /etc/hosts

Test the amavis connection with

telnet localhost 10024
ehlo localhost

telnet localhost 10025
ehlo localhost

You should get

telnet localhost 10024
Trying ::1...
Connected to localhost.
Escape character is '^]'.
220 [::1] ESMTP amavisd-new service ready
ehlo localhost

telnet localhost 10025
Trying ::1...
telnet: connect to address ::1: Connection refused
Connected to localhost.
Escape character is '^]'.
ehlo localhost
250-SIZE 20480000
250 DSN

Configuring Postfixadmin

Postfixadmin is the email domain and email account management tool.

Create a base /usr/share/postfixadmin/config.local.php

bind 'set disable-completion on'
cat <<\EOF>/usr/share/postfixadmin/config.local.php || exit 1
 * Contains configuration options that override the default config file

 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 * You have to set \$CONF['configured'] = true; before the
 * application will run!
 * Doing this implies you have changed this file as required.
 * i.e. configuring database etc; specifying setup.php password etc.
$CONF['configured'] = true;

// In order to setup Postfixadmin, you MUST specify a hashed password here.
// To create the hash, visit /postfixadmin/setup.php in a browser and type a password into the field,
// on submission it will be echoed out to you as a hashed value.
$CONF['setup_password'] = 'changeme';
$CONF['database_password'] = 'postfixsqlpassword';
// Default Aliases
// The default aliases that need to be created for all domains.
$CONF['default_aliases'] = array (
	'abuse' => 'abuse@change-this-to-your.domain.tld',
	'hostmaster' => 'hostmaster@change-this-to-your.domain.tld',
	'postmaster' => 'postmaster@change-this-to-your.domain.tld',
	'webmaster' => 'webmaster@change-this-to-your.domain.tld'
$CONF['admin_email'] = 'postfixadmin_email';
$CONF['dovecotpw'] = "/usr/bin/doveadm pw";
$CONF['page_size'] = '20';
$CONF['aliases'] = '50';
$CONF['mailboxes'] = '50';
$CONF['maxquota'] = '100';
$CONF['transport'] = 'YES';
$CONF['vacation'] = 'YES';
$CONF['vacation_domain'] = 'autoreply.change-this-to-your.domain.tld';
$CONF['special_alias_control'] = 'YES';
$CONF['footer_text'] = 'Return to change-this-to-your.domain.tld';
$CONF['footer_link'] = 'http://change-this-to-your.domain.tld';
$CONF['quota'] = 'YES';
$CONF['used_quotas'] = 'YES';
$CONF['new_quota_table'] = 'YES';
// $CONF['create_mailbox_subdirs_hostoptions']=array('notls');

bind 'set disable-completion off'
sed -i -e "s/postfixadmin_email/$admin_email/w /dev/stdout" /usr/share/postfixadmin/config.local.php
sed -i -e "s/postfixsqlpassword/$Postfix_Database_Password/w /dev/stdout" /usr/share/postfixadmin/config.local.php
sed -i -e "s/change-this-to-your.domain.tld/$your_domain_tld/gw /dev/stdout" /usr/share/postfixadmin/config.local.php

SELinux seems to be a problem for Postfixadmin (and Roundcubemail which needs the setsebool).

chcon -R -t httpd_sys_content_rw_t /usr/share/postfixadmin/templates
setsebool -P httpd_can_network_connect on

Now go to host/mailadmin/setup.php

Setup should proceed to create/update its database structures.

Next is creating the Setup Password by clicking by entering password and clicking on generate hash.  Then edit /usr/share/postfixadmin/config.local.php with the provided hash.

Use this password to add your postfixadmin admin account.  Use this account to log into host/mailadmin.  Create a mail domain; set up a DNS MX entry for it and create a user.  Postfixadmin should set up all the files for the user and send it a welcome message that will be the basis for testing the components.

Configuring Roundcubemail

Roundcubemail configuring is easier now with the installer, but there are a few permission items to take care of first.

chown root:apache /etc/roundcubemail
chmod 775 /etc/roundcubemail

Roundcubemail is written in php and a couple php defaults may not be right for Roundcubemail, particularly the file upload size and message size maximums.  The following will comment out the defaults and add new maximums.  Change them as you need.

sed -i -e "/^upload_max_filesize/s/^/#/w /dev/stdout" /etc/php.ini
sed -i -e '/^#upload_max_filesize/ a upload_max_filesize = 5M' /etc/php.ini
sed -i -e "/^post_max_size/s/^/#/w /dev/stdout" /etc/php.ini
sed -i -e '/^#post_max_size/ a post_max_size = 20M' /etc/php.ini

Depending on how you setup roundcubemail.conf for httpd


The installer does most of the work.  Things to do in the install include:
In the General configuration, select Enchant spellcheck_engine.
In the Database section set your Roundcubemail MySQL password.
In the IMAP section, change the junk_mbox to Spam.
In the SMTP section,
set smtp_server to fqdn
Check "Use the current IMAP username and password for SMTP authentication"
In the Plugins section,
Check managesieve
Check password
Click the UPDATE CONFIG button, start a
cat > /etc/roundcubemail/
copy the content of the text box into the prompt and end with a Cntl-D.

Do not add a closing ?> tag.

Follow the instructions to test Roundcubemail.

If in your system 0 quota means no limit

cat <<\EOF>>/etc/roundcubemail/ || exit 1
$config['quota_zero_as_unlimited'] = true;

To allow browser-autocompletion of username and host on login form.

cat <<\EOF>>/etc/roundcubemail/ || exit 1
$config['login_autocomplete'] = 1;

To directly delete messages in Junk instead of moving to Trash.

cat <<\EOF>>/etc/roundcubemail/ || exit 1
$config['delete_junk'] = true;

This quide uses 'Spam' for the 'Junk' folder.  To force Roundcubemail to display 'Spam', rather than 'Junk' as default.

cat <<\EOF>>/etc/roundcubemail/ || exit 1
$config['show_real_foldernames'] = true;

Apache is configured to redirect to secure connections.  Roundcubemail can check and redirect if the Apache configuration is wrong with:

cat <<\EOF>>/etc/roundcubemail/ || exit 1
$config['force_https'] = true;

Now we set up the manage sieve plugin.

cp /usr/share/roundcubemail/plugins/managesieve/ /usr/share/roundcubemail/plugins/managesieve/

For the password plugin:

cp /usr/share/roundcubemail/plugins/password/ /usr/share/roundcubemail/plugins/password/
cat <<\EOF>>/usr/share/roundcubemail/plugins/password/ || exit 1
$config['password_db_dsn'] = 'mysql://postfix:postfixsqlpassword@localhost/postfix';
$config['password_query'] = 'UPDATE mailbox SET password=%c WHERE username=%u limit 1;';
sed -i -e "s/postfixsqlpassword/$Postfix_Database_Password/w /dev/stdout" /usr/share/roundcubemail/plugins/password/

Restart Apache.

systemctl restart httpd

Testing the Configuration

This completes configuring the software. Next are some tests to confirm things are working.

Use Postfixadmin to create a virtual domain and a user or so.  Use Roundcubemail to log into a user account and check receipt of the Postfixadmin welcome message.

Roundcubemail use did a basic test of dovecot imap access.  For a fuller test, use all of the following connection methods (via fqdn from a remote system).

# IMAP access
telnet localhost 143
telnet $your_host_tld 143
openssl s_client -connect $your_host_tld:993
openssl s_client -connect $your_host_tld:143 -starttls imap
# POP3 access
telnet localhost 110
telnet $your_host_tld 110
openssl s_client -connect $your_host_tld:995
openssl s_client -connect $your_host_tld:110 -starttls pop3

IMAP log in and some commands are

a login userid password
b select inbox
c list "" *
c fetch 1 body[header]
d lsub "" *
e logout

POP3 log in and some commands are

user userid
pass password
retr 1

For more on Dovecot testing see

For anti-virus testing, find sample.tar.gz.compl (check the version of amavisd-new) and change to that folder and untar it

cd /usr/share/doc/amavisd-new-2.11.1/test-messages
perl -pe 's/./chr(ord($&)^255)/sge' <sample.tar.gz.compl | zcat | tar xvf -

Then send the following emails.  virus-sample should be dropped, GTUBE should be moved to Spam, and README should end up in INBOX.

sendmail -i useremail < sample-virus-simple.txt
sendmail -i useremail < sample-spam-GTUBE-junk.txt
sendmail -i useremail < README


© Robert G. Moskowitz -- 2019