Finally, a definitive mail server guide

A successful telnet into a mail server

Hello! Long time no see. A lot has happened since I’ve been away. My physical server’s hard drive blew up, and I unfortunately lost a couple of my big projects. ISBN2Dewey’s dead, maybe for keeps – the rest are in source control, but don’t have databases anymore.

Needless to say, I now have nightly backups to Amazon S3. I’ll cover that script in a future blog post; right now, I want to talk about mail servers.

My mail servers: a history

I used to host my own mail. Of course, at the time I was on Windows Server 2012, so I was just using hMailServer. It fit my needs, was easy to configure, and pretty much worked right out of the box.

Of course, when I got the new server running, I went right to CentOS like a good Linux sysadmin. I’ll discuss the intricacies of the rest of the current setup later, but certainly my biggest challenge was building a mail server from scratch using Postfix and Dovecot – I hadn’t done anything like that before.

The good news is that there were a lot of tutorials, because this was something I hadn’t done before. The bad news…well, none of them were particularly great. So, after getting my mail server up and running and testing the hell out of it, I decided it was time to get a good tutorial up on the Internet somewhere, using what I’d learned from the extensive Postfix documentation (bookmark this, you won’t regret it), the Dovecot documentation (“Why Does It Not Work?” is a fantastic starting point), a couple incomplete tutorials, and a bit of trial and error.

In this post, we’re going to cover the complete setup of a functional mail server, from base CentOS install all the way up to virtual domains, users, and hosts. We’ll make sure an SSL gets installed so that your mail’s secure, and we’ll walk through testing the mail server afterwards. Due to the long config blocks, I opted not to create an Asciinema video for this one, but rest assured that I’ll do those when it’s appropriate in the future.

What we’ll do

Things that I will cover include:

  • Setting up a Postfix/Dovecot mail server with shared auth
  • Making sure your server only accepts secure connections
  • Virtual domains, users, and aliases
  • Testing your mail server using telnet

Things that I will not cover include:

  • Setting up your favorite web-based mail client. I believe that’s
    • outside of the realm of this tutorial
    • fairly self-explanatory anyway
  • Setting up a server for one domain and one user.
    • You can still run only one user/domain with virtual users and domains
    • It’s way easier to scale when you inevitably buy that second domain if everything’s already in place
    • Every tutorial I found that adds virtual users and domains after the fact is awful, just awful
  • Actually using Dovecot’s conf.d includes
    • The idea of using separate config files for separate config blocks in Dovecot is great, but the config is so darn short it’s a lot simpler to just stick things in one file. I’ll cover this in more depth later.
  • Advanced spam management
    • I will set up basic Spamhaus DNSBL checking, but anything further should be up to the individual user’s preference.

Things You Need

  • A domain – I use Hover for my domain services and recommend them without reservation. I’m using mydomain.com for the example configuration below.
  • A server – If this was 6 months ago I’d have told you to use DigitalOcean, but AWS now offers a similar-cost option at the low end – a t2.nano – and a whole slew of other services which I’ll be posting about taking advantage of in the future. I’d recommend spinning up a server there.
  • An IP – Make sure you’ve got whatever IP you’re assigning your webserver handy. We’ll need it later.

Getting Started

Go ahead and install CentOS on your server. The most recent installers give you a few more options than they used to. Notably, if you end up picking the wrong security profile you will probably have to install firewalld after first system boot. Just be mindful of that and make sure you’ve got everything set up appropriately. If you’re building from the minimal AWS AMI, everything should be configured fine initially and you won’t have to worry about it.

Required Packages

We’ll need a few things for everything to run smoothly. Run the following commands to make sure you have everything you need in place:

yum install epel-release -y
yum install postfix dovecot certbot -y

If, for whatever reason, you don’t like building minimal installs with CentOS, make sure you remove sendmail as well.

Laying the Groundwork

Before we get started with the meat of the configuration, we need to set up our virtual mail user. I used gid and uid 5000; you can use whatever you want, as long as it’s not in use by something else and as long as it’s over 1000.

groupadd vmail -g 5000
useradd vmail -r -g 5000 -u 5000 -d /var/vmail -m -c "Virtual mail user" -s /sbin/nologin

Next, we need to set up a cert. For this tutorial, we’re using the Certbot utility written to work with the free Let’s Encrypt service. If you have a web server running, shut it off for this next step – we don’t have a webroot, so certbot needs to set up a temporary web server to verify the domain. Run certbot certonly, follow the prompts, and generate your cert. I prefer to use mail.mydomain.com for the mail server cert. You’ll notice that the cert has been put in /etc/letsencrypt/live/mail.mydomain.com/fullchain.pem and the key is located in /etc/letsencrypt/live/mail.mydomain.com/privkey.pem.

Finally, make sure your firewall’s configured properly, opening ports using firewall-cmd --add-port XXX --zone=public --permanent. If on AWS, you can use a security group instead. Either way, make sure ports 993, 995, 25, 465, and 587 are open.

Postfix Setup

We’ll use Postfix as our SMTP server to actually send mail. It authenticates against Dovecot’s user database, which I’ll walk through setting up below.

main.cf

main.cf is the heart of the Postfix configuration. Go ahead and hop in – make a backup of /etc/postfix/main.cf, then open it with your favorite editor, remove the contents of the file, and add the following (commented to provide additional information) lines:

queue_directory = /var/spool/postfix #Mail queued for delivery
command_directory = /usr/sbin
daemon_directory = /usr/libexec/postfix
data_directory = /var/lib/postfix
mail_owner = postfix
unknown_local_recipient_reject_code = 550
alias_maps = hash:/etc/postfix/aliases
alias_database = $alias_maps
myhostname = mail.mydomain.com
mydomain = mydomain.com
inet_interfaces = all #Listens on all IPs - it doesn't have to do this, but I find it an awful lot easier to, especially since we're using virtual domains and can run everything on one mail server.
inet_protocols = ipv4 #If you want to run this on IPv6, you'll need to set up your networking stack to allow for it, and change this to "ipv4, ipv6". There are a lot of tricky things associated with Postfix on IPv6, check http://www.postfix.org/IPV6_README.html for details.
mydestination = $myhostname, localhost.$mydomain, localhost

debug_peer_level = 2
debugger_command =
 PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
 ddd $daemon_directory/$process_name $process_id & sleep 5

sendmail_path = /usr/sbin/sendmail.postfix
newaliases_path = /usr/bin/newaliases.postfix
mailq_path = /usr/bin/mailq.postfix
setgid_group = postdrop
html_directory = no
manpage_directory = /usr/share/man
sample_directory = /usr/share/doc/postfix-2.6.6/samples
readme_directory = /usr/share/doc/postfix-2.6.6/README_FILES

relay_domains = $mydestination

#These are the virtual aliases, domains, and mailboxes. I'll explain the formatting of the files below.
virtual_alias_maps=hash:/etc/postfix/vmail_aliases 
virtual_mailbox_domains=hash:/etc/postfix/vmail_domains
virtual_mailbox_maps=hash:/etc/postfix/vmail_mailbox

virtual_mailbox_base = /var/vmail #The home directory of the vmail user we set up above. This is where the mail gets stored.
virtual_minimum_uid = 5000 #Uid of vmail user
virtual_transport = virtual
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000

smtpd_sasl_auth_enable = yes #This is super important; we will only allow authenticated mail below. 
smtpd_sasl_type = dovecot
smtpd_sasl_path = /var/run/dovecot/auth-client
smtpd_sasl_security_options = noanonymous
smtpd_sasl_tls_security_options = $smtpd_sasl_security_options
smtpd_sasl_local_domain = $mydomain
broken_sasl_auth_clients = yes

smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, reject_rbl_client zen.spamhaus.org #Exclude authed and local clients from spam checks, check all against spamhaus.org
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination #Authed clients can specify any destination domain.

smtpd_use_tls = yes #Super important. We want to only allow secure connections.
smtpd_tls_key_file = /etc/letsencrypt/live/mail.mydomain.com/privkey.pem
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.mydomain.com/fullchain.pem

smtpd_tls_loglevel = 3
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom

master.cf

We need to take a quick detour into /etc/postfix/master.cf to make sure that Postfix is set up to use SASL auth with Dovecot. Open up the file, and uncomment lines starting with submission and smtps. Modify the -o lines as below, leaving you with a config block looking like this:

submission inet  n    -    n    -    -    smtpd
 -o smtpd_tls_security_level=encrypt
 -o smtpd_sasl_auth_enable=yes
 -o smtpd_client_restrictions=permit_sasl_authenticated,reject
 -o milter_macro_daemon_name=ORIGINATING
smtps    inet  n    -    n    -    -    smtpd
 -o smtpd_tls_wrappermode=yes
 -o smtpd_sasl_auth_enable=yes
 -o smtpd_client_restrictions=permit_sasl_authenticated,reject
 -o milter_macro_daemon_name=ORIGINATING

Creating virtual domains, users, and aliases

Describing these as “virtual” makes some sense in that Postfix abstracts them out of the Unix authentication layer, but it’s still a little weird – when you log in, your username will be “me@mydomain.com”, with a unique password. Just accept the terminology and move on. With that, let’s dive in.

Virtual domains are our way of managing all of the domains that Postfix will be willing to manage. It’s pretty easy to set up – create /etc/postfix/vmail_domains with the following text:

mydomain.com     OK
myotherdomain.org     OK
mythirddomain.biz     OK

…and so on and so forth, until you’ve added all the domains you want to handle on this server. The “OK” text is kind of a placeholder – you could use “1”, or honestly whatever you want. If you remove it, that domain will be disabled, though.

Virtual mailboxes are where the fun really begins. This is how we set up all of the different accounts that we’re actually going to host on the server. Again, it’s pretty straightforward, but there are a couple of “gotchas” which I’ll cover. Create /etc/postfix/vmail_mailbox with the following text:

me@mydomain.com     mydomain.com/me/
second-acct@mydomain.com     mydomain.com/second-acct/

Again, keep adding as many mailboxes as you’d like. The big gotcha here is the trailing slash. What you’re doing here is actually specifying the path (under /var/vmail, our mail root), where the mailbox is going to live. If you leave off the trailing slash, Postfix will think you want all the mail written to a file, traditional Linux mail spool style, and this will cause hilarious problems later on when you’re trying to send and receive mail and create file locks and stuff like that. We want this to be mailbox-style, where each message gets written into a folder, and the trailing slash lets us do that. Mailboxes must be on domains that the server will actually act as a mail server for; you can’t create a mailbox for, for example, my-gmail@gmail.com.

Finally, we have virtual aliases. These basically tell Postfix to, when it receives mail addressed to one of the aliases, send it to the destination email paired with the alias instead. Open up /etc/postfix/vmail_aliases and add as many aliases as you’d like, formatted as follows:

alias@mydomain.com     me@mydomain.com
alias2@mydomain.com    my-gmail@gmail.com

You can set up aliases to either existing mailboxes on the mail server or to external addresses – both work just fine.

Once you set up all of your domains, mailboxes, and aliases, use postmap to make them into a usable format for Postfix. Run the following:

postmap /etc/postfix/vmail_domains
postmap /etc/postfix/vmail_aliases
postmap /etc/postfix/vmail_mailbox

This is literally all you have to do to get the Postfix half of our mail server ready to go. Don’t start it yet, though – we haven’t set up Dovecot, so auth will be broken.

Dovecot Setup

Dovecot will be our client for POP3 and IMAP, as well as our auth server. The config is, as above, pretty simple.

dovecot.conf

/etc/dovecot/dovecot.conf is the heart of Dovecot – what makes it all tick. In the default install, there’s a bunch of fiddly little files in the conf.d directory which handle a lot of things modularly, but our Dovecot config fits on a single page, so we’re going to ignore all of them. Back up the default configuration file, open it up in your favorite editor, rip out all of the text, and replace it with the following code (which has been commented for clarity):

listen = *
ssl = required # Do not allow unencrypted auth as this could expose login details in transit.
ssl_cert = </etc/letsencrypt/live/mail.mydomain.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.mydomain.com/privkey.pem
protocols = imap pop3
disable_plaintext_auth = no #We're encrypted, so if a client wants to use AUTH PLAIN it's not going to end our world.
auth_mechanisms = plain login
mail_access_groups = vmail
default_login_user = vmail #Again, this is our vmail user created way back in step 1.
first_valid_uid = 5000
first_valid_gid = 5000
mail_location = maildir:/var/vmail/%d/%n #This tells Dovecot to expect a directory for mail, not a file. It creates a directory if one can't be found.
passdb { #We handle our users and passwords through a passwd file. I'll cover this below.
 driver = passwd-file
 args = scheme=SHA512-CRYPT /etc/dovecot/passwd #I used SHA512 here because I didn't want to assume everyone has access to bcrypt, but you can pick your favorite scheme here: http://wiki.dovecot.org/Authentication/PasswordSchemes
}
userdb {
 driver = static
 args = uid=5000 gid=5000 home=/var/vmail/%d/%n allow_all_users=yes
}
service auth {
 unix_listener auth-client {
   group = postfix
   mode = 0666
   user = postfix
 }
 user = root
}
service imap-login {
 inet_listener imaps {
   port = 993
 }
 process_min_avail = 1
 user = vmail
}

service pop3-login {
 inet_listener pop3s {
 port = 995
 }
 user = vmail
}

Creating users

Dovecot’s not just our POP3/IMAP server – it’s also our authentication source for the entire mail server. We’ll need to set up a passwd file – touch /etc/dovecot/passwd && chmod 600 /etc/dovecot/passwd – and then add some users.

To add users, you’ll need to generate a password; the easiest way to do this is to use the builtin doveadm. A handy shortcut to just get the hashed password is doveadm pw -s SHA512-CRYPT | cut -d '}' -f2; remember to replace SHA512-CRYPT with your preferred password scheme if you changed it in dovecot.conf above. Once you’ve got your password, you can add it to authentication using echo 'me@mydomain.com:PASSWORD' >> /etc/dovecot/passwd.

You don’t need to have a user set up in Postfix unless they need a mailbox; if you’re creating send-only addresses like noreply@mydomain.com, this is the only step you need to take. Otherwise, make sure the users here are set up in /etc/postfix/vmail_mailbox.

Testing Your Server

Now that you’ve got Dovecot configured to authenticate, we’re ready to fire up the entire mail server:

systemctl enable postfix
systemctl enable dovecot
systemctl start dovecot
systemctl start postfix

If you get errors on startup, check in /var/log/maillog and systemctl status <failing service> to see if you can pinpoint the exact cause of the error. Adding mail_debug = yesauth_debug_passwords=yes, and auth_debug = yes to your Dovecot config and checking maillog can provide a lot of insight – just make sure to remove them afterwards, especially auth_debug_passwords, and clear your log files after troubleshooting.

To test, fire up your favorite telnet client, and telnet into your server on port 25. You should be met with 250 mail.mydomain.com ESMTP Postfix. Type EHLO mail.mydomain.com, and you should get a whole bunch of 250 messages. Type AUTH LOGIN; the expected response is 334 VXNlcm5hbWU6. You’ll then need to input your base64 encoded username – remember, the username should be in the format “me@mydomain.com”. The response to this should be 334 UGFzc3dvcmQ6. Input your base64 encoded password, and you should be met with 235 2.7.0 Authentication successful. If you’ve gotten this far, your mail server works – you successfully negotiated a login with Postfix and it authenticated against Dovecot. The only remaining step is to start using it.

Putting It Live

Fire up your DNS manager and hop in. There are three records that we’re going to need to set to make sure that everything’s working as intended – an A record, an SPF record, and an MX record.

The obvious and easy one is setting up a “mail” A record to point at the IP of your mail server. The MX record is similarly straightforward – make sure you remove any other records, then add a record with a priority of 0 (0 is the highest possible priority, counterintuitively) and a value of “mail.mydomain.com”.

The SPF record is the toughie, because you don’t get any guidance from most DNS managers – and it’s not actually called an SPF record in said managers. You’ll need to create a new TXT record, and set the value to v=spf1 a mx -all. What this is basically saying is “allow mail to be sent from any host that’s listed in our A or MX records, and disallow everything else”. This prevents other people from impersonating your email address. Very useful, very important to have configured correctly.

If you’re managing multiple domains, you won’t need the A record on those domains – just set up the MX record to point to “mail.mydomain.com” and use the same SPF record as above.

Special note for those of you doing this on AWS: You need to apply to have your rate limit removed on this EC2 instance to use it as a mail server. Typically, AWS takes care of this within 5 minutes, so it won’t hold you up.

Once you set DNS up, your mail server will be able to send and receive mail for your domains. You can set up a webmail client on the server (Roundcube and SquirrelMail are both available through the EPEL repository, last I checked), connect through a local client like Thunderbird, or connect through your favorite managed mail provider (here are the instructions for Gmail, for example).

Please let me know in the comments below if you have any questions or are running into problems, or if you think I left something out. I tried to make this writeup as detailed as I possibly could without making it too long, and hopefully I succeeded. I’m interested to hear your thoughts and feedback.

Leave a Reply