Securing Your Postfix Mail Server With Greylisting, SPF, DKIM and DMARC and TLS

A few months ago, while trying to debug some SPF problems, I came across “Domain-based Message Authentication, Reporting & Conformance” (DMARC).

DMARC basically builds on top of two existing frameworks, Sender Policy Framework (SPF), and DomainKeys Identified Mail (DKIM).

SPF is used to define who can send mail for a specific domain, while DKIM signs the message. Both of these are pretty useful on their own, and reduce incoming spam significantly, but the problem is you don’t have any “control” over what the receiving end does with email. For example, company1’s mail server may just give the email a higher spam score if the sending mail server fails SPF authentication, while company2’s mail server might outright reject it.

DMARC gives you finer control, allowing you to dictate what should be done. DMARC also lets you publish a forensics address. This is used to send back a report from remote mail servers, and contains details such as how many mails were received from your domain, how many failed authentication, from which IPs and which authentication tests failed.

I’ve had a DMARC record published for my domains for a few months now, but I have not setup any filter to check incoming mail for their DMARC records, or sending back forensic reports.

Today, I was in the process of setting up a third backup MX for my domains, so I thought I’d clean up my configs a little, and also setup DMARC properly in my mail servers.

So in this article, I will be discussing how I setup my Postfix servers using Greylisting, SPF, DKIM and DMARC, and also using TLS for incoming/outgoing mail. I won’t be going into full details for how to setup a Postfix server, only the specifics needed for SPF/DKIM/DMARC and TLS.

We’ll start with TLS as that is easiest.


I wanted all incoming and outgoing mail to use opportunistic TLS.

To do this all you need to do is create a certificate:

[root@servah ~]# cd /etc/postfix/
[root@servah ~]# openssl genrsa -des3 -out
[root@servah ~]# openssl rsa -in -out
[root@servah ~]# mv
[root@servah ~]# openssl req -new -key -out

Now, you can either self sign it the certificate request, or do as I have and use Once you have a signed certificate, dump it in mx1.example.crt, and tell postfix to use it in /etc/postfix/

# Use opportunistic TLS (STARTTLS) for outgoing mail if the remote server supports it.
smtp_tls_security_level = may
# Tell Postfix where your ca-bundle is or it will complain about trust issues!
smtp_tls_CAfile = /etc/ssl/certs/
# I wanted a little more logging than default for outgoing mail.
smtp_tls_loglevel = 1
# Offer opportunistic TLS (STARTTLS) to connections to this mail server.
smtpd_tls_security_level = may
# Add TLS information to the message headers
smtpd_tls_received_header = yes
# Point this to your CA file. If you used, this
# available at
smtpd_tls_CAfile = /etc/postfix/ca.crt
# Point at your cert and key
smtpd_tls_cert_file = /etc/postfix/
smtpd_tls_key_file = /etc/postfix/
# I wanted a little more logging than default for incoming mail.
smtpd_tls_loglevel = 1

Restart Postfix:

[root@servah ~]# service postfix restart

That should do it for TLS. I tested by sending an email from my email server, to my Gmail account, and back again, checking in the logs to see if the connections were indeed using TLS.


Greylisting is method of reducing spam, which is so simple, yet so effective it’s quite amazing!

Basically, incoming relay attempts are temporarily delayed with a SMTP temporary reject for a fixed amount of time. Once this time has finished, any further attempts to relay from that IP are allowed to progress further through your ACLs.

This is extremely effective, as a lot of spam bots will not have any queueing system, and will not re-try to send the message!

As EPEL already has an RPM for Postgrey, so I’ll use that for Greylisting:

[root@servah ~]# yum install postgrey

Set it to start on boot, and manually start it:

[root@servah ~]# chkconfig postgrey on
[root@servah ~]# service postgrey start

Next we need to tell Postfix to pass messages through Postgrey. By default, the RPM provided init scripts setup a unix socket in /var/spool/postfix/postgrey/socket so we’ll use that.

Edit /etc/postfix/, and in your smtpd_recipient_restrictions, add check_policy_service unix:postgrey/socket, like I have:

  check_policy_service unix:postgrey/socket,

As you can see, I am also using various RBLs.

Next, we restart Postfix:

[root@servah ~]# service postfix restart

All done. Greylisting is now in effect!


Next we’ll setup SPF.

There are many different SPF filters available, and probably the most popular one to use with Postfix would be pypolicyd-spf, which is also included in EPEL, but I was unable to get OpenDMARC to see the Recieved-SPF headers. I think this is due to the order in which a message is passed through a milter and through a postfix policy engine, and I was unable to find a workaround. So instead I decided to use smf-spf, which is currently unmaintained, but from what I understand it is quite widely used, and quite stable.

I did apply some patches to smf-spf which were posted by Andreas Schulze on the the OpenDMARC mailing lists. They are mainly cosmetic patches, and aren’t necessary but I liked them so I applied them.

I was going to write a RPM spec file for smf-spf, but I noticed that Matt Domsch has kindly already submitted packages for smf-spf and libspf2 for review.

I did have to modify both packages a little. For smf-spf I pretty much only added the patches I mentioned eariler, and a few minor changes I wanted. For libspf2 I had to re-run autoreconf and update Matt Domsch’s patch as it seemed to break on EL6 boxes due to incompatible autoconf versions. I will edit this post later and add links to the SRPMS later.

I build the RPMs, signed them with my key and published it in my internal RPM repo. I won’t go into detail into that, and will continue from installation:

[root@servah ~]# yum install smf-spf

Next, I edited /etc/mail/smfs/smf-spf.conf, set smf-spf to start on boot and started smf-spf:

 RefuseFail on
 AddHeader on
 User smfs
 Socket inet:8890@localhost

Set smf-spf to start on boot, and also start it manually:

[root@servah ~]# chkconfig smf-spf on
[root@servah ~]# service smf-spf start

Now we edit the Postfix config again, and add the following to the end of

milter_default_action = accept
 milter_protocol = 6
 smtpd_milters = inet:localhost:8890

Restart Postfix:

[root@servah ~]# service postfix restart

Your mail server should now be checking SPF records! 🙂
You can test this by trying to forge an email from Gmail or something.


DKIM was a little more complicated to setup as I have multiple domains. Luckily, OpenDKIM is already in EPEL, so I didn’t have to do any work to get an RPM for it! 🙂

Install it using yum:

[root@servah ~]# yum install opendkim

Next, edit the OpenDKIM config file. I’ll just show what I done using a diff:

< Mode v
> Mode sv
< Selector default
> #Selector default
< #KeyTable /etc/opendkim/KeyTable
> KeyTable /etc/opendkim/KeyTable
< #SigningTable refile:/etc/opendkim/SigningTable
> SigningTable refile:/etc/opendkim/SigningTable
< #ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
> ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
< #InternalHosts refile:/etc/opendkim/TrustedHosts
> InternalHosts refile:/etc/opendkim/TrustedHosts

Next, I created a key:

[root@servah ~]# cd /etc/opendkim/keys
[root@servah ~]# opendkim-genkey --append-domain --bits=2048 --domain --selector=dkim2k --restrict --verbose

This will give you two files in /etc/opendkim/keys:

  • dkim2k.txt - Contains your public key which can be published in DNS. It’s already in a BIND compatible format, so I won’t explain how to publish this in DNS.
  • dkim2k.private - Contains your private key.

Next, we edit /etc/opendkim/KeyTable. Comment out any of the default keys that are there and add your own:

[root@servah ~]# cat /etc/opendkim/KeyTable

Now edit /etc/opendkim/SigningTable, again commenting out the default entries and entering our own:

[root@servah ~]# cat /etc/opendkim/SigningTable

Repeat this process for as many domains as you want. It would also be quite a good idea to use different keys for different domains.

We can now start opendkim, and set it to start on boot:

[root@servah ~]# chkconfig opendkim on
[root@servah ~]# service opendkim start

Almost done with DKIM!

We just need to tell Postfix to pass mail through OpenDKIM to verify signatures of incoming mail, and to sign outgoing mail. To do this, edit /etc/postfix/ again:

# Pass SMTP messages through smf-spf first, then OpenDKIM
smtpd_milters = inet:localhost:8890, inet:localhost:8891
# This line is so mail received from the command line, e.g. using the sendmail binary or mail() in PHP
# is signed as well.
non_smtpd_milters = inet:localhost:8891

Restart Postfix:

[root@servah ~]# service postfix restart

Done with DKIM!

Now your mail server will verify incoming messages that have a DKIM header, and sign outgoing messages with your own!


Now it’s the final part of the puzzle.

OpenDMARC is not yet in EPEL, but again I did find an RPM spec waiting review, so I used it.

Again, I won’t go into the process of how to build an RPM, lets assume you have already published it in your own internal repos and continue from installation:

[root@servah ~]# yum install opendmarc

First I edited /etc/opendmarc.conf:

< # AuthservID name
> AuthservID
< # ForensicReports false
> ForensicReports true
< HistoryFile /var/run/opendmarc/opendmarc.dat/;
< s
> HistoryFile /var/run/opendmarc/opendmarc.dat
< # ReportCommand /usr/sbin/sendmail -t
> ReportCommand /usr/sbin/sendmail -t -F ' DMARC Report" -f ''
< # Socket inet:8893@localhost
> Socket inet:8893@localhost
< # SoftwareHeader false
> SoftwareHeader true
< # Syslog false
> Syslog true
< # SyslogFacility mail
> SyslogFacility mail
< # UserID opendmarc
> UserID opendmarc

Next, set OpenDMARC to start on boot and manually start it:

[root@servah ~]# chkconfig opendmarc on
[root@servah ~]# service opendmarc start

Now we tell postfix to pass messages through OpenDMARC. To do this, we edit /etc/postfix/ once again:

# Pass SMTP messages through smf-spf first, then OpenDKIM, then OpenDMARC
smtpd_milters = inet:localhost:8890, inet:localhost:8891, inet:localhost:8893

Restart Postfix:

[root@servah ~]# service postfix restart

That’s it! Your mail server will now check the DMARC record of incoming mail, and check the SPF and DKIM results.

I confirmed that OpenDMARC is working by sending a message from Gmail to my own email, and checking the message headers, then also sending an email back and checking the headers on the Gmail side.

You should see that SPF, DKIM and DMARC are all being checked when receiving on either side.

Finally, we can also setup forensic reporting for the benefit of others who are using DMARC.

DMARC Forensic Reporting

I  found OpenDMARC’s documentation to be extremely limited and quite vague, so there was a lot of guess work involved.

As I didn’t want my mail servers to have access to my DB server, I decided to run the reporting scripts on a different box I use for running cron jobs.

First I created a MySQL database and user for opendmarc:

[root@mysqlserver ~]# mysql -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 1474392
Server version: 5.5.34-MariaDB-log MariaDB Server`

Copyright (c) 2000, 2013, Oracle, Monty Program Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> CREATE DATABASE opendmarc;
MariaDB [(none)]> GRANT ALL PRIVILEGES ON opendmarc.* TO opendmarc@'' IDENTIFIED BY 'supersecurepassword';

Next, we import the schema into the database:

[root@scripty ~]# mysql -h -u opendmarc -p opendmarc < /usr/share/doc/opendmarc-1.1.3/schema.mysql

Now, to actually import the data from my mail servers into the DB, and send out the forensics reports, I have the following script running daily:


set -e

cd /home/mhamzahkhan/dmarc/


for HOST in $HOSTS; do
  # Pull the history from each host
  scp -i /home/mhamzahkhan/.ssh/dmarc root@${HOST}:/var/run/opendmarc/opendmarc.dat ${HOST}.dat
  # Purge history on each each host.
  ssh -i /home/mhamzahkhan/.ssh/dmarc root@${HOST} "cat /dev/null > /var/run/opendmarc/opendmarc.dat";

  # Merge the history files. Not needed, but this way opendmarc-import only needs to run once.
  cat ${HOST}.dat >> merged.dat

/usr/sbin/opendmarc-import --dbhost=${DBHOST} --dbuser=${DBUSER} --dbpasswd=${DBPASS} --dbname=${DBNAME} --verbose < merged.dat
/usr/sbin/opendmarc-reports --dbhost=${DBHOST} --dbuser=${DBUSER} --dbpasswd=${DBPASS} --dbname=${DBNAME} --verbose --interval=86400 --report-email '' --report-org ''
/usr/sbin/opendmarc-expire --dbhost=${DBHOST} --dbuser=${DBUSER} --dbpasswd=${DBPASS} --dbname=${DBNAME} --verbose

rm -rf *.dat

That’s it! Run that daily, and you’ll send forensic reports to those who want them. 🙂

You now have a nice mail server that checks SPF, DKIM, and DMARC for authentication, and sends out forensic reports!

With this setup, I haven’t received any spam in the last two months! That’s just as far as I can remember, but I’m sure it’s been a lot longer than that! 🙂

Any comments, and suggestions welcome!

Related Posts