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 |
Install iptables:
yum -y install iptables-services |
Now install fail2ban:
yum install -y epel-release |
Create fail2ban local configration file:
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local |
Edit this file to enable sshd jail. Set:
[sshd] |
Now enable and start the fail2ban service:
systemctl start 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 |
Use the following commands to check the status:
timedatectl |
Update system then reboot
Bring the system up to date:
yum -y update && reboot |
After reboot, log in and check:
# sestatus |
LEMP
MariaDB
Install MariaDB server:
yum install -y mariadb-server |
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] |
Install Nginx:
yum install nginx |
Edit /etc/nginx/nginx.conf
:
worker_processes 4; |
Set worker_processes to the CPU core number.
Start nginx:
systemctl enable nginx.service |
Check to see Nginx is running on port 80:
# netstat -ntlp | grep :80 |
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
, set
date.timezone = America/Vancouver |
Edit /etc/php-fpm.d/www.conf
, use socket instead of TCP port for better performance:
;listen = 127.0.0.1:9000 |
Next enable and start php-fpm:
systemctl enable 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 |
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 |
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 { |
Let’s make a test page:
mkdir /var/www/html/postfixadmin |
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