Deploy a Dedicated E-Mail Server with CentOS 7

This guide will help you configure an E-Mail server for your domain. The configuration choices made here are appropriate for a smaller user base.

This guide is a bit exhaustive. We’re going to cover purchasing a dedicated cloud server, DNS configuration, securing the server, configuration of the server, and spam protection. We optionally cover servicing mail for multiple domain names from one server.

  • Operating System: CentOS 7 .
  • Dedicated Server: I prefer Linode
  • SMTP Software: Exim
  • IMAP/POP3: Dovecot
  • Account Management: Local linux users

Please Note

  • example.com is the domain name referenced in the guide. Of course, where appropriate replace this with your actual domain name
  • Anywhere you see <Server IP Address> used in commands, please replace this with your server’s actual IP address before running the command. Any time you forget, imagine me taunting you.
  • Also, I was going to use Postfix, but CentOS hasn’t updated the Postfix package in years.

Server Prep: Provisioning a linode dedicated server

I prefer my E-Mail server to handle only E-Mail. Linode $5/mo Linode1024 server option is perfect for this.

Once you’ve purchased the server, visit your server dashboard. Click on Deploy an Image. Choose CentOS 7 as your linux distribution. Choose a strong root password . Press the Deploy button.

Return to your server dashboard, and press the Boot button to power on your server. Find your server’s IP address under the Remote Access tab of the dashboard.

Make a note of this IP address. Throughout this guide, you will need to replace the text <Server IP Address> with the ip address you noted here.


DNS Prep: Configure your DNS server

Other mail servers know where to send your mail by consulting your DNS zone. Most mail servers refuse to accept email sent by your users unless your DNS zone correctly identifies your mail server, and authorizes it to send mail on your domain’s behalf.

If your DNS records are not correct, you will not be able to properly send or receive mail from other servers on the internet.

It may take up to 24 hours for updates to your DNS to complete. But usually, 30-120 minutes should suffice.

DNS: Properly identify your new server

Other servers will mistake you for a dirty spammer unless your DNS records are in order. This means both forward and reverse lookups much match.

A Forward Lookup is when somebody asks “What is the IP Address for mail.example.com” and gets the answer 1.2.3.4.

A Reverse Lookup is when somebody asks “What is the domain name for 1.2.3.4” and gets the answer mail.example.com.

You can check these using the host command from your shell:

host mail.example.com might display:

mail.example.com has address 1.2.3.4
mail.example.com has IPv6 address 1234.5678.90ab.cdef.1234.5678

host 1.2.3.4 might display:

4.3.2.1.in-addr.arpa domain name pointer mail.example.com.

Add DNS zone records for Forward Lookup

This is done differently depending on which name registrar you use. Log into your registrar and find the screen for managing DNS records.

  • Name Cheap
  • Network Solutions
  • Go Daddy

  • Add an A record.
    • Type: A
    • Host: mail
    • Value:
  • Add an AAAA record
    • Type: AAAA
    • Host: mail
    • Value:
  • Add an SPF record
    • Type: TXT
    • Host: @
    • Value: v=spf1 a a:mail.example.com -all
  • Add an MX record
    • Type: MX
    • Host: @
    • Value: mail.example.com. 10

Set DNS Reverse Lookup

This is different with every server provider. Linode makes this easy. Visit the Remote Access tab of your server dashboard. Under the Public IPs section you will find a link for Reverse DNS. Here you can specify what name should be returned when somebody looks up the IP address of your server. Linode will not allow you to set this value until the last step has been properly configured. It may take as long as 24 hours after setting your forward lookup records before linode will allow you to set your reverse lookup records. If forward lookups aren’t correctly configured, you will not be able to complete this step.


Server Prep: Updates and Firewall

Once your server has booted up, connect as root via SSH. All the commands below assume you are logged in as root user.

SSH is under constant attack on the internet. I prefer not to worry about somebody guessing or stealing my password. I also prefer not to worry about unknown hacks against SSH. Therefore you are going to block SSH for everybody except from your own whitelisted IP address.

If you don’t know your public IP address, you can ask google what is my ip

Activate the firewall

# Activate firewall
systemctl start firewalld
systemctl enable firewalld

Whitelist my trusted IP

You can repeat this for as many IP addresses as you wish to trust. Replace 1.2.3.4 with your trusted address.

# Trust my ip 
firewall-cmd --zone=trusted --add-source=1.2.3.4
firewall-cmd --zone=trusted --add-source=1.2.3.4 --permanent

Block SSH trafic from the public

Your trusted IP address can still access SSH

# Disable ssh to the world (my trusted ip can still ssh)
firewall-cmd --zone=public --remove-service=ssh --permanent
firewall-cmd --zone=public --remove-service=ssh

Open mail ports in firewall

# Open the mail ports now
firewall-cmd --zone=public --add-service=smtp
firewall-cmd --zone=public --add-service=smtps
firewall-cmd --zone=public --add-service=imap
firewall-cmd --zone=public --add-service=imaps
firewall-cmd --zone=public --add-service=pop3
firewall-cmd --zone=public --add-service=pop3s

# Open the mail ports after reboots
firewall-cmd --zone=public --add-service=smtp --permanent
firewall-cmd --zone=public --add-service=smtps --permanent
firewall-cmd --zone=public --add-service=imap --permanent
firewall-cmd --zone=public --add-service=imaps --permanent
firewall-cmd --zone=public --add-service=pop3 --permanent
firewall-cmd --zone=public --add-service=pop3s --permanent


If your Public IP changes and you are locked out of SSH!

This is no problem. Log into your linode dashboard at linode.com. Under the Remote Access tab, you find an option called Launch Lish Console via Browser. This will give you an SSH shell to your server in your web browser. Log in as root from here., and run the above trust firewall commands to permit your new public ip through your firewall.


Server Prep: Hostname and Updates

Your server must know it’s own name. Replace the example domain and ip with your domain and ip.

# Set the hostname
hostnamectl set-hostname mail.example.com

# Set the domainname
domainname mail.example.com

# Update local hosts file
cat "1.2.3.4 mail.example.com" >> /etc/hosts

Update the server

# updates
yum -y install epel-release
yum -y update
yum clean all


Bonus Points: Track configuration changes with git

If you’re familiar with git, use it to track configuration changes. I email myself the results of git status on a daily basis.

yum -y install git
git config --global user.name "Mitch"
git config --global user.email "mitch@example.com"
cd /etc
git init
git add .
git commit -m "Initial /etc configuration"

What files have changed? git status

What changes were made in those files? git diff

Commit changes every time I update the server configuration

git add .
git commit -m "Update the server"

View of history of changes git log


Generate a free SSL Certificate

I like my E-Mail to travel the internet via SSL encrypted channels whenever possible. For this, you need a valid SSL certificate. The Let’s Encrypt Project was kind enough to make this very easy.

The certificate generator is going to verify your domain name before issuing a certificate. This isn’t going to work if your DNS isn’t properly configured.

# install letsencrypt
yum -y install certbot

# Allow letsencrypt through the firewall
systemctl stop firewalld

# Request an SSL certificate
certbot certonly \
        -d mail.example.com \  
        --standalone \
        -n \
        -m mitch@example.com \
        --agree-tos

# Restore the firewall
systemctl start firewalld

# The exim and dovecot users must be able to
# read the ssl certificates.  Loosen permissions.
chmod a+rx /etc/letsencrypt/live 
chmod a+rx /etc/letsencrypt/archive


Exim: Install and Configure

Install the SMTP server

yum -y install exim

/etc/exim/exim.conf

Make several changes to this large configuration file. For each change, locate the appropriate lines in the file and update accordingly.

Set the full mail server domain name. This needs to match the DNS. primary_hostname = mail.example.com

Specify which top level domains we receive mail for domainlist local_domains = @ : localhost : localhost.localdomain : example.com

Set the maximum size for E-Mail attachments The Default is 50M. I’ve doubled it here

# Add this line after the domainlist line near the top of the file
message_size_limit=100M

Specify the SSL certificate

tls_certificate = /etc/letsencrypt/live/mail.example.com/fullchain.pem
tls_privatekey  = /etc/letsencrypt/live/mail.example.com/privkey.pem

Uncomment this line, so we can use linux account passwords to authenticate mail users auth_advertise_hosts = ${if eq {$tls_cipher}{}{}{*}}

Also comment out this line, to enable the above line: # auth_advertise_hosts =

Ask the SMTP server to store e-mail messages for the mail users in their account folders, by using this local_delivery section

local_delivery:
  driver = appendfile
  file = $home/Maildir
  maildir_format 
  maildir_use_size_file
  delivery_date_add
  envelope_to_add
  return_path_add

Exim is going to let Dovecot do the work of authenticating users for login attempts. Find the authenticators section, and add these two sections

# Append to authenticators section

dovecot_login:
  driver = dovecot
  public_name = LOGIN
  server_socket = /var/run/dovecot/auth-client
  server_set_id = $auth1

dovecot_plain:
  driver = dovecot
  public_name = PLAIN
  server_socket = /var/run/dovecot/auth-client
  server_set_id = $auth1

Notify linux we’re using exim instead of sendmail

alternatives --set mta /usr/sbin/sendmail.exim

Test the exim config file

If a typo has invalidated the exim configuration file, this will help you find the problem exim -C /etc/exim/exim.conf -bV

Enable Exim as as service

Start Exim, enable starting Exim on boot

systemctl start exim
systemctl enable exim

Install and Configure Dovecot

yum -y install dovecot

/etc/dovecot/conf.d/10-ssl.conf

Configure SSL cert for dovecot

ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem

/etc/dovecot/conf.d/10-auth.conf

All connections are secured by SSL. Allow plaintext logins over ssl

disable_plaintext_auth=no
auth_mechanisms = plain login

/etc/dovecot/conf.d/10-mail.conf

Set user mailbox location.

mail_location = maildir:~/Maildir

/etc/dovecot/conf.d/10-master.conf

Allow exim to authenticate via dovecot. The unix_listener auth-client block must be added inside the service auth block

service auth {
# ...
# ...
    unix_listener auth-client {
        mode = 0660
        user = exim
    }
# ...
# ...
}

Enable Dovecot as a service

systemctl enable dovecot
systemctl start dovecot

Adding and managing users

Your E-Mail server will service a mailbox for every local user on the system. Therefore, to add e-mail accounts, just create a system user and set the system user password. To remove an e-mail account, delete the system user.

When creating the user, I prefer to set their group as exim, and their shell as /usr/sbin/nologin. This ensures shared file access with the mail server, and stops the user from actually logging into the server somehow.

Creating mail accounts

# for me@example.com
adduser -g exim -s /usr/sbin/nologin me
passwd me

# for mitch@mitchjacksontech.com
adduser -g exim -s /usr/sbin/nologin mitch
passwd mitch

Deleting mail accounts

userdel -r me
userdel -r mitch

System Aliases /etc/aliases

The aliases applied in /etc/aliases will also apply to your e-mail system. As the sysadmin, if I want to receive all the system mail generated for the root user, update that alias here.

It would be considered good form to keep up with the postmaster address. It’s going to be 99.9999999% spam. But somebody might email you about a delivery problem that is legitimate. You can alias that mailbox to your actual mail account here.

If you want mitch@example.com to receive mail for mcp@example.com, help@example.com and rickroll@example.com, add entries to /etc/aliases and restart the mail server.


Troubleshooting

Check the exim config file

exim -C /etc/exim/exim.conf -bV

DNS Problems

If DNS or SPF isn’t correct, there will be problems sending and/or receiving mail. You can use the host tool to verify your settings. Many websites exist to help you check your DNS records are correct. You may find mxtoolbox or network-tools helpful.

Testing mail routing

Ask exim where mail goes if somebody sends to an e-mail address.
exim4 -bt mitch@example.com

Check the logs

There’s a ton of useful fun in the log files. I like to watch them scroll bywhen the server is healthy. They are informative when it is not. Running the following set of commands will show you log entries as they happen in your terminal.

# attack of the logs
#

# General server logs
journalctl -f &
tail -f /var/log/secure &
tail -f /var/log/messages &

# mail logs
tail -f /var/log/maillog &
tail -f /var/log/exim/main.log &
tail -f /var/log/exim/panic.log &
tail -f /var/log/exim/reject.log &

selinux

selinux is probably interfering with your mail server, unless you know how to use it. If you want to understand selinux, find somebody who doesn’t hate it to explain. Otherwise, just disable it by editing /etc/selinux/config: SELINUX=disabled


Handling mail for multiple domain names

You can accomplish this goal with very little additional configuration. There are more complicated configuration paths for this involving databases and boatloads of extra work. Here we’re keeping it simple.

Every user of your mail server continues to be a local system user. If you direct exim to handle multiple top level domains using local unix accounts, you must specify which local user account is for which e-mail address.

You can support a large number of users and domain names using this method, but the usernames may get messy unless you have an organized approach from the start.

/etc/exim/aliases.virtual

Create this file, and populate it with a map of local users and e-mail addresses. This file can also be used to map multiple e-mail addresses to one user account. For example:

# /etc/exim/aliases.virtual
#
# Line Syntax:
# <E-Mail Address> : <local user>

# example.com
mitch@example.com : mitch
admin@example.com : mitch
paul@example.com  : paul
help@example.com  : paul

# testdomain.net
mitch@testdomain.net : mitch_tdn
andy@testdomain.net  : andy
paul@testdomain.net  : paul

Since we have two people named Mitch using our server, one username is mitch. The other gets the username mitch_tdn. Paul chose to only have one account, paul@example.net, and have the email alias paul@testdomain.net.

/etc/exim/exim.conf

Each domain we will receive mail for must be added to this line of the config file: domainlist local_domains = @ : localhost : localhost.localdomain : example.com : testdomain.net : bagofcrap.com

In the routers section, tell exim where to find the account map you made Place this section inside the routers section, directly before the localuser router

# consult the alias map
domains_virtual:
  driver = redirect
  data = ${lookup{$local_part@$domain}lsearch{/etc/exim/aliases.virtual}}

Exim has good intentions here, but we must intervene. Find the section below for authenticated user relay control, and modify it to match. Otherwise, when andy@testdomain.net sends an email, exim will irritatingly rewrite the sender address as andy@example.com before the message is delivered. /etc/exim/exim.conf

# Find the relay control for authenticated users
# and add 'sender_retain' where shown:
accept  authenticated = *
          control       = submission/sender_retain
          control       = dkim_disable_verify

Spam control

A large volume of spam can be discarded through simple sanity checks, and blacklist checks. I don’t cover heuristic spam detection such as spam assassin . As you grow to more users with more spam, you may need more desperate measures. These controls have worked very well for me.

All these changes will be made to the ACL lists inside the specified section exim.conf

acl_check_mail

  # If the sender name contains an ip address
  drop message        = Helo name contains a ip address (HELO was $sender_helo_name) and not is valid
       condition      = ${if match{$sender_helo_name}{\N((\d{1,3}[.-]\d{1,3}[.-]\d{1,3}[.-]\d{1,3})|([0-9a-f]{8})|([0-9A-F]$
       condition      = ${if match {${lookup dnsdb{>: defer_never,ptr=$sender_host_address}}}{$sender_helo_name}{no}{yes}}
       delay          = 45s


  # HELO is neither FQDN nor address literal
  drop
    # Required because "[IPv6:<address>]" will have no .s
    condition   = ${if match{$sender_helo_name}{\N^\[\N}{no}{yes}}
    condition   = ${if match{$sender_helo_name}{\N\.\N}{no}{yes}}
    message     = Access denied - Invalid HELO name (See RFC2821 4.1.1.1)

  # HELO is forging my hostname
  drop message   = "REJECTED - Bad HELO - Host impersonating [$sender_helo_name]"
       condition = ${if match{$sender_helo_name}{$primary_hostname}}

  # HELO is faked interface address
  drop condition = ${if eq{[$interface_address]}{$sender_helo_name}}
     message   = $interface_address is _my_ address


acl_check_rcpt

# check for sender blacklist
deny message = Access denied - $sender_host_address\
               listed by $dnslist_domain\n$dnslist_text
     dnslists = dnsbl.sorbs.net:sbl.spamhaus.org:bl.spamcop.net:cbl.abuseat.org

# Drop invalid bounces
drop  message = Legitimate bounces are never sent to more than one recipient.
      senders = : postmaster@*
      condition = ${if >{$recipients_count}{0}{true}{false}}


It’s an E-Mail Party

You should now have your own E-Mail server. drop me a line and let me know how it’s working out for you.

#linux

Written on March 29, 2017