Setting Up Your Own Mail Server (Postfix, Dovecot, Mailutils)

In this post I will tell you how to set up your own mail server using Postfix and DovecotPostfix functions as a Mail Delivery Agent (MDA), so any apps you have running on the server can send out emails, and Dovecot functions as a Mail Transfer Agent (MTA), which lets you hook up a Mail User Agent (MUA), such as Windows 10’s Mail app, or Thunderbird.  Because I’m moving away from PHP, I did not experiment with webmail.  There are no real popular Node.js webmail apps yet.

To set up your own mail server, you have to have a public facing server with a domain name, static IP, and Certificate Authority (CA) signed SSL certificate, there’s just no way around that.  Mail programs balk at connecting with self-signed certs, and services like Gmail won’t even accept mail from dynamic IP addresses on servers hosted on home networks.  You can still set one up on your home server to test a lot of things, and send mail between different users on the same server.

The real deal can be cheap, though.  DigitalOcean offers servers (which they call Droplets) as cheap as $5/month (mine is $20/month), and Let’s Encrypt can hook you up with a signed wildcard SSL-cert for free.  I will go over how to get your cert in a linked post, because it will be useful for all kinds of things.

Networking Setup

Application Setup

Troubleshooting

References

Networking Setup

Note, all my DNS examples are shown using DigitalOcean, but any hosting service should have a similar operation (e.g. GoDaddy.com).

Obtaining your signed wildcard SSL certificate

I go over that here.

If you are setting this up on a server hosted at home and just want a self-signed one to see if everything else works before you promote to production, I’ve got another post here that goes over that.

Creating the necessary DNS entries

I added this one early on because DNS records take a while to propagate through the world, so if you do this now, it will likely have taken effect everywhere by the time you’re done with this post.

If you have a domain name that points to your server, you’ll have A records already.  I have two, one for jogerfy.com and one for *.jogerfy.com.  This means that if a user goes to https://jogerfy.com/ or https://www.jogerfy.com/ they will get to the same server, and beyond that redirection is handled with Nginx.  If you do not have these already, you create them like so from your DNS management panel (obviously substituting your own server’s IP for the one in these screenshots, which was made up).

Note that for example.com, the HOSTNAME is @, and for *.example.com, the HOSTNAME is *.

Once your A records are there, you’ll need to add at least two more DNS records, an MX record, and a TXT record.  These tell other mail services like Gmail that you are a real mail service that can be reached by a reply,and not a spam factory running on a home network.

The MX record is entered as follows, again using an @ for the HOSTNAME.

The TXT record is a special one called a Sender Policy Framework (SPF) record, and it is technically optional, but Gmail will not accept mail unless you have one.  The exact wording may vary, but this one is a good generic setting.

There you go.  In a couple hours at most, Gmail will know you’re legit.  It will still route any mail sent to a user to their Spam folder at this point, until they email you back once, and from then onward messages sent to that user will go into their Inbox.  Most other services are not so cautious and will pass along mail straight to the Inbox after seeing the MX and TXT records.

Configuring the firewall on your server

I’m a big believer in security, so though I don’t understand a lot, I do know that we don’t want open ports that we’re not using.  If you have a an SSH server and a web server running, you most likely have open only ports 22, 80, and 443.  SSH uses port 22.  Web servers use ports 80 and 443 (HTTP defaults to port 80, and HTTPS defaults to port 443).  For mail, you’ll need to open ports 25, 143, 465, and 587.  Incoming mail is relayed on port 25, mail software like Windows 10’s Mail app uses ports 143 for incoming mail, and usually 587 for outgoing, and submission of mail by apps, such as a Node.js app you may have running, likes to send on port 465.  By setting up your own server, you can create as many accounts as you want and avoid sending through your personal Gmail account.  Here’s how you open those ports.

You should have iptables (and it’s IPv6 equivalent, ip6tables) installed, whether you’re using Ubuntu or CentOS.  You’ll need another tool that isn’t usually installed called iptables-persistent in order to save your settings.

sudo apt-get install iptables-persistent

A lot of people now are using an extra administration tool called Uncomplicated Firewall (UFW).  To see if you have it installed, do

which ufw

If you don’t see it, you can do

sudo apt-get ufw
sudo ufw enable

Then you can simply open the ports like so

sudo ufw allow 22
sudo ufw allow 143
sudo ufw allow 465
sudo ufw allow 587

I don’t use UFW. As I said above, I don’t understand security very well, and although UFW is supposed to be Uncomplicated Firewall, it seems to add a lot of entries to the iptables that I don’t understand and therefore won’t put my money on.  If you do all these UFW steps above and type

sudo iptables -S

you’ll see what I mean.  Therefore, I just use iptables manually. I type

sudo iptables -S

It should start with three -P entries and then have at least 3 -A entries, one of them being port 22.

-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT

These are Allow entries. The port 22 one allows you to connect via SSH. Make sure you don’t delete this one. To add more entries, you’d type:

sudo iptables -I INPUT 4 -p tcp --dport 25 -j ACCEPT
sudo iptables -I INPUT 5 -p tcp --dport 143 -j ACCEPT
sudo iptables -I INPUT 6 -p tcp --dport 465 -j ACCEPT
sudo iptables -I INPUT 7 -p tcp --dport 587 -j ACCEPT
sudo ip6tables -I INPUT 4 -p tcp --dport 25 -j ACCEPT
sudo ip6tables -I INPUT 5 -p tcp --dport 143 -j ACCEPT
sudo ip6tables -I INPUT 6 -p tcp --dport 465 -j ACCEPT
sudo ip6tables -I INPUT 7 -p tcp --dport 587 -j ACCEPT

The number after the INPUT argument tells where in the order of Allow entries it should be added. So if you have a lot of Allow entries, such as for port 80 and port 443 and maybe 1521 (Oracle) and 5432 (PostgreSQL), you may want to place them further down the list, and you’d raise that number appropriately. You can do

sudo iptables -S

To check your work. Once you are satisfied, save the settings with

sudo service netfilter-persistent save

If you don’t use this command, once you reboot, none of your changes are saved, which is actually handy if you screw something up. If you just added an entry twice or added it in the wrong order and wish to delete it, you can do

sudo iptables -D INPUT -p tcp -m tcp --dport 143 -j ACCEPT

And obviously substitute the port you want to delete for the 143. Don’t forget to run the same command with ip6tables if you want to delete it there as well.  Save your settings with netfilter-persistent save.

Application Setup

Mailutils

This is a set of tools for sending and reading mail from the command line. You can use it to test to see if your Postfix installation and mailboxes are set up correctly. Linux has a built-in mail command. So, why Mailutils? The built-in mail command only reads form /var/mail/$USER, and we would like to keep our mail in /home/$USER/Maildir so that it is more compatible with our endpoint app, like Window 10’s Mail app, or Thunderbird, which use a folder structure, rather than keeping all mail in a single file. Let’s configure this.

sudo apt-get install mailutils

Thence, create the file…

sudo nano /etc/mailutils.conf

And fill it with…

mailbox {
  # Create mailbox url using pattern.
  mailbox-pattern "maildir:///home/${user}/Maildir";
}

You will also need to change PAM so when you log in it will look in this new location.

sudo nano /etc/pam.d/sshd

Change

session    optional     pam_mail.so standard noenv # [1]

to:

session    optional     pam_mail.so dir=~/Maildir standard noenv # [1]

Postfix

This is the majority of the mail server, and it requires the most configuration. Start by installing it.

sudo apt-get install postfix

Whether or not it is already installed, you’ll need to run its configuration tool.

sudo dpkg-reconfigure postfix
General type of mail configuration: Internet Site
System mail name: example.com
Root and postmaster mail recipient: john.doe
Other Destinations to accept mail for (blank for none): $myhostname, $myhostname.$mydomain, $mydomain, localhost, localhost.localdomain
Force synchronous update on mail queue?: <No>
Local networks: 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
Use procmail for local delivery? <No>
Mailbox size limit (bytes): 0
Local address extension character: +
Internet protocols to use: all

john.doe is the administrator of this machine.  You should substitute your main account for this username.

There is a lot more configuration to come.  No worries, if you want to have a basic level of security, paste in the following, substituting your own domain for example.com.  If you haven’t set up a real wildcard CA-signed cert as I wrote about earlier, make sure your smptd_tls_cert_file and smtpd_tls_key_file point to the appropriate files.  You would also omit the smtpd_tls_CAfile line since self-signed SSL certificates do not have a Certificate Authority (CA).

sudo postconf -e 'mydomain = example.com'
sudo postconf -e 'home_mailbox = Maildir/'
sudo postconf -e 'smtpd_sasl_type = dovecot'
sudo postconf -e 'smtpd_sasl_path = private/auth'
sudo postconf -e 'smtpd_sasl_local_domain ='
sudo postconf -e 'smtpd_sasl_security_options = noanonymous'
sudo postconf -e 'broken_sasl_auth_clients = yes'
sudo postconf -e 'smtpd_sasl_auth_enable = yes'
sudo postconf -e 'smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination'
sudo postconf -e 'smtp_tls_security_level = may'
sudo postconf -e 'smtpd_tls_security_level = may'
sudo postconf -e 'smtpd_tls_protocols = !SSLv2, !SSLv3'
sudo postconf -e 'smtp_tls_note_starttls_offer = yes'
sudo postconf -e 'smtpd_tls_cert_file = /etc/letsencrypt/live/example.com/cert.pem'
sudo postconf -e 'smtpd_tls_key_file = /etc/letsencrypt/live/example.com/privkey.pem'
sudo postconf -e 'smtpd_tls_CAfile = /etc/letsencrypt/live/example.com/fullchain.pem'
sudo postconf -e 'smtpd_tls_loglevel = 1'
sudo postconf -e 'smtpd_tls_received_header = yes'

 

I won’t go over all of this, but the smtpd_tls_protocols says that we will only be accepting TLS connections, which are currently the most secure, home_mailbox sets us up to use the same Maildir directory that we configured for Mailutils earlier, which is more modern than keeping all users in a central /var/mail/$USER file, and smtpd_sasl_type sets us up to use Dovecot, which I’ll be going over in the next section.

sudo nano master.cf

Uncomment the following lines (delete the prefixing #)

submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes

And

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

This enables both the SMTPS and submission services, which are used to sent mail via an MUA app (mail app) and submit mail on behalf of other apps (e.g. Node.js apps).

Let’s add a few aliases to /etc/aliases because root cannot receive mail by default on modern systems

sudo nano /etc/aliases

Replace its content with the following

mailer-daemon: postmaster
postmaster: root
nobody: root
hostmaster: root
usenet: root
news: root
webmaster: root
www: root
ftp: root
abuse: root
root: john.doe

Replace john.doe with the user doing this configuration.

Restart Postfix to activate the changes with the following command

sudo systemctl restart postfix.service

You are ready to test it with Mailutils to see if mail gets delivered. Send some to yourself. If your username is john.doe, try

echo "mail body"| mail -r "john.doe@example.com" -s "test mail" john.doe
mail

You can also try sending mail to other users on the same machine, with the exception of root, who will not receive it, for security purposes.

If it arrived, you’ll see it listed in the mail command line utility. You can read it by typing its number and then enter, e.g. 1, and then delete it with d1 and then exit with q. You also should now have a directory called Maildir under /home/john.doe. We changed it from /var/mail/john.doe because Dovecot defaults to this newer convention.

Dovecot

The final step. Install it and a couple of necessary accessories with the following command

sudo apt-get install dovecot dovecot-imapd dovecot-pop3d

Now edit three configuration files for it

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

Find the section Postfix smtp-auth and uncomment and change it as follows

# Postfix smtp-auth
unix_listener /var/spool/postfix/private/auth {
  mode = 0660
  user = postfix
  group = postfix
}
sudo nano /etc/dovecot/conf.d/10-mail.conf

Change the mail_location to match that which we set up for Mailutils earlier

mail_location = mbox:~/mail:INBOX=/var/mail/%u

becomes

mail_location = maildir:~/Maildir:LAYOUT=fs
sudo nano /etc/dovecot/conf.d/10-auth.conf

Change the auth_mechanisms to add the login option (basic encryption)

auth_mechanisms = plain

becomes

auth_mechanisms = plain login

And finally update Dovecot’s certificates and the protocols it will support

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

Change the lines

ssl_cert = </etc/dovecot/private/dovecot.pem
ssl_key = </etc/dovecot/private/dovecot.key

to

ssl_cert = </etc/letsencrypt/live/jogerfy.com/fullchain.pem 
ssl_key = </etc/letsencrypt/live/jogerfy.com/privkey.pem

and the line

ssl_protocols = !SSLv3

to

ssl_protocols = !SSLv2, !SSLv3

Now restart Dovecot

sudo systemctl restart dovecot

Now you can hook up modern mail apps to it, such as Windows 10’s Mail. Try this.
Settings->Manage accounts->Add an account->Advanced setup->Internet email
Enter your settings like so

Email address: john.doe@example.com
User name: john.doe
Password: <john.doe's Linux account password>
Account name: john.doe@example.com
Send your messages using this name: John Doe
Incoming mail server: imap.example.com
Account type: IMAP4
Outgoing (SMTP) email server: smtp.example.com
☑ Outgoing server requires authentication
☑ Use the same user name and password for sending mail
☑ Require SSL for incoming email
☑ Require SSL for outgoing mail

Note that imap.example.com and smtp.example.com are the same server. In the earlier section on setting up DNS records I mentioned that we added an A record that pointed *.example.com to a single IP address. If you did not do this, please point it to the actual server name. If you are using a self-signed SSL certificate, this will still work, but you will have to click through some warnings in Mail, and you’ll have to have your mail servers referenced in your machine’s host file. If you’re using Linux, add them to /etc/hosts, and if you’re using Windows, add them to C:\Windows\SYSTEM32\drivers\etc\hosts
.

You’ll have to give yourself permission to edit the file under Windows first, then add an entry like this, substituting the machine’s IP on your local home network.

192.168.0.128  imap.example.lcl  smtp.example.lcl

If everything is set up correctly on a public facing server with DNS records, you should be able to email from the account to outside addresses, receive email. And even on a home server you should at least be able to email other users on the same server, and see the messages both from Mail and the command line mail utility, and see deleted messages disappearing in both. If not, read on to the next section.

Troubleshooting

I must have hit every branch on the tree on the way down to getting this working, and without the following tools, I never would have been able to figure out what I was doing wrong:

log files

This is the first and most important tool. It told me Gmail was rejecting my mails and how to fix that.

sudo tail /var/log/mail.err
sudo tail /var/log/mail.log

nmap

This can tell you whether your ports are open in the firewall, and if they’re available but “closed,” they likely are missing a backend service. I found out I didn’t have the dovecot-imapd and dovecot-pop3d packages installed this way because I had port 465 and 587 open in iptables but closed in nmap.

nmap localhost

sslscan

This can tell you what ciphers your server accepts on a specific port. SSLv3 ciphers on down are no longer considered secure enough, so you should only be seeing TLS ciphers.

sslscan localhost:465

dig

This can be run from a faraway server to see if MX or other types of records have propagated yet.

dig MX example.com +short

Should return the DNS entry to route mail to a particular server.

openssl

The most complex command here, openssl will tell you if something is running on a port at all, if it has auth_mechanisms of plain or login or anything else, and if a user can be authenticated on a particular port using a username and password. One example is

openssl s_client -crlf -connect localhost:465

Then type in

EHLO localhost

And you should see, among other things

250-AUTH PLAIN LOGIN
250-AUTH=PLAIN LOGIN

If you don’t, there is a problem upstream. Type QUIT to exit.


References

Networking Setup

Application Setup

Troubleshooting