Title: Setting up a mail server Date: 2015-04-24 Category: Tutorial ## Introduction 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](https://www.digitalocean.com/?refcode=1cd69e4c3389) or [Vultr](http://www.vultr.com/?ref=6804947) 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](http://www.startssl.com/). 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](http://www.namecheap.com/?aff=85990) 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 : ```bash 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 : ```bash [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 : ```bash 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 : ```bash 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 : ```bash :~$ 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 : ```sql 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 : ```sql 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 : ```sql USE mail; ``` The first table we are going to create will contain the domains we will be using with our mail server : ```sql 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 : ```sql 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 : ```sql 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 : ```sql INSERT INTO virtual_domains (`name`) VALUES ('captainark.net'); ``` We can now create users associated with this domain in the virtual_users table : ```sql 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 : ```sql 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](http://www.postfix.org/). ### Configuration backup First, let's backup the original configuration files : ```bash 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 : ```bash 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 : ```bash 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 : ```bash 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 : ```bash 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 : ```bash 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) : ```bash 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 : ```bash chown root: /etc/postfix/mysql* && chmod 600 /etc/postfix/mysql* ``` You can use the command postmap to confirm that everything is working properly : ```bash 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 : ```bash systemctl restart postfix ``` That's it for Postfix, for now ; Dovecot is next ! ## Configuring Dovecot ### Dovecot global configuration By default, on Debian, [Dovecot](http://www.dovecot.org/) 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 : ```bash 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 = " TrustedAuthservIDs "" 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 : ```bash mkdir /var/spool/postfix/opendmarc chown opendmarc: /var/spool/postfix/opendmarc ``` We also have to add the postfix user to the opendmarc group : ```bash 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 : ```bash 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](https://support.google.com/a/answer/2466563?hl=en). ## Configuring Monit [Monit](http://mmonit.com/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 : ```bash 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 : ```bash chown root: /etc/monit/conf.d/mail && chmod 600 /etc/monit/conf.d/mail ``` Then, we have to reload the monit daemon : ```bash 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](http://www.rainloop.net/) 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](http://www.rainloop.net/docs/installation/). 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](https://twitter.com/captainark). ## References Here are the tutorials I used to set up my own mail server : - [A complete tutorial on setting up a mail server](http://sealedabstract.com/code/nsa-proof-your-e-mail-in-2-hours/) - [Another complete tutorial](https://docs.raccoon.io/mail-server-setup-with-postfix-dovecot/) - [A third tutorial from DigitalOcean](https://www.digitalocean.com/community/tutorials/how-to-configure-a-mail-server-using-postfix-dovecot-mysql-and-spamassasin) - [A tutorial on setting up OpenDKIM](https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-dkim-with-postfix-on-debian-wheezy) - [A tutorial on setting up OpenDMARC](https://guillaume.vaillant.me/?p=481) (in french)