Email Server With Postfix Dovecot and MailScanner (Part 1 - LEMP)

Email server is quite complicate. It is not only just for sending and receiving emails, but also need lots features and protections. Recently I built such an email server for my company. The whole process is worth to document.

I know there are some solutions exist for easy and quick setup a mail server, such as iRedMail. But I prefer to build the server step by step from scratch. This way I can understand how these all different components put together and how they work with each other. It’s kind of like driving an automobile, knowing a little bit better how the engine and transmission works will help me when there is a problem or need improvement down the road.

Features and Components

I begin with a list of some important features I needed for the mail system:

  • support virtual domain and mailboxes
  • support SMTP submission with STARTTLS
  • support IMAP/POP3 with SSL/TLS
  • have a secured web mail
  • have a management web interface for user accounts
  • have a management web interface for MailScanner

Here I list the major components I will build on the new email server:

  • LEMP server (CentOS 7 + Nginx + MariaDB + PHP)
  • Postfix
  • Dovecot (with Sieve filter)
  • MailScanner (which using ClamAV and Spamassassin)
  • MailWatch (Web UI for MailScanner)
  • RoundCube webmail
  • Postfix Admin (a web based interface used to manage mailboxes, virtual domains and aliases.)
  • Fail2ban and iptables (Firewall)
  • OpenSSL and Let’s Encripts SSL certificate
  • OpenDKIM and SPF

Domain name and DNS

Before I start to build the email server, I checked that I have these network related stuff ready:

  • Domain name: I’ll use subdomain as the host name of the mail server.
  • A static IP from my ISP:
  • DNS records:                  600     IN      A 3600 IN A 3599 IN MX 10 1697 IN CNAME 1697 IN CNAME 1697 IN CNAME
  • Reverse DNS PTR record: (Need ask my ISP to set this reverse DNS up for me)

    $ dig -x +short

CentOS installation

I installed CentOS 7.2 64bit which is my faverate OS for server platform. During the installation, I setup the static IP, time zone, and hard drive partitions.

I’ll save all the emails in /home/vmail later on, so make sure it has enough space for email storage.

Once I finished installation and reboot the server, I login in as root and start configration.

Disable SELinux

sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config

Install some packages

yum -y install net-tools nano wget man bind-utils git mailx telnet


Centos 7 come with Firewalld as the firewall but I like to use iptables for firewall, so let’s disable the default installed firewalld.

systemctl stop firewalld
systemctl mask firewalld
systemctl disable firewalld

Install iptables:

yum -y install iptables-services
systemctl enable iptables
systemctl start iptables

Now install fail2ban:

yum install -y epel-release
yum install -y fail2ban jwhois

Create fail2ban local configration file:

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edit this file to enable sshd jail. Set:

enabled = true

Now enable and start the fail2ban service:

systemctl start fail2ban.service
systemctl enable fail2ban.service

Now the ssh port 22 is protected by Fail2ban against brutal-force attack. In the future, anytime I need open up a new port for a service in iptables, I’ll also need to configure the corresponding fail2ban jail to have it protected.

There are lots scan activities on the well-known ssh port 22. So I changed the sshd to listen on a different port. Also configured iptables and fail2ban accordingly.


CentOS 7 use chronyd to keep the clock synchronized.
If timezone is not set, then:

timedatectl set-timezone America/Vancouver

Now install Chrony:

yum install -y chrony
systemctl enable chronyd
systemctl start chronyd

Use the following commands to check the status:

chronyc tracking
chronyc sources
chronyc sourcestats

Update system then reboot

Bring the system up to date:

yum -y update && reboot

After reboot, log in and check:

# sestatus
SELinux status: disabled



Install MariaDB server:

yum install -y mariadb-server
systemctl enable mariadb.service
systemctl start mariadb.service

Secure MariaDB. Run this command, set the root password, and answer Y to all other questions:


Install Nginx

Add Nginx Repo by create a configration file /etc/yum.repos.d/nginx.repo with the following content:

name=nginx repo

Install Nginx:

yum install nginx

Edit /etc/nginx/nginx.conf:

worker_processes 4;
gzip on;
server_tokens off;

Set worker_processes to the CPU core number.

Start nginx:

systemctl enable nginx.service
systemctl start nginx.service

Check to see Nginx is running on port 80:

# netstat -ntlp | grep :80
tcp 0 0* LISTEN 9901/nginx: master

Now open up port 80 and 443 on iptables. I can access for the nginx’s default page.

Install PHP5

PHP5 works in nginx through PHP-FPM. So install some php packages:

yum install php-fpm php-cli php-mysql php-gd php-mcrypt php-intl php-ldap php-odbc php-pdo php-pecl-memcache php-pear php-mbstring php-xml php-xmlrpc php-mbstring php-snmp php-soap php-imap

Edit /etc/php.ini , set

date.timezone = America/Vancouver
cgi.fix_pathinfo = 0

Edit /etc/php-fpm.d/www.conf, use socket instead of TCP port for better performance:

;listen =
listen = /var/run/php-fpm/php-fpm.sock
user = nginx
group = nginx

Next enable and start php-fpm:

systemctl enable php-fpm.service
systemctl start php-fpm.service

Let’s Encrypt SSL Certificate

I use the free LE SSL Cert for all the virtual hosts and SMTP/IMAP/POP3

First make sure all DNS record are pointed to the correct IP. This includes all A records for @, www, and smtp.

yum install git bc
git clone /opt/letsencrypt

Also make sure port 443 is open on iptables.

Obtain a certicate:

systemctl stop nginx
cd /opt/letsencrypt
./letsencrypt-auto certonly --standalone

Fill in my email address, then agree the agreement, then fill in all the domain names:,,,,



  • Congratulations! Your certificate and chain have been saved at
    /etc/letsencrypt/live/ Your cert will
    expire on 2016-05-29. To obtain a new version of the certificate in
    the future, simply run Let’s Encrypt again.

Let’s Encript SSL Cert is only valid for 90 days. To renew it, set a cron job (or wrote a script). The command to renew is:

/root/.local/share/letsencrypt/bin/letsencrypt renew --agree-tos

To test this command, add --dry-run flag at the end.

Here is my simple bash script


systemctl stop nginx
/root/.local/share/letsencrypt/bin/letsencrypt renew --agree-tos
systemctl start nginx


To obtain LE SSL certificates in one liner command:

/opt/letsencrypt/letsencrypt-auto certonly --standalone --agree-tos --email myemail@domain.tld -d -d -d -d -d -d -d

Configure Virtual Host

I will have few web sites running on this server using named based virtual host. They are RoundCube webmail, Postfix Admin and MailWatch.

I need to create a 2048 bit DH params file(default is 1024 bit):

openssl dhparam -out /etc/nginx/dhparams.pem 2048

Now let’s configure the first virtual host by create a new configration file /etc/nginx/conf.d/postfixadmin.conf with content:

server {

listen 80;
return 301 https://$server_name$request_uri; # enforce https


server {

listen 443 ssl;
root /var/www/html/postfixadmin;
index index.php;
charset utf-8;
access_log /var/log/nginx/pa-access.log;
error_log /var/log/nginx/pa-error.log;

## SSL settings
ssl_certificate /etc/letsencrypt/live/;
ssl_certificate_key /etc/letsencrypt/live/;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/dhparams.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_ecdh_curve secp521r1;

add_header Strict-Transport-Security max-age=31536000;

location / {
try_files $uri $uri/ index.php;

location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;


Let’s make a test page:

mkdir /var/www/html/postfixadmin
echo "<?php phpinfo(); ?>" > /var/www/html/postfixadmin/info.php

Restart Nginx and test by go to, it should jump to https:// automatcally and show the PHP info page.

A good test for SSL can be done at SSL LABS. I got the highest score “A+“!

Repeat the above steps to create other virtual host sites(mailwatch, roundcube). Later we will build these sites in their doc root directory.

Now I have a basic LEMP server with SSL and firewall protections. I’m now ready to build the mail server on this system.

Quick links: