Mail Server Postfix Dovecot & SpamAssassin on Ubuntu 20.04

How to Install a Mail Server on Debian/Ubuntu Using Postfix, Dovecot, and MariaDB (MySQL) Ubuntu 20.04

This article is a detailed manual on how to set up manually a secure email server using Postfix, Dovecot, and the open version of MySQL – MariaDB. The guide is for Debian/Ubuntu based distros, but it will include some of the Centos 7.x tools and commands as well.

How Mail Servers Work

Without getting into details, mail servers send and receive messages using three major components:

MTA- Mail Transfer Agent. This one works on a broader level. It gets the emails from your server and delivers it to the outside world. It also accepts the emails that come from the outside world and adds it to the MTA queue of your server.

MDA- Mail Delivery Agent. Gets the emails from the MTA queue and delivers it the individual mail boxes of the users.

IMAP, POP3- Manages the connections with the users as they are reading their email messages.

We will use Postfix as a MTA, and then will setup Dovecot as a mail delivery agent (MDA). Also, we need a running MariaDB or MySQL server to store our domains and email addresses.

MX Record DNS

The first important step is to set a DNS record for your mail server. This can be an A or CNAME record. Then we add MX (Mail Exchange) records for all the domain we are going to receive emails for. Set the hostname or @ for mydomain.com, then the hostname or IP address of the mail server. The priority is usually set to 10. You can have more than one server. Set the proper priority for each one. Example:

mail A xxx.xxx.xxx
@    MX  10  mail.mydomain.com.

Set mail.mydomain.com as an A record pointing to your server IP or CNAME to mydomain.com.

Another important step is to add mail.mydomain.com ip address to /etc/hosts/

127.0.0.1 localhost.localdomain localhost
xxx.xxx.xxx.xxx mail.mydomain.com mail

Don’t forget to change xxx.xxx.xxx.xxx with your ip and mydomain .com with your actual domain.

Install SSL Certificate

Before installing Dovecot, our MDA and Pop3/Imap server, we need to install a SSL certificate. The good thing is, we already have the tool Certbot installed for the FTPs and HTTPs servers and we are going to use it again to create a self- signed certificate. This SSL certificate  will authenticate the identity of the mail server to users and encrypt the transmitted messages between the user’s mail program and the mail server. While you can purchase a certificate from any other authority, I always prefer Certbot by Let’s Encrypt, because it is free and easy to use. If you don’t have the certbot package installed already, do so by typing the command:

sudo apt install certbot

Here you will find the Certbot Instruction, should you need more help. Once installed, run the certbot tool in a standalone mode. Please notice that if you have a web server listening on ports 80 and 443, you will need to stop it temporarily.

$ sudo certbot certonly --standalone -d mail.mydomain.com

Install Postfix Dovecot Packages

Let’s make sure everything is up-to-date with sudo apt-get update && sudo apt-get upgrade && sudo apt autoclean. Now we can proceed with installing all the packages we will need for the mail server. The whole setup includes postfix, postfix-mysql, dovecot-core, dovecot-imapd, dovecot-pop3d, dovecot-lmtpd, dovecot-mysql. Note that if you do not have mysql-server installed, you will need to do so at this step. Note that I am not including mysql-server in the line below as I have already mariadb-server package installed.

sudo apt-get install postfix postfix-mysql dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-mysql

During the installation you will be prompted to choose between different types of mail server configurations. Pick the Internet Server option.

Postfix Installation Guide

On the next screen, specify the default domain name this server will use for email addresses without specified domain.

Setup MySQL (MariaDB) Database for the Mail Server

Now we will create a designated MySQL user to manage a database with tables to hold our domains, users, email addresses, and aliases. You can find this setup on many websites; it is simple and doesn’t require a lot of MySQL knowledge.

1.Login into the MySQL server as a root user and -p (require password)

 sudo mysql -u root -p

2. Create a new database named mailserver

CREATE DATABASE mailserver;

3. Create a MySQL user mailuser and grant it permission to the new database. Don’t forget to change the mypassword with a secure version! Flush privileges to reload the MySQL database

CREATE USER 'mailuser'@'127.0.0.1' IDENTIFIED BY 'mypassword';
GRANT SELECT ON mailserver.* TO 'mailuser'@'127.0.0.1';
FLUSH PRIVILEGES;

4. Use the new mailserver database

USE mailserver;

5. Virtual Domains At this step we are creating a table containing the names of the domains recognized and allowed to send/receive emails through the server.

CREATE TABLE `virtual_domains` (
`id`  INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

6. Virtual Emails In the next table virtual_users we are creating the users. In this table we are adding the email addresses we are going to service and their passwords. Also, notice that every user (email address) is associated with a domain from the previous table. This connection is done with a foreign key.

CREATE TABLE `virtual_users` (
`id` INT NOT NULL AUTO_INCREMENT,
`domain_id` INT NOT NULL,
`password` VARCHAR(106) NOT NULL,
`email` VARCHAR(120) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

7. Virtual Aliases In this table we are going to add all the emails that will be forwarded to another email.

CREATE TABLE `virtual_aliases` (
`id` INT NOT NULL AUTO_INCREMENT,
`domain_id` INT NOT NULL,
`source` varchar(100) NOT NULL,
`destination` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

8. After creating the tables, let us enter the needed data inside:

Virtual Domains We will introduce all the domains we are going to receive mails for in the table. For the sake of the example I am only introducing two domains and one subdomain with its FQDN.

INSERT INTO `mailserver`.`virtual_domains`
(`id` ,`name`)
VALUES
('1', 'mydomain1.com'),
('2', 'host1.mydomain1.com'),
('3', 'mydomain2.com'),;

Virtual EMails At this step we are going to add the emails, passwords, and domains at the ‘virtual_users’ table.

INSERT INTO `mailserver`.`virtual_users`
(`id`, `domain_id`, `password` , `email`)
VALUES
('1', '1', ENCRYPT('mypassword1', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'email1@mydomain1.com'),
('2', '3', ENCRYPT('mypassword2', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'email2@mydomain2.com');

Virtual Aliases Now we are going to set aliases (source) which are going to forward to another email (destination).

INSERT INTO `servermail`.`virtual_aliases`
(`id`, `domain_id`, `source`, `destination`)
VALUES
('1', '1', 'myalias@example.com', 'email1@mydomain1.com');

Exit MariaDB (MySQL)

MariaDB > exit

Setup UFW to Allow Mail Server Traffic

Allow SMTP, Secure IMAP, Secure POP3 thru Uncomplicated Firewall UFW. Now, allowing ports 465 and 2525 is completely optional. I have read that port 465 is depreciated and is assigned by IANA for other services. 2525 is the SMTP relay port.

sudo ufw allow 25,465,587,993,995/tcp

How to Configure Postfix

Postfix has one main configuration file main.cf Here is: /etc/postfix/main.cf where all the fun begins. Make sure you make a copy of the original file by typing:

$ sudo cp /etc/postfix/main.cf /etc/postfix/main.cd.bk

After that type nano /etc/postfix/main.cf to edit the file. We are going to implement mail.mydomain.com as a host and Let’s Encrypt keys below. There is no need to change anything before the TLS section.

# See /usr/share/postfix/main.cf.dist for a commented, more complete version

# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# TLS parameters
smtpd_tls_cert_file=/etc/letsencrypt/live/mail.mydomain.com/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/mail.mydomain.com/privkey.pem
smtpd_use_tls=yes
smtpd_tls_auth_only = yes

smtpd_sasl_type = dovecot 
smtpd_sasl_path = private/auth 
smtpd_sasl_auth_enable = yes 
smtpd_recipient_restrictions =
permit_sasl_authenticated,
permit_mynetworks,
reject_unauth_destination


mydestination = localhost 

#make sure mail.mydomain.com matches your FQDN
myhostname = mail.mydomain.com

#instructs Postfix to do the local mail delivery to all virtual domains using Dovecot
virtual_transport = lmtp:unix:private/dovecot-lmtp

#last three lines tell Postfix we are using mysql to store info about the virtual domains, users and aliases.
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf, mysql:/etc/postfix/mysql-virtual-email2email.cf

#Lets set some restriction 
smtpd_relay_restrictions =
        permit_mynetworks,
        permit_sasl_authenticated,
        defer_unauth_destination
smtpd_recipient_restrictions = permit_mynetworks, 
        permit_sasl_authenticated
smtpd_client_restrictions = permit_mynetworks,
    permit_sasl_authenticated,
    reject_invalid_hostname,
    reject_unknown_client_hostname,
    smtpd_helo_restrictions = reject_unknown_helo_hostname

    # Don't accept mail from domains that don't exist.
smtpd_sender_restrictions = reject_unknown_sender_domain,
     permit_mynetworks,
     permit_sasl_authenticated,
     reject_non_fqdn_sender
 

Now let us create the three files that contain additional configuration settings. I will start with mysql-virtual-mailbox-domains.cf.

$ sudo nano /etc/postfix/mysql-virtual-mailbox-domains.cf
user = mailuser
password = thepassword
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_domains WHERE name='%s'

Don’t forget the change the username, password and database name with the ones you created with mysql. After that restart Postfix

$ sudo service postfix restart

At the next step we check if Postfix is set up correctly and can interact with the MySQL server and query the virtual_domains table. In my case the query will return 1.

postmap -q mydomain1.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
1

Next, let’s create mysql-virtual-mailbox-maps.cf file. It will contain:

sudo nano /etc/postfix/mysql-virtual-mailbox-maps.cf 
		
user = mailuser
password = mypassword
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_users WHERE email='%s'

Similarly, we can test if Posfix can query the virtual_users table. The response should be 1 again.

postmap -q email1@mydomain1.com mysql:/etc//etc/postfix/mysql-virtual-mailbox-maps.cf
1

At the end, we will create the /etc/postfix/mysql-virtual-alias-maps.cf

sudo nano /etc/postfix/mysql-virtual-alias-maps.cf

user = mailuser
password = mypassword
hosts = 127.0.0.1
dbname = mailserver
query = SELECT destination FROM virtual_aliases WHERE source='%s'

We meed to make sure Postfix can find the aliases. Enter the postmap command and it should return the mail that’s forwarded to the alias:

postmap -q alias@mydomain1.com mysql:/etc/postfix/mysql-virtual-alias-maps.cf

Modify /etc/postfix/master.cf

After that let us allow users send through Postfix using TLS encryption on port 587. We need to modify the /etc/postfix/master.cf file.

$ sudo nano /etc/postfix/master.cf

We need to do some work to cover the services Postfix is going to perform: sudo nano /etc/postfix/master.cf

smtp      inet  n       -       y       -       -       smtpd
#smtp      inet  n       -       y       -       1       postscreen
#smtpd     pass  -       -       y       -       -       smtpd
#dnsblog   unix  -       -       y       -       0       dnsblog
#tlsproxy  unix  -       -       y       -       0       tlsproxy
submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
   -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
#  -o smtpd_tls_auth_only=yes
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
#  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
smtps     inet  n       -       y       -       -       smtpd
 -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

......................

Restart Postfix: sudo systemctl restart postfix

Configure Dovecot

Dovecot is a POP3, IMAP server, i.e. clients connect to the mail server using it. Also it is a LDA (local delivery agent) server. As a LDA it gets the messages from Postfix and puts them into the individual mail boxes.

In this section of the Howto we will configure POP3, IMAP and LDA services. There are seven files in particular that contain basic server information, user authentication, etc. Enter the cp commands bellow, one at a time.

cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.orig
cp /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf.orig
cp /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf.orig
cp /etc/dovecot/dovecot-sql.conf.ext /etc/dovecot/dovecot-sql.conf.ext.orig
cp /etc/dovecot/conf.d/10-master.conf /etc/dovecot/conf.d/10-master.conf.orig
cp /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf.orig

Edit the file nano /etc/dovecot/dovecot.conf Make sure it includes the lines

!include conf.d/*.conf
!include_try /usr/share/dovecot/protocols.d/*.protocol
protocols = imap pop3 lmtp

postmaster_address = postmaster at mydomain.com

Now, let us get to the mail configuration file: /etc/dovecot/conf.d/10-mail.conf This file manages the way Dovecot interacts with the server file system to store and extract mail. Find the following commands and uncomment them.

mail_location = maildir:/var/mail/vhosts/%d/%n
-----
mail_privileged_group = mail
----

At the next step we create a storage folder to hold our messages. The main holder directory is at /var/mail/vhosts/ Every domain we receive emails for will be a subdirectory, e.g. /var/mail/vhosts/mydomain.com/

$ sudo mkdir -p /var/mail/vhosts/mydomain.com

Let’s create a group vmail with id 5000 and add a user vmail to that group. After that we will change the owner of the /var/mail/ folder to vmail user:group. Follow the commands one by one:

$ sudo groupadd -g 5000 vmail 
$ sudo useradd -g vmail -u 5000 vmail -d /var/mail
$ sudo chown -R vmail:vmail /var/mail

Next let us edit the authentication configuration file /etc/dovecot/conf.d/10-auth.conf Make sure you uncomment the line disable_plaintext_auth = yes and set the auth_mechanisms = plain login. Comment out #!include auth-system.conf.ext and uncomment !include auth-sql.conf.ext The file will look similar to this one:

#---
disable_plaintext_auth = yes
#---
auth_mechanisms = plain login
#--
#!include auth-system.conf.ext
#---
!include auth-sql.conf.ext
#---

Edit the file /etc/dovecot/conf.d/auth-sql.conf.ext to contain the following authentication and placement information:

passdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
  driver = static
  args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
}

The file above shows that we need to edit the file /etc/dovecot/dovecot-sql.conf.ext to contain the custom MySQL information.

$ sudo nano /etc/dovecot/dovecot-sql.conf.ext
driver = mysql
connect = host=127.0.0.1 dbname=mailserver user=mailuser password=mypassword
default_pass_scheme = SHA512-CRYPT
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';

Change the owner of the Dovecot directory to vmail user, then change the permissions recursively read- write and executable by the owner:

$ sudo chown -R vmail:dovecot /etc/dovecot
$ sudo chmod -R o-rwx /etc/dovecot 

We need to be careful while editing /etc/dovecot/conf.d/10-master.conf because different parameters will need to be changed. Beware of the opening and closing parentheses.

First group is for the POP3 and IMAP servers. We are only allowing POP3s and IMAPs

#...
service imap-login {
  inet_listener imap {
    #port = 143
  }
  inet_listener imaps {
    port = 993
    ssl = yes
  }
#  ...
}
#...
service pop3-login {
  inet_listener pop3 {
    #port = 110
  }
  inet_listener pop3s {
    port = 995
    ssl = yes
  }

Next thing to do is to setup the LMTP daemon to transfer maill from Postfix to Dovecot.

service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    #mode = 0666i
    mode = 0600
    user = postfix
    group = postfix
  }
...
}

Find the service auth directive and change it as follows

...
service auth {
...
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
 
  unix_listener auth-userdb {
    mode = 0600
    user = vmail
  }
...
  user = dovecot
}
...
...
service auth-worker {
...
  user = vmail
}

After saving all the changes to the 10-master.conf edit the file /etc/dovecot/conf.d/10-ssl.conf to require SSL and specify the path to your domain SSL key and certificate.

...
# SSL/TLS support: yes, no, required. <doc/wiki/SSL.txt>
ssl = required
...
ssl_cert = </etc/letsencrypt/live/mydomain.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mydomain.com/privkey.pem

Of course, don’t forget to change mydomain.com with your name. Restart the Dovecot

$ sudo systemctl restart dovecot

Testing the Emailserver with MailUtils

While this step is completely optional, we should make sure that our mail server is able to send mail to the outer world. We will need the Mailutils package. Type the following command in order to install it on the server.

 $ sudo apt-get install mailutils

Send an email to an address outside our mail server. This will show that the SMTP is functioning. This can be a gmail address.

 echo "Email text over here" | sudo mail -s "Email subject line" myemail@gmail.com -aFrom:myotheremail@mydomain.com

This will send an email to your gmail address. Please, be sure to check the Gmail Spam folder and mark it as not spam.

You can also test if the Mail server can receive emails and also if the there are servers listening on ports 993 and 995 using the telnet command:

$ telnet mydomain.com 993

The output should be similar to:

Trying xxx.xxx.xxx.xxx…
Connected to mydomain.com.
Escape character is ‘^]’.

Configure SpamAssassin

Last but not least let us install and configure both SpamAssassin and Postfix to work together. Install the SpamAssassin package:

$ sudo apt-get install spamassassin spamc

Create a dedicated user for spamassassin:

adduser spamd --disabled-login

Configure the default settings at

$sudo nano /etc/default/spamassassin

Inside the file we enable the spamd daemon and configure the options

#Enable the spamd daemon
ENABLED=1
#-----------

HOME_FOLDER="/var/log/spamassassin/"
OPTIONS="--create-prefs --max-children 5 --username spamd --helper-home-dir ${HOME_FOLDER} -s ${HOME_FOLDER}spamd.log" 
#Specify the pid file
PIDFILE="${HOME_FOLDER}spamd.pid"
#Setup SpamAssassin's rules to be updated automatically
CRON=1

Inside the open /etc/spamassassin/local.cf we will specify the antispam rules. SpamAssassin will score each email against these rules and every email that receives a score higher than 5 will be considered a spam message.

rewrite_header Subject ***** SPAM _SCORE_ *****
required_score          5.0
use_bayes               1
use_bayes_rules         1
bayes_auto_learn        1

Let’s get back to Postfix’s  /etc/postfix/master.cf file to let it know that each email will be checked against SpamAssassin’s rules. Find the following line smtp inet n – – – – smtpd and add the spamassassin filters

smtp      inet  n       -       -       -       -       smtpd
-o content_filter=spamassassin
#----
#Also we need to add the following lines
spamassassin unix -     n       n       -       -       pipe
    user=spamd argv=/usr/bin/spamc -f -e  
    /usr/sbin/sendmail -oi -f ${sender} ${recipient}

Don’t forget to save and restart both SpamAssassin and Postfix. Congratulation! Now let us do some research to harden up our mail server!

Recommended resources and websites

Netstat is our friend. Check if there are running services on ports 25, 143, 587, 993 and 995. This can be done with:

netstat -na | grep LISTEN | grep 587

Also, immediately after having the email server installed, we should verify the security of the setup. There are a lot of websites that offer their advisory services for free or low cost. Here are some of them:

  1. https://www.emailsecuritygrader.com/
  2. https://mxtoolbox.com/spf.aspx
  3. https://mxtoolbox.com/DMARC.aspx

The last two tools are very important, because they will give you an insight of the SPF records of the domain and how it will improve your domain mail deliverability.

Leave a Comment

Your email address will not be published. Required fields are marked *