# Setting up a mail server

24 April 2015

In this first tutorial, I’ll explain how I’ve configured my mail server using the following :

- A server running Linux Debian (jessie) ;
- Postfix ;
- Postfix-policyd-spf-python ;
- Dovecot ;
- Spamassassin ;
- OpenDKIM ;
- OpenDMARC ;
- Monit ;
- Rainloop.

I’m assuming you have some basic knowledge of Linux and DNS configuration.

You can host this server at home, but you might have issues with your ISP not allowing outbound traffic on TCP port 25, and your emails might be considered to be spam by other providers if your IP is dynamic and/or you can’t configure a reverse DNS record on it.

The cheapest VMs from DigitalOcean or Vultr are powerful enough to have this configuration running smoothly.

We’ll also need a SSL certificate for this configuration. You can create an auto-signed one or get a free valid one from StartSSL. For the purpose of this tutorial, I’ll consider you’ve chosen the latter.

You’ll also need a domain name. I’ve chosen Namecheap as a registrar. I won’t go into details on how to configure it, but you’ll need at the very least a A record on your server’s IP as well as a MX record pointing to it.

I use the captainark.net domain as an example throughout this tutorial. You’ll have to use your actual domain for your configuration to work !

Note: links in this section are sponsored.

## Initial configuration

### Installing the required packages

First thing first, we need to install the packages we’ll need for this configuration :

    apt update

    apt install mysql-server mysql-client postfix postfix-mysql \
    postfix-policyd-spf-python dovecot-core dovecot-imapd dovecot-lmtpd \
    dovecot-mysql dovecot-sieve dovecot-managesieved dovecot-antispam \
    opendkim opendkim-tools monit opendmarc spamassassin spamc

During its installation, Postfix will prompt you with configuration questions. Choose “Internet Site”, and when asked about your System mail name, provide it with your server’s FQDN (it should be the output of the `hostname -f` command on your server).

You’ll also have to set-up a password for the MySQL root user.

### Additional configuration

The PTR records on your server’s IPv4 and/or IPv6 should match your server’s FQDN (a `dig -x` on your server’s IP should match a `hostname -f` on your server).

You’ll have to open the following TCP ports on your server for this configuration to work : 25, 465, 587 and 993.

If you don’t want to have to remember the root user MySQL password, you can create a .my.cnf file in your current user home directory containing the following lines :

    [client]
    host     = localhost
    user     = root
    password = myverysecurepassword
    socket   = /var/run/mysqld/mysqld.sock

Once it has been created, change the permissions on the file to make sure no other user can read it :

    chmod 600 ~/.my.cnf

I also like to change the default MySQL shell to see what database I’m using at any given time. Since I use bash, I achieve this the following way :

    echo 'export MYSQL_PS1="[\u@\h] (\d)> "' > ~/.bash_aliases

You’ll have to logout from the current shell for the modification to be taken into account (if you’re using SSH, log out and back into your server).

You should now be able to log into MySQL without specifying a password, and it should look like this :

    :~$ mysql mysql
    [...]
    [root@localhost] (mysql)>

## Configuring the MySQL database

### Initial configuration

We now need to configure the MySQL database Postfix and Dovecot will be using. In this tutorial, we’ll be calling it “mail”, but you can name it whatever you want.

First, in a mysql shell, let’s create the MySQL database :

    CREATE DATABASE mail;

Now, we are going to create the user that Postfix and Dovecot will be using to access the database. We will only be granting this user select permission :

    GRANT SELECT ON mail.* TO 'mail'@'localhost' IDENTIFIED BY 'mailpassword';
    FLUSH PRIVILEGES;

We are now going to create the necessary tables for our needs. Let’s first use the mail database :

    USE mail;

The first table we are going to create will contain the domains we will be using with our mail server :

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

Then, we are going to create the table that will contain our users and their password :

    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;

Finally, the last table we are going to create will contain our mail aliases :

    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;

### Domains, users and aliases management

We are now going to add data to the tables we have created.

First, let’s add a domain to the virtual_domains table :

    INSERT INTO virtual_domains (`name`) VALUES ('captainark.net');

We can now create users associated with this domain in the virtual_users table :

    INSERT INTO virtual_users (`domain_id`, `password` , `email`) VALUES
    ('1', ENCRYPT('notanactualpassword', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))),
    'example@captainark.net');

This is not mandatory, but we can also create our first mail alias :

    INSERT INTO virtual_aliases (`domain_id`, `source`, `destination`) VALUES
    ('1', 'alias@captainark.net', 'example@captainark.net');

Now, all messages sent to alias@captainark.net will be forwarded to example@captainark.net.

Use the same syntax to create additional domains, users and aliases. If you have more than one domains configured, be sure to associate your users and aliases with the correct domain_id.

## Configuring Postfix

Next, we are going to configure Postfix.

### Configuration backup

First, let’s backup the original configuration files :

    cp /etc/postfix/main.cf /etc/postfix/main.cf.orig
    cp /etc/postfix/master.cf /etc/postfix/master.cf.orig

### User and group creation

We are now going to create a user and group called vmail that will be used by both Postfix and Dovecot :

    groupadd -g 5000 vmail
    useradd -g vmail -u 5000 vmail -d /var/mail -m -s /bin/false

### SSL certificates

Next, we are going to create the folder where we will store the SSL certificates :

    mkdir /etc/postfix/ssl
    chown root: /etc/postfix/ssl && chmod 600 /etc/postfix/ssl

Purists will probably want to store their certificates in /etc/ssl/private. If you choose to do so, you’ll have to adapt the path of those files for the remainder of this tutorial.

If you’ve decided to create a certificate with StartSSL, you’ll end up with two files, a .crt and a .key. I’ll name those files server.crt and server-with-passphrase.key. Put both these files in the folder we’ve just created.

Now, let’s remove the passphrase from the key :

    cd /etc/postfix/ssl
    openssl rsa -in server-with-passphrase.key -out server.key

You’ll be prompted for the passphrase you chose during the certificate generation.

Next, we have to download the appropriate intermediate certificate :

    wget -O /etc/postfix/ssl/sub.class1.server.ca.pem \
    http://www.startssl.com/certs/sub.class1.server.ca.pem

We now have to make sure that the permissions on those files are correct :

    chown root: /etc/postfix/ssl/* && chmod 600 /etc/postfix/ssl/*

The last thing we have to do here is to generate Diffie-Hellman keys for Perfect Forward Secrecy (PFS) :

    openssl gendh -out /etc/postfix/dh_512.pem -2 512
    openssl gendh -out /etc/postfix/dh_1024.pem -2 1024

### Postifx configuration

First, let’s edit the /etc/postfix/main.cf file. It should end up looking something like that :

    smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
    biff = no

    broken_sasl_auth_clients = yes
    config_directory = /etc/postfix
    disable_vrfy_command = yes
    smtpd_data_restrictions = reject_unauth_pipelining, permit
    smtpd_helo_required = yes

    queue_directory = /var/spool/postfix
    append_dot_mydomain = no
    readme_directory = no

    smtpd_use_tls=yes
    smtpd_tls_auth_only = yes
    smtpd_tls_cert_file=/etc/postfix/ssl/server.crt
    smtpd_tls_key_file=/etc/postfix/ssl/server.key
    smtpd_tls_CAfile=/etc/postfix/ssl/sub.class1.server.ca.pem
    smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
    smtpd_tls_protocols=!SSLv2,!SSLv3
    smtpd_tls_mandatory_ciphers=high
    smtpd_tls_dh1024_param_file = /etc/postfix/dh_1024.pem
    smtpd_tls_dh512_param_file = /etc/postfix/dh_512.pem
    smtpd_tls_eecdh_grade = strong
    smtpd_tls_loglevel = 1
    smtpd_tls_received_header = yes
    smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache

    tls_preempt_cipherlist = yes
    tls_random_source = dev:/dev/urandom

    smtpd_data_restrictions = reject_unauth_pipelining, permit
    smtpd_helo_required = yes

    smtp_tls_CAfile = $smtpd_tls_CAfile
    smtp_tls_mandatory_protocols=!SSLv2,!SSLv3
    smtp_tls_protocols=!SSLv2,!SSLv3
    smtp_tls_security_level = may
    smtp_tls_loglevel = 1
    smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

    smtpd_milters =
    non_smtpd_milters = $smtpd_milters
    milter_protocol = 2
    milter_default_action = accept

    smtpd_recipient_restrictions =
      reject_invalid_hostname,
      reject_non_fqdn_hostname,
      reject_non_fqdn_sender,
      reject_non_fqdn_recipient,
      reject_unknown_sender_domain,
      reject_unknown_recipient_domain,
      permit_mynetworks,
      permit_sasl_authenticated,
      reject_unauth_destination,
      permit

    smtpd_sasl_auth_enable = yes
    smtpd_sasl_local_domain = $myhostname
    smtpd_sasl_security_options = noanonymous
    smtpd_sasl_tls_security_options = $smtpd_sasl_security_options
    smtpd_tls_auth_only = yes
    smtpd_sasl_type = dovecot
    smtpd_sasl_path = private/auth

    myhostname = myserver.captainark.net ### CHANGE THIS
    alias_maps = hash:/etc/aliases
    alias_database = hash:/etc/aliases
    myorigin = /etc/mailname
    mydestination = localhost, myserver.captainark.net ### CHANGE THIS
    relayhost =
    mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
    mailbox_size_limit = 0
    recipient_delimiter = +
    default_transport = smtp
    relay_transport = smtp
    inet_interfaces = all
    inet_protocols = all

    virtual_transport = lmtp:unix:private/dovecot-lmtp
    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

The variable “myhostname” has to be defined to you server’s FQDN. The file /etc/mailname should contain your server’s FQDN as well.

Next, we need to edit the /etc/postfix/master.cf file. You need to uncomment the following lines :

    submission inet n       -       -       -       -       smtpd
      -o syslog_name=postfix/submission
      -o smtpd_tls_security_level=encrypt
      -o tls_preempt_cipherlist=yes
      -o smtpd_sasl_auth_enable=yes
      -o smtpd_client_restrictions=permit_sasl_authenticated,reject

    smtps     inet  n       -       -       -       -       smtpd
      -o syslog_name=postfix/smtps
      -o smtpd_tls_wrappermode=yes
      -o smtpd_sasl_auth_enable=yes
      -o smtpd_client_restrictions=permit_sasl_authenticated,reject

You also have to add the following lines at the end of the file :

    dovecot    unix  -       n       n       -       -       pipe
      flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${recipient}

### MySQL access for Postfix

We now need to allow Postfix to connect to the MySQL database we have created earlier. To that end, we must create three files.

/etc/postfix/mysql-virtual-mailbox-domains.cf should contain the following lines :

    user = mail
    password = mailpassword
    hosts = 127.0.0.1
    dbname = mail
    query = SELECT 1 FROM virtual_domains WHERE name='%s'

/etc/postfix/mysql-virtual-mailbox-maps.cf should contain the following lines :

    user = mail
    password = mailpassword
    hosts = 127.0.0.1
    dbname = mail
    query = SELECT 1 FROM virtual_users WHERE email='%s'

/etc/postfix/mysql-virtual-alias-maps.cf should contain the following lines :

    user = mail
    password = mailpassword
    hosts = 127.0.0.1
    dbname = mail
    query = SELECT destination FROM virtual_aliases WHERE source='%s'

Since these files contain a password, let’s make sure they are not world-readable :

    chown root: /etc/postfix/mysql* && chmod 600 /etc/postfix/mysql*

You can use the command postmap to confirm that everything is working properly :

    postmap -q captainark.net mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf

    postmap -q example@captainark.net mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf

    postmap -q alias@captainark.net mysql:/etc/postfix/mysql-virtual-alias-maps.cf

Let’s restart postfix for our modifications to be taken into account :

    systemctl restart postfix

That’s it for Postfix, for now ; Dovecot is next !

## Configuring Dovecot

### Dovecot global configuration

By default, on Debian, Dovecot uses multiple configuration files in /etc/dovecot/conf.d. I found it annoying to maintain, and I ended up only using the /etc/doveconf.conf file.

As always, let’s start by backing up the original configuration file :

    mv /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.orig

Next, we are going to create a new /etc/dovecot/dovecot.conf file. It should contain the following lines :

    !include_try /usr/share/dovecot/protocols.d/*.protocol
    protocols = imap lmtp sieve

    mail_location = maildir:/var/mail/%d/%n
    mail_privileged_group = vmail
    mail_plugin_dir = /usr/lib/dovecot/modules
    mail_plugins =

    disable_plaintext_auth = yes
    auth_mechanisms = plain login

    service director {
      unix_listener login/director {
      }
      fifo_listener login/proxy-notify {
      }
      unix_listener director-userdb {
      }
      inet_listener {
      }
    }

    namespace inbox {
      inbox = yes
      type = private
        mailbox Drafts {
          auto = subscribe
          special_use = \Drafts
        }
        mailbox Junk {
          auto = subscribe
          special_use = \Junk
        }
        mailbox Sent {
          auto = subscribe
          special_use = \Sent
        }
        mailbox Trash {
          auto = subscribe
          special_use = \Trash
        }
    }

    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 = 0
      }
    }

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

    service imap {
    }

    service pop3 {
    }

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

    service auth-worker {
      user = vmail
    }

    service dict {
      unix_listener dict {
      }
    }

    ssl = required
    ssl_cert = </etc/postfix/ssl/server.crt
    ssl_key = </etc/postfix/ssl/server.key
    ssl_ca = </etc/postfix/ssl/sub.class1.server.ca.pem
    ssl_protocols = !SSLv2 !SSLv3
    ssl_cipher_list = AES128+EECDH:AES128+EDH:!aNULL;
    protocol lda {
      mail_plugins = $mail_plugins sieve
    }

    protocol imap {
      mail_plugins = $mail_plugins
    }
    protocol lmtp {
      mail_plugins = $mail_plugins sieve
    }

    plugin {
      sieve = /var/mail/sieve/users/%u.sieve
      sieve_after = /var/mail/sieve/after
      sieve_before = /var/mail/sieve/before
      sieve_global_dir = /var/lib/dovecot/sieve/
      sieve_dir = ~/sieve
    }

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

Dovecot will use the same SSL certificate as Postfix.

Using this configuration, your virtual users’ emails will be stored in /var/mail/$domain/$user/ and will be owned by the vmail user.

For this to work, we have to create the domain folder :

    mkdir -p /var/mail/captainark.net
    chown vmail: /var/mail/captainark.net && chmod 770 /var/mail/captainark.net

Dovecot will create the virtual users’ folders automatically.

### Dovecot access to the MySQL database

We now need to allow Dovecot to connect to the mail database we have populated earlier. To do so, we are going to create a /etc/dovecot/sql.conf file with the following content :

    driver = mysql
    connect = host=localhost dbname=mail user=mail password=mailpassword
    default_pass_scheme = SHA512-CRYPT
    password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';

You’ll have to change the password to the one you have defined earlier. Since this file contains a password, let’s make sure it’s not world-readable :

    chown root: /etc/dovecot/sql.conf && chmod 600 /etc/dovecot/sql.conf

### Configuring Sieve

The last thing we need to configure here is sieve. The idea is to have all messages flagged as spam automatically moved to the mailbox Junk folder.

To do so, let’s first create the required folders :

    mkdir -p /var/mail/sieve/before
    mkdir /var/mail/sieve/after
    mkdir /var/mail/sieve/users
    chown -R vmail: /var/mail/sieve && chmod -R 770 /var/mail/sieve

If you want to have sieve rules for a specific user, simply create $user@$domain.sieve file in the users folder (example@captainark.net in my case).

All .sieve files in the before folder will be used for all your virtual users, before their individual configuration ; the .sieve files in the after folder will be used, well, you guessed it, after.

Let’s create a filter.sieve file in the /var/mail/sieve/before folder with the following content :

    require ["envelope", "fileinto", "imap4flags", "regex"];

    if not header :regex "message-id" ".*@.*\." {
          fileinto "Junk";
    }

    if header :contains "X-Spam-Level" "*****" {
          fileinto "Junk";
    }

Last thing we have to do is to change the permissions on the newly created file :

    chown vmail: /var/mail/sieve/before/filter.sieve && \
    chmod 660 /var/mail/sieve/before/filter.sieve

That’s all ; now, all email we receive that is flagged as spam by SpamAssassin will be moved to the Junk folder.

Let’s restart dovecot :

    systemctl restart dovecot

We now have a working mail server !

To connect to it and access your mailbox, configure your email client as follow :

- Username: example@captainark.net ;
- Password: the password you chose for your virtual user ;
- IMAP: your server’s FQDN, port 993 (SSL/TLS with normal password) ;
- SMTP: your server’s FQDN, port 465 (SSL/TLS with normal password).

## Configuring SpamAssassin

### The alternatives

Next thing we have to do is to configure the actual anti-spam. I tried a few, but I ended up sticking with SpamAssassin. Here’s why :

- DSPAM is no longer maintained and has been removed from Debian Jessie ;
- Rspamd is interesting, has been integrated in Debian Jessie, but is poorly documented at this time ;
- Bogofilter does not seem to have the greatest server integration.

### The actual configuration

SpamAssassin’s configuration is pretty straightforward. First, let’s edit the /etc/default/spamassassin file :

    ENABLED=1
    [...]
    CRON=1

Before the cron runs for the first time, we have to manually update SpamAssassin’s ruleset :

    sa-learn

Next, as usual, let’s back up the original configuration file :

    mv /etc/spamassassin/local.cf /etc/spamassassin/local.cf.orig

Let’s create a new /etc/spamassassin/local.cf file with the following content :

    rewrite_header Subject [SPAM]
    report_safe 0
    required_score 5.0
    use_bayes 1
    bayes_auto_learn 1

    whitelist_from *@captainark.net

Next, to have Postfix send incoming emails through SpamAssassin, we have to edit the /etc/postfix/master.cf file. At the very beginning, we have to add a line under the smtp definition :

    smtp      inet  n       -       -       -       -       smtpd
      -o content_filter=spamassassin

At the very end of the same file, we have to add the following lines :

    spamassassin unix -     n       n       -       -       pipe
      user=debian-spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}

Let’s restart SpamAssassin and Postfix :

    systemctl restart postfix
    systemctl restart spamassassin

That’s all for SpamAssassin ! To check if it is working, send yourself an email from another provider. You should see the following headers in it :

    X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on
    	myserver.captainark.net
    X-Spam-Level:

## Configuring SPF

### Allowing your server to send emails for your domain

SPF (Sender Policy Framework) is a mechanism that confirms that your server’s IP is allowed to send emails for your domain. Technically, it is a TXT DNS record which looks something like this :

    captainark.net IN TXT "v=spf1 mx ~all"

This DNS record lets other mail servers know that hosts that have a MX record for my domain are also allowed to send emails for it.

For more information on SPF syntax, you can consult the official documentation.

Without a properly configured SPF record, other mail servers might flag your emails as spam or outright drop them.

### Checking SPF record for inbound mail

Now that we have set up our own SPF record, let’s configure Postfix to check that other mail servers communicating with us have done the same.

First, let’s add the two following lines at the end of /etc/postfix-policyd-spf-python/policyd-spf.conf :

    Header_Type = AR
    Authserv_Id = "<server's FQDN>"

Then, let’s edit the /etc/postfix/master.cf file and add the following lines at the end :

    policy-spf  unix  -       n       n       -       -       spawn
      user=nobody argv=/usr/bin/policyd-spf

Let’s now edit the /etc/postfix/main.cf. In the “smtpd_recipient_restrictions” section, add the “check_policy_service” line as seen below :

    smtpd_recipient_restrictions =
    [...]
      reject_unauth_destination,
      check_policy_service unix:private/policy-spf,
      permit

We now have to restart postfix :

    systemctl restart postfix

Our server is now checking other mail server’s SPF records.

To make sure that it is working, send yourself an email from another provider. You should see the following header in it :

    Authentication-Results: myserver.captainark.net; spf=pass (sender SPF authorized)
    [...] receiver=example@captainark.net)

## Configuring OpenDKIM

DKIM (DomainKeys Identified Mail) is a mechanism that validates a domain name identity for an email through cryptographic authentication.

While not mandatory, setting up DKIM improves the odds of emails sent from your server not being flagged as spam by other providers.

With this configuration, OpenDKIM will also check the key for inbound emails.

### Software side

First, let’s backup the original configuration file and create a folder for the configuration files :

    mv /etc/opendkim.conf /etc/opendkim.conf.orig
    mkdir /etc/opendkim.d

We now have to create a /etc/opendkim.conf file with the following content :

    AutoRestart             Yes
    AutoRestartRate         10/1h
    UMask                   002
    Syslog                  yes
    SyslogSuccess           Yes
    LogWhy                  Yes

    OversignHeaders         From
    AlwaysAddARHeader       yes

    Canonicalization        relaxed/simple

    ExternalIgnoreList      refile:/etc/opendkim.d/TrustedHosts
    InternalHosts           refile:/etc/opendkim.d/dkim/TrustedHosts
    KeyTable                refile:/etc/opendkim.d/dkim/KeyTable
    SigningTable            refile:/etc/opendkim.d/dkim/SigningTable

    Mode                    sv
    PidFile                 /run/opendkim/opendkim.pid
    SignatureAlgorithm      rsa-sha256

    UserID                  opendkim:opendkim

    Socket                  local:/var/spool/postfix/opendkim/opendkim.sock

Let’s then create the necessary folders :

    mkdir -p /etc/opendkim.d/keys/captainark.net/

Now, we are going to create the /etc/opendkim.d/TrustedHosts file with the following content :

    localhost
    127.0.0.1
    ::1
    captainark.net

This file contains the hosts and domains for which OpenDKIM should sign emails.

Next, let’s create the /etc/opendkim.d/KeyTable :

    mail._domainkey.captainark.net captainark.net:mail:/etc/opendkim.d/keys/captainark.net/mail.private

This file tells OpenDKIM which key it should use for each selector.

Finally, let’s create the /etc/opendkim.d/SigningTable file :

    *@captainark.net mail._domainkey.captainark.net

This file tells OpenDKIM which selector it should use for each domain.

We now have to generate the private/public key pair for our domain :

    cd /etc/opendkim.d/keys/captainark.net/
    opendkim-genkey -s mail -d captainark.net

This creates two files ; mail.private contains our private key, mail.txt contains our public key.

Let’s change the permissions on those files :

    chown -R opendkim: /etc/opendkim.d/keys
    chmod -R 700 /etc/opendkim.d/keys
    chmod 600 /etc/opendkim.d/captainark.net/*

### Postfix integration

The last thing we have to do is to configure Postfix to communicate with OpenDKIM.

First, let’s create the necessary folders :

    mkdir /var/spool/postfix/opendkim
    chown opendkim: /var/spool/postfix/opendkim

We also have to add the postfix user to the opendkim group :

    useradd -G opendkim postfix

Now, let’s edit the /etc/postfix/master.cf file, like so :

    smtpd_milters = unix:/opendkim/opendkim.sock

We now have to restart OpenDKIM and Postfix :

    systemctl restart opendkim
    systemctl restart postfix

### DNS side

For DKIM to work, you have to configure a DNS TXT record in your zone. This record was automatically generated by OpenDKIM in the mail.txt file mentioned earlier :

    mail._domainkey IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkJq0CW3tl2XHZ1CN5XdbqRDU7KfXOJ70nlwI09bHmDU63/Yz3J5rl863S0t2ncVHfIudZANj0OaiJe5HRR7WCsjuNIhQFfPFGIWLNClpxqdQVQURI38sAGeyn7Ed/Cor1AiWABzFWzel0kvXILw8K/NTzxaAPeSa9ttwQEgSmowIDAQAB" ; ----- DKIM key mail for captainark.net

All you have to do is to copy and paste this record in your DNS zone file.

To make sure that OpenDKIM is working, you can send an empty email to check-auth@verifier.port25.com. You should receive a response with the following content :

    ==========================================================
    Summary of Results
    ==========================================================
    SPF check:          pass
    DomainKeys check:   neutral
    DKIM check:         pass
    Sender-ID check:    pass
    SpamAssassin check: ham

## Configuring OpenDMARC

DMARC (Domain-based Message Authentication, Reporting & Conformance) standardizes SPF and DKIM authentication mechanisms.

It lets the owner of a domain name indicate that his email is protected by SPF and/or DKIM and what other providers should do with emails that do not pass those checks.

### Software side

Once again, let’s backup the original configuration file :

    mv /etc/opendmarc.conf /etc/opendmarc.conf.orig

We now have to create a /etc/opendmarc.conf file with the following content :

    AutoRestart             Yes
    AutoRestartRate         10/1h
    UMask                   0002
    Syslog                  true

    AuthservID              "<your server's FQDN>"
    TrustedAuthservIDs      "<your server's FQDN>"
    IgnoreHosts             /etc/opendkim.d/TrustedHosts

    RejectFailures          false

    UserID                  opendmarc:opendmarc
    PidFile                 /run/opendmarc.pid
    Socket                  local:/var/spool/postfix/opendmarc/opendmarc.sock

### Postfix integration

The last thing we have to do is to configure Postfix to communicate with OpenDMARC.

First, let’s create the necessary folders :

    mkdir /var/spool/postfix/opendmarc
    chown opendmarc: /var/spool/postfix/opendmarc

We also have to add the postfix user to the opendmarc group :

    useradd -G opendmarc postfix

Now, let’s edit the /etc/postfix/master.cf file, like so :

    smtpd_milters = unix:/opendkim/opendkim.sock, unix:/opendmarc/opendmarc.sock

We now have to restart OpenDMARC and Postfix :

    systemctl restart opendmarc
    systemctl restart postfix

You should now see the following headers in your incoming emails :

    Authentication-Results: myserver.captainark.net; dmarc=pass header.from=gmail.com

### DNS side

DMARC, like SPF and DKIM, is based on DNS TXT records.

Here is how I configured it for the captainark.net domain :

    _dmarc IN TXT "v=DMARC1; p=none; rua=mailto:postmaster@captainark.net; ruf=mailto:postmaster@captainark.net"

This tells other providers to not reject or quarantine emails should a SPF or DKIM check fail, but to send a daily report of those checks to postmaster@captainark.net.

For more information on the DMARC syntax, here is an article from Google.

## Configuring Monit

Monit is a daemon that makes sure that other daemons are running. If they crash, it restarts them automatically. Is is not directly related to a mail server per say, but it’s pretty easy to set up.

First, as always, let’s backup the original configuration file :

    mv /etc/monit/monitrc /etc/monit/monitrc.orig

We now have to create a new /etc/monit/monitrc file with the following content :

    set daemon 30
    set logfile syslog facility log_daemon

    set httpd port 2812 and
    use address localhost
    allow localhost

    set mailserver localhost
    with timeout 30 seconds
    using hostname myserver.captainark.net

    set mail-format { from: monit@captainark.net }

    include /etc/monit/conf.d/*

Then, we are going to create a /etc/monit/conf.d/mail file with the following content :

    check process postfix
      with pidfile "/var/spool/postfix/pid/master.pid"
      start program = "/bin/systemctl start postfix"
      stop program = "/bin/systemctl stop postfix"
      alert monit@captainark.net
      group mail

    check process dovecot
      with pidfile "/run/dovecot/master.pid"
      start program = "/bin/systemctl start dovecot"
      stop program = "/bin/systemctl stop dovecot"
      alert monit@captainark.net
      group mail
      depends on postfix

    check process spamassassin
      with pidfile "/run/spamassassin.pid"
      start program = "/bin/systemctl start spamassassin"
      stop program = "/bin/systemctl stop spamassassin"
      alert monit@captainark.net
      group mail
      depends on postfix, dovecot

    check process opendkim
      with pidfile "/run/opendkim/opendkim.pid"
      start program = "/bin/systemctl start opendkim"
      stop program = "/bin/systemctl stop opendkim"
      alert monit@captainark.net
      group mail
      depends on postfix, dovecot

    check process opendmarc
      with pidfile "/run/opendmarc/opendmarc.pid"
      start program = "/bin/systemctl start opendmarc"
      stop program = "/bin/systemctl stop opendmarc"
      alert monit@captainark.net
      group mail
      depends on postfix, dovecot

Let’s make sure that permissions on the file are correct :

    chown root: /etc/monit/conf.d/mail && chmod 600 /etc/monit/conf.d/mail

Then, we have to reload the monit daemon :

    monit reload

Now, the `monit summary` command should have the following output :

    The Monit daemon 5.4 uptime: 3d 0h 41m

    Process 'postfix'                   Running
    Process 'dovecot'                   Running
    Process 'spamassassin'              Running
    Process 'opendkim'                  Running
    Process 'opendmarc'                 Running

## Configuring Rainloop

Rainloop is a web-based email client. I won’t go into details on how to configure it in this tutorial ; here’s a link to the official documentation.

You’ll need a web server with PHP 5.3+ to run Rainloop. You do not have to run Rainloop on the same host as your mail server. No database is required.

## Conclusion

We now have a mail server that should be running pretty smoothly. It could still be improved by setting up things such as greylisting or virus detection.

If you have found this tutorial useful, if you’ve found an error in it or if you have any question, please feel free to leave a comment below or to contact me on Twitter.

## References

Here are the tutorials I used to set up my own mail server :

- A complete tutorial on setting up a mail server
- Another complete tutorial
- A third tutorial from DigitalOcean
- A tutorial on setting up OpenDKIM
- A tutorial on setting up OpenDMARC (in french)

## Flexget init script