mnnpr

Setting up your own Mail Server

Configuring and running your own email server is a major pain in the ass. So, I wanted to write a post about setting one up, mainly to document all the things I did while setting one up for myself. Hopefully this guide helps someone else as well.

Depending on your VPS provider and your OS, you might have to do some other things as well. I use an Ubuntu 18.04 LTS VPS provided by Hetzner.

List of Services

DNS Settings

For the entirety of this post, I am going to assume that your domain is yourdomain.com. Open up your DNS provider’s dashboard and create a new MX record as follows:

mail.yourdomain.com    MX      10      YOUREXTERNALIPADDRESS

Installing Packages

Next, We’ll install the packages I mentioned above by running:

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

While installation, you’ll be prompted to enter a password for the root MySQL user as well as set a configuration type for Postfix (Go with Internet Site).

Generating SSL Certificates

We’ll be generating SSL certificates using Certbot/LetsEncypt. To generate the ceritificates, run:

sudo certbot -d yourdomain.com -d mail.yourdomain.com

To automatically renew the certificates, you can set a cronjob for cerbot renew.

Configuring MariaDB

We have to create a database that will have tables for domains, email addresses/passwords and email aliases. We’ll also create a dedicated MySQL user for Postfix and Dovecot.

Creating the Database and Tables

To create the database, run the following command:

sudo mysqladmin -p create mailserver

Then login to the MySQL commandline using:

sudo mysql -p mailserver

Next, we’ll create a new MySQL user (mailuser in this case) by entering the following command:

GRANT SELECT ON mailserver.* TO `mailuser`@`127.0.0.1` IDENTIFIED BY `mailuserpass`;
FLUSH PRIVILEGES;

Enter the following command to create a table for the domains that will receive mail on your server:

CREATE TABLE `virtual_domains` (`id` int(11) NOT NULL auto_increment, `name` varchar(50) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Enter the following command to create a table for all of the email addresses and passwords:

CREATE TABLE `virtual_users` (`id` int(11) NOT NULL auto_increment, `domain_id` int(11) NOT NULL, `password` varchar(106) NOT NULL, `email` varchar(100) 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;

Enter the following command to create a table for your email aliases:

CREATE TABLE `virtual_aliases` (`id` int(11) NOT NULL auto_increment,`domain_id` int(11) 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;

With this, we’re done with creating the tables.

Adding Data

Now that we’ve created the database and tables, we have to add some data in the tables.

Add your domains to the virtual_domains table. You can add as many domains as you want in the VALUES section of the command below:

INSERT INTO mailserver.virtual_domains (id ,name) VALUES ('1', 'yourdomain.com'), ('2', 'mail.yourdomain.com');

Make a note of which id goes with which domain.

Next, Add email addresses to the virtual_users table:

INSERT INTO mailserver.virtual_users (id, domain_id, password , email) VALUES ('1', '1', ENCRYPT('USERPASSWORD', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'USEREMAIL@yourdomain.com');

To add email alias to the virtual_aliases table, run the following query:

INSERT INTO mailserver.virtual_aliases (id, domain_id, source, destination) VALUES ('1', '1', 'alias@yourdomain.com', 'USEREMAIL@yourdomain.com');

With this, we’re finished with the MySQL configuration.

Postfix Configuration

Postfix has two config files: /etc/postfix/main.cf & /etc/postfix/master.cf. I would recommend taking a backup of the original config files before proceeding so that you can undo any changes if things go wrong.

Start by editing the /etc/postfix/main.cf as shown below:

# TLS parameters
smtpd_tls_cert_file=/etc/letsencrypt/live/yourdomain.com/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/yourdomain.com/privkey.pem
smtpd_use_tls=yes
smtpd_tls_security_level = may
smtp_tls_security_level = may
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# Authentication
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes

# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = mail.yourdomain.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydomain = mail.yourdomain.com
myorigin = $mydomain
mydestination = $myhostname, localhost.localdomain, localhost
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all

# Handing off local delivery to Dovecot's LMTP, and telling it where to store mail
virtual_transport = lmtp:unix:private/dovecot-lmtp

# 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

Now we have to tell Postfix how to connect to MySQL.

For connecting virtual domains, create the file /etc/postfix/mysql-virtual-mailbox-domains.cf

user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_domains WHERE name='%s'

Similarly, for email address connection, create the file /etc/postfix/mysql-virtual-mailbox-maps.cf

user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_users WHERE email='%s'

Finally, for aliases connection, create the file /etc/postfix/mysql-virtual-alias-maps.cf

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

Restart postfix by typing,

sudo systemctl restart postfix

Now, to verify if the connections are working, try the following commands:

postmap -q yourdomain.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
postmap -q email@yourdomain.com mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
postmap -q alias@yourdomain.com mysql:/etc/postfix/mysql-virtual-alias-maps.cf

You should get an output for each of the commands. If nothing is returned, you have some error.

Now we’ll edit the second config file, /etc/postfix/master.cf. Make sure the following lines are uncommented:

smtp      inet  n       -       y       -       -       smtpd
submission inet n       -       y       -       -       smtpd
smtps     inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes

This enables SMTP over STARTTLS (Ports 25 & 587) as well as SMTP over SSL/TLS (Port 465). You should use SMTP over SSL/TLS (Port 465) for sending mails as it is more secure than STARTTLS.

Restart the postfix service and make sure it starts without any errors.

With this we are done with Postfix configuration (at least for now).

Dovecot Configuration

Like postfix, I would recommend taking backup of every config file you edit.

Firstly, open the main dovecot config file /etc/dovecot/dovecot.conf and check if the following lines are present and uncommented:

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

Now we’ll configure how dovecot stores the emails. Open /etc/dovecot/conf.d/10-mail.conf and set mail_location as per your preference. I have it set as follows:

mail_location = maildir:/data/mail/vhosts/%d/%n

%d is the domain and %n is the email id. Make sure the location you provide exists and has the correct permissions. So, in my case,

sudo mkdir /data/mail
sudo mkdir -p /data/mail/vhosts/yourdomain.com
sudo groupadd -g 5000 vmail
sudo useradd -g vmail -u 5000 vmail -d /data/mail
sudo chown -R vmail:vmail /data/mail

makes the directory and sets correct permissions for the same. The vmail user will be responsible for reading mail from the server.

Now let’s setup authentication in dovecot so that only authenticated users can read the emails. Start by opening /etc/dovecot/conf.d/10-auth.conf and make sure the following variables are setup as shown:

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

Now, let’s create /etc/dovecot/conf.d/auth-sql.conf.ext with the authentication information:

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

We’ll also have to update /etc/dovecot/dovecot-sql.conf.ext with our custom MySQL connection information:

driver = mysql
connect = host=127.0.0.1 dbname=mailserver user=mailuser password=mailuserpass
default_pass_scheme = SHA512-CRYPT
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';

Next, change the owner and group of the /etc/dovecot/ directory to vmail and dovecot by entering the following command:

chown -R vmail:dovecot /etc/dovecot

Also, change the permissions on the /etc/dovecot/ directory by entering the following command:

chmod -R o-rwx /etc/dovecot

Next, we’ll have to configure LMTP socket for local mail delivery and the auth socket for authentication in /etc/dovecot/conf.d/10-master.conf:

service imap-login {
  inet_listener imap {
    port = 0
  }
  inet_listener imaps {
    #port = 993
    #ssl = yes
  }
}

service pop3-login {
  inet_listener pop3 {
    port = 0
  }
  inet_listener pop3s {
    #port = 995
    #ssl = yes
  }
}

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

service auth {
  unix_listener auth-userdb {
    mode = 0666
    user = vmail
    group = vmail
  }

  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
  }

  # Auth process is run as this user.
  user = dovecot
}

service auth-worker {
  # Auth worker process is run as root by default, so that it can access
  # /etc/shadow. If this isn't necessary, the user should be changed to
  # $default_internal_user.
  user = vmail
}

To configure SSL, we’ll have to edit /etc/dovecot/conf.d/10-ssl.conf:

ssl = required
ssl_cert = </etc/letsencrypt/live/yourdomain.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/yourdomain.com/privkey.pem
ssl_client_ca_dir = /etc/ssl/certs
ssl_protocols = !SSLv2 !SSLv3

Finally, open the file /etc/dovecot/conf.d/15-lda.conf for one last setting:

postmaster_address = %d

Restart dovecot by entering the following command:

sudo service dovecot restart

That’s It. If email sending and receiving works, then go on to the next part of this guide. Else, check for logs using,

sudo tail -f /var/log/mail.log

There’s a catch though - we haven’t set up things like SPF, DKIM, DMARC and rDNS. Setting these up makes it possible for the recipient server to verify if the sender is verified or if someone is just spoofing the sender domain. DKIM in particular also lets mail servers detect if your mail has been tampered with in transit. Linode has a great article on this which I’ll link to at the bottom.

It is mandatory to set up SPF, DKM and DMARC if you don’t want your emails to go into receiver’s spam folder, so follow the linode article linked at the end.

General Tips


Sources:


comments and questions can be left here