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:
mydomain.com
. I’ll use subdomainsmtp.mydomain.com
as the host name of the mail server. - A static IP from my ISP: 11.22.33.44
DNS records:
mydomain.com. 600 IN A 11.22.33.44
smtp.mydomain.com. 3600 IN A 11.22.33.44
mydomain.com. 3599 IN MX 10 smtp.mydomain.com.
postfixadmin.mydomain.com. 1697 IN CNAME smtp.mydomain.com.
mailwatch.mydomain.com. 1697 IN CNAME smtp.mydomain.com.
roundcube.mydomain.com. 1697 IN CNAME smtp.mydomain.com.Reverse DNS PTR record: (Need ask my ISP to set this reverse DNS up for me)
$ dig -x 11.22.33.44 +short
smtp.mydomain.com.
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 |
Firewall
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:[sshd]
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.
NTP
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:timedatectl
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
LEMP
MariaDB
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:mysql_secure_installation
Install Nginx
Add Nginx Repo by create a configration file /etc/yum.repos.d/nginx.repo
with the following content:[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=0
enabled=1
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 0.0.0.0:80 0.0.0.0:* LISTEN 9901/nginx: master
Now open up port 80 and 443 on iptables. I can access http://smtp.mydomain.com 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
, setdate.timezone = America/Vancouver
cgi.fix_pathinfo = 0
Edit /etc/php-fpm.d/www.conf
, use socket instead of TCP port for better performance:;listen = 127.0.0.1:9000
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 |
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:mydomain.com, www.mydomain.com,smtp.mydomain.com, mailwatch.mydomain.com, postfixadmin.mydomain.com roundcube.mydomain.com
output:
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/mydomain.com/fullchain.pem. 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 le_renew.sh:#!/bin/bash
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 mydomain.com -d www.mydomain.com -d smtp.mydomain.com -d mailwatch.mydomain.com -d postfixadmin.mydomain.com -d smtp.mydomain.com -d roundcube.mydomain.com
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 postfixadmin.mydomain.com
by create a new configration file /etc/nginx/conf.d/postfixadmin.conf
with content:server {
listen 80;
server_name postfixadmin.mydomain.com;
return 301 https://$server_name$request_uri; # enforce https
}
server {
listen 443 ssl;
server_name postfixadmin.mydomain.com;
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/mydomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";
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 http://postfixadmin.mydomain.com/info.php, 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+“!
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:
- Part 1: LEMP
- Part 2: Postfix and Dovecot
- Part 3: MailScanner and MailWatch
- Part 4: SPF, DKIM and DMARC
- Part 5: Roundcube Webmail
- Part 6: Afterthoughts