October 6, 2019 · software linux SHS

Self-Hosting Series Part 2: Mail Server


I hope you are excited for Part 2 of the Self-Hosting Series, a guide on how to run a secure personal mail server! You can check out the rationale for this series in Part 1: Why I'm Ditching iCloud. For this post, we will be replacing iCloud Mail and iCloud Notes. Even though Apple touts itself as a privacy-focused company, email is one of the few iCloud services that isn't encrypted at rest by Apple. Here is a capture of the relevant section of the 'iCloud Security Overview':


However, encryption at rest doesn't make a tangible difference to users because the private keys are stored on site as well, allowing for decryption at any time. This pushed me to search for an alternative to services such as iCloud Mail and Gmail, which finally led to self-hosting as a solution. I have been running a personal mail server for two years now, and although it wasn't easy, it has definitely remained manageable.

Components of a Mail Server

A mail server relies on two protocols, Simple Mail Transfer Protocol (SMTP) and Internet Message Access Protocol (IMAP). SMTP is used to send and receive mail between different mail servers. IMAP is for clients to download messages, check drafts, and compose emails.

We will be installing SMTP and IMAP software, postfix and dovecot respectively. We will be running Postfix on port 25 and port 465. Port 25 is used for most SMTP mail transfer but is commonly blocked by many ISPs, so make sure to check if it is open on your network. Port 465 is the SMTPS port, a port that defaults to a TLS/SSL wrapper. RFC 8314 actually recommends using SMTPS by default. Lastly, Dovecot will be served over the IMAPS port, which is port 993.

Installation Guide

Before You Start

This guide assumes that you already have a VPS or another server set up securely with port 25 traffic available. If you need a guide on how to setup a VPS, I have posted one in the past. Furthermore, we assume that you are using Cloudflare as your DNS nameserver.

DNS Records

Open the Cloudflare DNS Settings, and define an A record for your mail server. For the rest of this article, we will assume this A record is for mail.ypark.ca. Now, if you want to receive mail at bobby@ypark.ca, then add an MX record for the root (@), pointing to mail.ypark.ca. If you want to receive mail at bobby@user.ypark.ca, then add an MX record for user.ypark.ca (i.e. user).


Reverse DNS

Reverse DNS records are essential for running a mail server, since it protects against spam. Open your VPS settings or contact your ISP, and add a reverse DNS hostname for your IPv4 address. From the above example, this would be mail.ypark.ca.


Set Hostname

The server needs to know its fully qualified domain name (FQDN) for easier installation of postfix later.

#sudo nano /etc/hostname


Setup Email Addresses

First, we need to make a user for our desired email address. If you want an email address of bobby@ypark.ca then make sure that a bobby user doesn't already exist on the server. If it does, delete it, and create a separate user for administration purposes.

The username you choose here will be the part of the email address that comes before the @ypark.ca suffix. The following command ensures that the user we are adding has reduced privileges.

sudo adduser --shell /usr/sbin/nologin bobby

The password that you decide here will be used for logging into your email account.

Setup Aliases

Now, we will set up aliases. Alias configuration allows emails sent to addresses such as root@ypark.ca and postmaster@ypark.ca to be redirected to another address like bobby@ypark.ca.

#sudo nano /etc/aliases

postmaster: root
root: bobby

Finally, update the aliases in memory.

sudo newaliases

Postfix and Dovecot Installation

Start by installing Postfix and dovecot.

sudo apt install postfix dovecot-imapd

During the Postfix Configuration screen, select "Internet Site", and type in the domain that you would like at the end of your email address (the MX record from above).


Store Mail in the Home Directory

We need to store incoming messages in the user's home directory. Run the following command for Postfix.

sudo postconf -e "home_mailbox = Maildir/"

Then, edit the Dovecot 10-mail.conf configuration file to the following:

#sudo nano /etc/dovecot/conf.d/10-mail.conf

mail_location = maildir:~/Maildir

Obtain Let's Encrypt SSL Certificate

We can install a Let's Encrypt certificate generator called acme.sh, and its dependency socat on the root account:

sudo su
wget -O -  https://get.acme.sh | sh
sudo apt install socat
sudo su

We exit and log back in to refresh the environment variables. Next, we obtain an SSL certificate by proving we own the mail.ypark.ca domain. This is done by running a web server on port 80 (make sure it's allowed in your firewall). Remember to substitute the domain in the command with that of your VPS/server, which could be different from your email address domain (i.e. not ypark.ca).

acme.sh --issue -d mail.ypark.ca --standalone --force

Now we can set up a scheduled job to copy the issued SSL certificate to our certificate/key directory, as well as reload Postfix and Dovecot. Once again, substitute all instances of mail.ypark.ca with the domain of your mail server.

acme.sh  --install-cert -d mail.ypark.ca --key-file /etc/ssl/private/mail.ypark.ca.key --fullchain-file /etc/ssl/certs/mail.ypark.ca.pem --reloadcmd "sudo postfix reload; sudo dovecot reload" --force

Enabling Dovecot SSL and Login

Here we will be enabling TLSv1.2, which only works if you have at least iOS 12 and macOS 10.15.

Modify the SSL requirements for Dovecot in 10-ssl.conf:

#sudo nano /etc/dovecot/conf.d/10-ssl.conf

ssl = required
ssl_cert = </etc/ssl/certs/mail.ypark.ca.pem
ssl_key = </etc/ssl/private/mail.ypark.ca.key
ssl_min_protocol = TLSv1.2

Next, prevent Dovecot logins from occurring over plain text:

#sudo nano 10-auth.conf 

disable_plaintext_auth = yes
auth_mechanisms = plain login

Next we allow IMAPS connections within the 10-master.conf file. IMAP doesn't use SSL, so we don't enable it:

#sudo nano /etc/dovecot/conf.d/10-master.conf

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

Lastly, in the same file, we enable Dovecot's login service, which Postfix will be using later:

#sudo nano /etc/dovecot/conf.d/10-master.conf

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

Enabling Postfix SSL and Login

Let's add SSL support to Postfix as well:

#sudo nano /etc/postfix/main.cf


Ensure that Postfix will only send outgoing SMTP mail messages if they are authorized, i.e. with a valid username/password:

#sudo nano /etc/postfix/main.cf

#### SASL ####
## specify SASL type ##
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_local_domain = $myhostname
smtpd_sasl_security_options = noanonymous

## enable SMTP auth ##
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination

Next, force Postfix to use SSL for logins:

#sudo nano /etc/postfix/main.cf 

#### SSL/TLS parameters ####
smtpd_tls_auth_only = yes
smtpd_tls_loglevel = 1

We want to disable login through port 25, as this is vulnerable to man-in-the-middle attacks. Open master.cf and uncomment the following option:

#sudo nano /etc/postfix/master.cf

smtp      inet  n       -       y       -       -       smtpd
  -o smtpd_sasl_auth_enable=no

Lastly, we will enable the SMTPS service, which enforces SSL by default. Simply scroll down and uncomment the smtps service, as well as the following options:

#sudo nano /etc/postfix/master.cf

smtps     inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=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=permit_sasl_authenticated,reject

Update Diffie-Hellman Parameters

For effective forward secrecy, you need a set of Diffie-Hellman Parameters that aren't being used elsewhere. We can generate new parameters with the following:

cd /etc/ssl/certs
sudo openssl dhparam -out dhparam.pem 2048

Next, update both Postfix and Dovecot to use these new parameters. For Postfix, edit the main.cf file:

#sudo nano /etc/postfix/main.cf

smtpd_tls_dh1024_param_file = /etc/ssl/certs/dhparams.pem

For Dovecot, edit the 10-ssl.conf file:

#sudo nano /etc/dovecot/conf.d/10-ssl.conf

ssl_dh = </etc/ssl/certs/dhparams.pem

Dovecot SSL Hardening

Since we have control over the devices being sued with Dovecot, the following is relatively safe. We will disable non-EC Diffie-Hellman by uncommenting the following line in the 10-ssl.conf file:

#sudo nano /etc/dovecot/conf.d/10-ssl.conf

# To disable non-EC DH, use:
ssl_cipher_list = ALL:!DH:!kRSA:!SRP:!kDHd:!DSS:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!RC4:!ADH:!LOW@STRENGTH

# or to disable SHA-1, ssl_cipher_list = ALL:!DH:!kRSA:!SRP:!kDHd:!DSS:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!RC4:!ADH:!SHA:!LOW@STRENGTH

Postfix SSL Hardening

The following configuration ensures that only high security ciphers are used to communicate between servers (if they both support SSL). Also, this enforces the usage of at least TLSv1.2. However, you can run into problems if the mail recipient doesn't support the newer standard. In this case, the connection will fail gracefully into an unencrypted (!) mode, but you shouldn't face any mail delivery problems. However, later on, once we start enforcing encryption, delivery problems may occur.

#sudo nano /etc/postfix/main.cf

## force email client high security ##
smtp_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtp_tls_ciphers = high
smtp_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtp_tls_mandatory_ciphers = high

smtpd_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_ciphers = high
smtpd_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_mandatory_ciphers = high

tls_preempt_cipherlist = yes

Break the Rules (more Postfix SSL hardening)

This configuration has the ability to break mail delivery, but it serves as protection against a man-in-the-middle attack. Most mail delivery over port 25 is vulnerable because it begins over a plain text connection before upgrading to SSL (STARTTLS). By enforcing encryption, any mail we receive will be encrypted. Furthermore, any mail server we connect to will be verified to have an SSL certificate that matches the corresponding MX record/domain. We can't use smtp_tls_security_level = secure, otherwise we can't send mail to servers that have a domain name different from that of the receiver's email address. This configuration is most similar to an always-on Message Transfer Aagent Strict Transport Security (MTA-STS) policy.

#sudo nano /etc/postfix/main.cf

#### SSL/TLS parameters ####
smtpd_tls_security_level = encrypt
smtp_tls_security_level = verify
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt

smtp_tls_exclude_ciphers = aNULL, eNULL, PSK, SHA, SHA256, SHA384, DSS, AESCCM
smtpd_tls_exclude_ciphers = aNULL, eNULL, PSK, SHA, SHA256, SHA384, DSS, AESCCM

smtp_tls_mandatory_exclude_ciphers = aNULL, eNULL, PSK, SHA, SHA256, SHA384, DSS, AESCCM
smtpd_tls_mandatory_exclude_ciphers = aNULL, eNULL, PSK, SHA, SHA256, SHA384, DSS, AESCCM

Restart Postfix and Dovecot

sudo service postfix stop
sudo service postfix start
sudo service dovecot stop
sudo service dovecot start

Protect Against DNS MITM

While the section above helped prevent downgrade attacks from MITM of the SMTP protocol, it does not help prevent MITM of DNS entries, i.e. the MX records. In simpler terms, an attacker could MITM a DNS request and replace it with a false MX record that points to their own server, which could then present a valid SSL certificate for an arbitrary domain. If you wanted to send mail to my email address, you would have no idea that the it was being sent to a fake mail server. This could potentially be remedied by a long list of domains and their SSL capabilities, as described by "Roll Your Own Network".

However, my current solution is to use DNS over HTTPS, which prevents network level modification of DNS requests. There are many guides online to setting up your own DNS over HTTPS DNS resolver, including one by Cloudflare.

Lastly, there exists a new DNS record that tells other mail servers to enforce encryption and validation before connecting to your mail server called MTA-STS, as mentioned above. An MTA-STS record enforces an encrypted session of TLSv1.2 or better and validates the domain name for your SSL certificate. However, once these records become cached, they will resist DNS MITM, thus enforcing secure encryption.

Configure with Fail2ban

We can install Fail2ban to protect our SSH, Postfix, and Dovecot services from repeated brute force attacks. The install command is:

sudo apt install fail2ban

This is the configuration for protecting Postfix (SASL) and Dovecot Login Attempts (SSH is enabled by default). Change bantime to how long you want the ban to last.

#sudo nano /etc/fail2ban/jail.local

bantime  = 60m
findtime  = 10m
maxretry = 5

enabled = true
filter = postfix[mode=auth]
port = smtp,465,submission
logpath = /var/log/mail.log
maxretry = 3

enabled = true
filter = dovecot
logpath = /var/log/mail.log
maxretry = 5

Configure with UFW

If you are already running Uncomplicated Firewall (UFW), then you don't need to worry about allowing all the port numbers. Just run the following commands to allow connections to the mail server.

sudo ufw allow smtp
sudo ufw allow imaps
sudo ufw allow smtps

Adding SPF and DMARC DNS Records

SPF and DMARC records help reduce the chances that other email providers filter out your domain as spam. If you go back to Cloudflare's DNS management page, you can add both of these records.

For SPF, you can use this SPF generator to help create your record. My SPF record was the following:

Record: TXT
Name: ypark.ca
Content: ypark.ca v=spf1 mx -all

This was my DMARC record:

Record: TXT
Name: _dmarc
Content: v=DMARC1; p=reject; rua=mailto:postmaster@naut.ca; ruf=mailto:postmaster@naut.ca; aspf=s

Adding DKIM Header and Records

DKIM also helps reduce the chances that your emails are marked as spam. I used the following guide:

DKIM with Postfix

Enable Spam Filtering

Although this is a bit out of scope for the article, you will definitely want to stop spam from reaching your newly formed mail server! You can use the following guide to help Postfix filter spam, using an SPF policy agent. This tool verifies that the SPF records of the incoming mail servers are valid.

Install DKIM, SPF and Postfix

After enabling that, a few more spam emails still slipped into my inbox, so I changed my Postfix recipient restrictions one more time to look like this:

#sudo nano /etc/postfix/main.cf

smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policyd-spf, reject_rbl_client zen.spamhaus.org

This configuration is using a spam domain blacklist. Find out how to enable one here.

Email Client Configuration

For your mail client, configure both SMTP and IMAP to use the address of the mail server. Use the same username and password that was created earlier. Enable Forced TLS encryption if available, and avoid STARTTLS.

Server: mail.ypark.ca
SMTP(S) Port: 465
IMAP(S) Port: 993
Username: bobby
Password: ********

Testing Mail Delivery

Mail Tester | mail-tester.com is an extremely useful tool in diagnosing mail delivery problems, and the "spamminess" of your mail server.


Dovecot Bugfix

If some of your mail clients are failing to connect to IMAP after a few minutes, then this StackExchange article may be of help. It explains how to increase Dovecot's internal user+IP limits, which fixed my problem.