Building My Mail Server

I have built a multi-domain email server on Ubuntu with postfix, dovecot, mysql, roundcube and spamassassin by following this guide. This post is a cut-through refined version from the exact steps that I followed, written while executing. I hope it will be useful to some people:

2017 Update: I wouldn’t go through these steps now and just use a hassle-free prebuilt docker image.

Let’s start.

apt-get update
apt-get upgrade --assume-yes

1. Start with setting hostname

apt-get install --assume-yes systemd-services
hostnamectl set-hostname mail.example.com
nano /etc/hosts #put new hostname and static ip

We’ll replace these with trusted ssl cert later on:

apt-get install --assume-yes ssl-cert
make-ssl-cert generate-default-snakeoil --force-overwrite

2. Install lamp server

apt-get install --assume-yes lamp-server^
apt-get install --assume-yes php-apc php5-mcrypt php5-memcache php5-curl php5-gd php-xml-parser
ln -sf ../../mods-available/mcrypt.ini /etc/php5/apache2/conf.d/20-mcrypt.ini

3. Configure PHP (8)

The default configuration settings for PHP and the additional packages mentioned above are sufficient for most casual usage. So unless you have something complicated or high-powered in mind, you should probably only change the expose_php setting in /etc/php5/apache2/php.ini. Set it to “Off”:

	expose_php = Off

4. Configure Apache (9)

Firstly configure the following lines in /etc/apache2/conf-enabled/security.conf to minimize the information that Apache gives out in its response headers:

	ServerTokens Prod
	 
	ServerSignature Off

Edit these lines in /etc/apache2/mods-available/ssl.conf to ensure that protocols that are no longer secure are not used:

	SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5
	SSLProtocol all -SSLv3

Put these in /etc/apache2/sites-available/000-default.conf

	<Directory "/var/www/html">
		Options FollowSymLinks
		AllowOverride All
	</Directory>
	LogLevel warn		

Put these in /etc/apache2/sites-available/default-ssl.conf

    # seb:
    <Directory "/var/www/html">
      Options FollowSymLinks
      AllowOverride All
      SSLOptions +StdEnvVars
    </Directory>
    
    SSLCertificateFile /etc/ssl/private/sebworks.com.crt
    SSLCertificateKeyFile /etc/ssl/private/sebworks.com.key
	SSLCertificateChainFile /etc/ssl/private/sub.class1.server.ca.pem

Put your keys and chain file under /etc/ssl/private, then

chmod -R 600 /etc/ssl/private

Edit /var/www/html/.htaccess

	RewriteEngine On
	# Redirect all HTTP traffic to HTTPS.
	RewriteCond %{HTTPS} !=on
	RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L]
	
service apache2 restart

5. Install more packages

apt-get install --assume-yes memcached

Choose Internet Site and default hostname (sigma.sebworks.com). Select no for dovecot ssl certificate question.

apt-get install --assume-yes mail-server^

Install other stuff. Note that I omit postgrey because we don’t need that much protection.

apt-get install --assume-yes \
	postfix-mysql \
	dovecot-mysql \
	amavis \
	clamav \
	clamav-daemon \
	spamassassin \
	php5-imap   ln -sf ../../mods-available/imap.ini /etc/php5/apache2/conf.d/20-imap.ini   service apache2 restart

I have removed some unnecessary packages from original doc, these are OK:

apt-get install --assume-yes \
	pyzor \
	cabextract \
	p7zip-full \
	unzip \
	unrar-free \
	zip

6. Setup mysql tables

mysql -uroot -p
create database mail;
grant all on mail.* to 'mail'@'localhost' identified by 'secret';

7. Install postfix admin

cd /root
wget http://downloads.sourceforge.net/project/postfixadmin/postfixadmin/postfixadmin-2.3.7/postfixadmin-2.3.7.tar.gz
tar -xf postfixadmin-2.3.7.tar.gz
rm -f postfixadmin-2.3.7.tar.gz
mv postfixadmin-2.3.7 /var/www/html/postfixadmin
chown -R www-data:www-data /var/www/html/postfixadmin

Follow original guide 13 and 14 here. Put .htaccess at the end to block setup.php. Create accounts.

My conflicting changes to /var/www/html/postfixadmin/config.inc.php:

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

8. Create vmail user

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

9. Dovecot configuration

Follow original guide 16 here.

My conflicting changes to /etc/dovecot/conf.d/10-auth.conf:

	auth_default_realm = sebworks.com

10. Configure Amavis, ClamAV, and SpamAssassin

Follow original guide 17 here.

11. Configure Postfix

Convenience bash commands for editing various files. Change MAILPASSWORD first.

export MAILPASSWORD=secret
cat << EOF > /etc/postfix/mysql_virtual_alias_domainaliases_maps.cf
user = mail
password = $MAILPASSWORD
hosts = 127.0.0.1
dbname = mail
query = SELECT goto FROM alias,alias_domain
  WHERE alias_domain.alias_domain = '%d'
  AND alias.address=concat('%u', '@', alias_domain.target_domain)
  AND alias.active = 1
EOF

cat << EOF > /etc/postfix/mysql_virtual_alias_maps.cf
user = mail
password = $MAILPASSWORD
hosts = 127.0.0.1
dbname = mail
table = alias
select_field = goto
where_field = address
additional_conditions = and active = '1'
EOF

cat << EOF > /etc/postfix/mysql_virtual_domains_maps.cf
user = mail
password = $MAILPASSWORD
hosts = 127.0.0.1
dbname = mail
table = domain
select_field = domain
where_field = domain
additional_conditions = and backupmx = '0' and active = '1'
EOF

cat << EOF > /etc/postfix/mysql_virtual_mailbox_domainaliases_maps.cf
user = mail
password = $MAILPASSWORD
hosts = 127.0.0.1
dbname = mail
query = SELECT maildir FROM mailbox, alias_domain
  WHERE alias_domain.alias_domain = '%d'
  AND mailbox.username=concat('%u', '@', alias_domain.target_domain )
  AND mailbox.active = 1
EOF

cat << EOF > /etc/postfix/mysql_virtual_mailbox_maps.cf
user = mail
password = $MAILPASSWORD
hosts = 127.0.0.1
dbname = mail
table = mailbox
select_field = CONCAT(domain, '/', local_part)
where_field = username
additional_conditions = and active = '1'
EOF

cat << EOF > /etc/postfix/header_checks
/^User-Agent:/               IGNORE
/^X-Mailer:/                 IGNORE
/^X-Originating-IP:/         IGNORE
EOF

I have changed much of postfix settings from the original guide so don’t use it.

main.cf:

cp /etc/postfix/main.cf /etc/postfix/main.cf.original

cat << 'EOF' > /etc/postfix/main.cf 
# See /usr/share/postfix/main.cf.dist for a commented, more complete version

# SASL parameters
# ---------------------------------

# Use Dovecot to authenticate.
smtpd_sasl_type = dovecot
# Referring to /var/spool/postfix/private/auth
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain =
smtpd_sasl_authenticated_header = yes

# TLS parameters
# ---------------------------------

smtpd_tls_auth_only = yes
smtpd_tls_security_level = may
smtp_tls_security_level = may
smtpd_tls_cert_file=/etc/ssl/private/sebworks.com.crt
smtpd_tls_key_file=/etc/ssl/private/sebworks.com.key
smtpd_tls_CAfile=/etc/ssl/private/sub.class1.server.ca.pem
smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
smtp_tls_note_starttls_offer = yes
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_sasl_tls_security_options = noanonymous

# General parameters
# ----------------------------------

myhostname = sigma.sebworks.com
mydestination =
# By default, mynetworks_style controls default behavior of mynetworks parameter. They're mutually exclusive. 
# If you have a separate web server that sends outgoing mail through this mailserver, you may want to add its IP address here
# mynetworks = 108.61.190.170/32, 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
# mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mynetworks_style = host

smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, warn_if_reject reject_non_fqdn_sender, reject_unknown_sender_domain
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
smtpd_data_restrictions = reject_unauth_pipelining

# Virtual mail
# ---------------------------------------

virtual_mailbox_base = /var/vmail
virtual_mailbox_maps = mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf, mysql:/etc/postfix/mysql_virtual_mailbox_domainaliases_maps.cf
virtual_uid_maps = static:150
virtual_gid_maps = static:8
virtual_alias_maps = mysql:/etc/postfix/mysql_virtual_alias_maps.cf, mysql:/etc/postfix/mysql_virtual_alias_domainaliases_maps.cf
virtual_mailbox_domains = mysql:/etc/postfix/mysql_virtual_domains_maps.cf

# Misc parameters
# ---------------------------------

# The first text sent to a connecting process.
smtpd_banner = $myhostname ESMTP $mail_name
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Tell postfix to hand off mail to the definition for dovecot in master.cf
virtual_transport = dovecot
dovecot_destination_recipient_limit = 1
# Use amavis for virus and spam scanning
content_filter = amavis:[127.0.0.1]:10024
# Getting rid of unwanted headers. See: https://posluns.com/guides/header-removal/
header_checks = regexp:/etc/postfix/header_checks
enable_original_recipient = no
mailbox_size_limit = 0
recipient_delimiter = +

delay_warning_time = 1h
unknown_local_recipient_reject_code = 450
maximal_queue_lifetime = 7d
minimal_backoff_time = 1000s
maximal_backoff_time = 8000s
smtp_helo_timeout = 60s
smtpd_soft_error_limit = 3
smtpd_hard_error_limit = 12
smtpd_delay_reject = yes
disable_vrfy_command = yes
smtpd_helo_required = yes

EOF

master.cf:

cp /etc/postfix/master.cf /etc/postfix/master.cf.original
cat << 'EOF' > /etc/postfix/master.cf

smtp      inet  n       -       -       -       -       smtpd
smtps     inet  n       -       -       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
pickup    unix  n       -       -       60      1       pickup
  -o content_filter=
  -o receive_override_options=no_header_body_checks
cleanup   unix  n       -       -       -       0       cleanup
qmgr      unix  n       -       n       300     1       qmgr
#qmgr     unix  n       -       n       300     1       oqmgr
tlsmgr    unix  -       -       -       1000?   1       tlsmgr
rewrite   unix  -       -       -       -       -       trivial-rewrite
bounce    unix  -       -       -       -       0       bounce
defer     unix  -       -       -       -       0       bounce
trace     unix  -       -       -       -       0       bounce
verify    unix  -       -       -       -       1       verify
flush     unix  n       -       -       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       -       -       -       smtp
relay     unix  -       -       -       -       -       smtp
#       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq     unix  n       -       -       -       -       showq
error     unix  -       -       -       -       -       error
retry     unix  -       -       -       -       -       error
discard   unix  -       -       -       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       -       -       -       lmtp
anvil     unix  -       -       -       -       1       anvil
scache    unix  -       -       -       -       1       scache
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/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/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix	-	n	n	-	2	pipe
  flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman   unix  -       n       n       -       -       pipe
  flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
  ${nexthop} ${user}
# The next two entries integrate with Amavis for anti-virus/spam checks.
amavis      unix    -       -       -       -       3       smtp
  -o smtp_data_done_timeout=1200
  -o smtp_send_xforward_command=yes
  -o disable_dns_lookups=yes
  -o max_use=20
127.0.0.1:10025 inet    n       -       -       -       -       smtpd
  -o content_filter=
  -o local_recipient_maps=
  -o relay_recipient_maps=
  -o smtpd_restriction_classes=
  -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 mynetworks=127.0.0.0/8
  -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
# Integration with Dovecot - hand mail over to it for local delivery, and
# run the process under the vmail user and mail group.
dovecot      unix   -        n      n       -       -   pipe
  flags=DRhu user=vmail:mail argv=/usr/lib/dovecot/dovecot-lda -d $(recipient)

EOF

Test server

Restart everything and test mail server functionality

service postfix restart
service spamassassin restart
service clamav-daemon restart
service amavis restart
service dovecot restart

Install roundcube

Accept configuring mysql database for roundcube.

apt-get install --assume-yes roundcube
apt-get install --assume-yes roundcube-plugins
apt-get install --assume-yes roundcube-plugins-extra

Make these changes to /etc/roundcube/main.inc.php (change in their proper lines)

	$rcmail_config['default_host'] = 'localhost';
	$rcmail_config['force_https'] = true;
	$rcmail_config['imap_cache'] = 'memcache';
	$rcmail_config['session_storage'] = 'memcache';
	$rcmail_config['memcache_hosts'] = array( 'localhost:11211' );

ln -s /var/lib/roundcube /var/www/html/mail

That’s about all for now!