It's time for part 3 of getting your e-mail server up and running! We have six main tasks with this segment, each of which has its own chunk of subtasks. We'll go through them in roughly this order:
- Install OpenDKIM so that we can use DomainKeys Identified Mail to help recipient domains validate that the e-mail we send actually comes from us
- Install SpamAssassin for spam filtering
- Install ClamAV for virus scanning (though this is somewhat optional, as we'll discuss when we get there)
- Configure SpamAssassin and ClamAV and talk a bit about spam filtering
- Configure our external DNS and talk a bit about how to handle internal DNS as well (as part of this, we'll set up DKIM and SPF records and also talk about reverse-lookups)
- Set up some server-side mail filters with Sieve to toss spam and do some other neat tricks
After completing part 2, our e-mail server has an almost fully configured Postfix and Dovecot stack. However, there is additional functionality that we need to bolt on to Postfix in order to make it behave like a big-boy SMTP server. We need it to be able to filter out spam and viruses, and we need to be able to tag our outgoing e-mails with a cryptographic hash to help prove that they're legit e-mails that we're actually sending.
To add this functionality to Postfix, we're going to be installing a number of milters—mail filter applications. You might have noticed a commented-out line in Postfix's
main.cf config file in part 2 that mentioned milters. Once we have them all installed, we'll uncomment that line to get them working.
The first thing we need to get operational is DKIM: DomainKeys
Identified Mail. But before we do that, a quick note on virtual users:
several readers have written in asking if the folder structure required
for the virtual users we created back in part 2 needs to be manually
created. The answer, fortunately, is no. The first time a virtual user
receives mail, Dovecot will automatically create everything the virtual
user needs under
Now, let's get to it!
OpenDKIM is one of the methods we'll use to help legitimize our mail server. When you send an e-mail with OpenDKIM, your mail server first generates a hash of the message's contents, then encrypts that hash with a private key and stores it in the e-mail's headers. On receipt of the e-mail, the recipient's mail server checks your domain's public DNS records for a specially constructed TXT record containing your private key's public counterpart; the recipient server then uses that public key to decrypt the encrypted message hash. Finally, the recipient server hashes the message and compares the value to the decrypted hash your server attached. If the two hashes are the same, then the recipient can have some degree of assurance that the message was indeed sent by an authorized sender from your domain.
We'll be using the OpenDKIM package to provide DKIM functionality to Postfix. It's a quick install with
(I'm going to stop mentioning that most of this needs to be done with root privilege; either preface your commands with
sudo or open a root shell via the method of your choice.)
The OpenDKIM package creates the accounts and Upstart jobs we need, but we're also going to create a directory to hold our OpenDKIM config files and private keys (hat tip to Drew Crawford and his excellent, in-depth tutorial for this bit). First, create the directory and set its ownership to the OpenDKIM user and group:
Next, we'll chdir into the directory and create our DKIM private/public keypair there with one of the OpenDKIM utilities:
The command line parameters we're using with
opendkim-genkey restrict the key we're generating so that it can only be used to sign mail, specify SHA256 as our hashing algorithm, and tell
opendkim-genkey the hostname to use and the output file names to generate.
After you run this command, you'll have a pair of files in your directory: the private key in
mail.private, and the public key in
Note also that the public key file is actually structured as a DNS TXT
record—this is super handy since we need to set just such a TXT record
on our public DNS server. Keep this file around, and we'll come back to
The next step is to rename the private key file from
mail.private to just
First, that bit of renaming:
Now create a file named
/etc/opendkim/KeyTable and make its contents look like the following:
This table will be used to associate
with the specific private key for that domain. You can also do this
directly in the OpenDKIM configuration file, but using a table gives you
the flexibility to manage multiple e-mail domains. If that's a future
goal, you can just add the additional domains and keys below the first.
/etc/opendkim/SigningTable and set its contents thusly:
This table will tell OpenDKIM which signature to attach to which
messages—in this case, sign all e-mails from all addresses ending in
@yourdomain.com with the signature for
mail.yourdomain.com. Again, the use of a table here lets you easily add additional domains if needed.
The last file to create is
/etc/opendkim/TrustedHosts, which just needs to contain the standard link local IP address:
This table will serve a couple of different purposes: it will be used to tell OpenDKIM the list of servers for which it should sign mail rather than verify mail and also which servers it will allow to send signed mail without providing credentials. Once again, using a table here rather than hard-coding values directly into OpenDKIM's config file gives you room to add additional domains if needed.
That's it for our tables and key files. However, before we leave this directory and move on to OpenDKIM's main configuration file, we need to set their owners and groups so that the OpenDKIM user can access them (since they're probably owned by root or by your account right now):
The last OpenDKIM step is to modify the configuration file and plug
in our tables, as well as set a few other options to make OpenDKIM do
what we want. That file is at
/etc/opendkim.conf, and it
should already have a few uncommented parameters in it. Leave the file's
existing contents alone and append the following at the bottom:
From the top down, we're setting
relaxed to deal with the ways that e-mail headers occasionally get
changed in transit. The next two entries tell OpenDKIM to use our
file (containing just the link local IP address) in the fashion
described above—saying mail coming from addresses listed in that file
should actually be signed and so on.
KeyTable points to our KeyTable file, and
SigningTable points to the SigningTable file.
tells OpenDKIM to be more verbose when explaining its signing and
verification decisions. Next, we specify a pidfile for the OpenDKIM
daemon and then a socket within Postfix's chroot jail in order to
receive connections from Postfix. Finally, we tell OpenDKIM to log
successful signatures and verifications, set our temp directory, and set
the UID/GID under which OpenDKIM will run.
Now, about that socket: there isn't a
directory yet, so we're going to create it and then set it to be owned
by the OpenDKIM user and root group to match how the rest of the
directories under /var/spool/postfix are permissioned:
Restart the OpenDKIM daemon to make our config changes live:
We face one final issue with OpenDKIM. Currently, the unix socket at
won't allow public writes—only its owner and group can write to it. But
we need Postfix to be able to write to the socket in order to
communicate with OpenDKIM, and we don't have a lot of crazy granularity
with Unix permission bits. The quickest way to overcome this problem is
to add the Postfix user to the OpenDKIM group, like this:
What are the potential security implications of this? An attacker compromising the Postfix account wouldn't have access to the OpenDKIM private key (since the permissions on that file only allow read and write by its owner, the OpenDKIM user), but anything that bridges account privilege separation barriers is a less-than-desirable thing. Still, this is a small workaround. One other way to handle this situation would be to abandon unix sockets and have OpenDKIM listen on a TCP port instead (and, indeed, that's what Drew Crawford's walkthrough suggests).