Written for CentOS 6.4, Postfix 2.6.6.


Things were easier to set up after understanding these things, even

Mail Transfer - Basic Idea

Lots of formal abbreviations! They are, luckily enough, quite
. Here’s the basic

Sender > Server > Server > ... > Server > Receiver  
(MUA)    (MTA)    (MTA)          (MTA)     (MUA)

You can be a bit more granular:

   Sender   >  Server > Server > ... >   Server     | Delivery |  >   Receiver  
(MUA > MSA)    (MTA)    (MTA)          (MTA > MDA)  | complete |     (MRA > MUA)

(MDAs, when local, are also called LDAs.)

This separation of purpose is good since you can use a variety of
applications and

at each stage. Lot of possibilities. E.g.

Sender > Postfix > Procmail > Clam Anti-Virus > SpamAssassin > Procmail > Fetchmail > Receiver  
(MUA)    (MTA)     (                       MDA                        )     (MRA)       (MUA)

Open Relays

Where it is not required to (1) authenticate to your server, and/or (2)
be in the same network as the server to send email. This is very bad for
public-facing mail servers. From a simpler time when there were very few
email servers and everybody was nice to each other.

Mailbox Formats

There are quite a few, each
with its own pros and cons. I personally like Maildir.


yum install postfix cyrus-sasl  
ln -s /usr/sbin/sendmail.postfix /etc/alternatives/mta --force


The Basics

Postfix ships with sane and secure defaults. Here’s stuff I changed in

First set the hostname and domain

myhostname =  
mydomain = $myhostname

Mail from this server will come from this domain.

myorigin = $mydomain

Accept mail on specified interface2 and all protocols (IPv4 and

inet_interfaces = all  
inet_protocols = all

This server will think itself the final MTA (in the chain above) for
these domains:

mydestination = $mydomain, localhost

The server will only trust itself4

mynetworks_style = host

Use the Maildir format for message delivery

home_mailbox = Maildir/

Change the banner for fun (and no version information)

smtpd_banner = $myhostname ESMTP Why, hello there!

Now edit [ /etc/postfix/]
to enable the submission port5.

submission inet n       -       n       -       -       smtpd

Uncomment any options ("-o"); we’ll take care of these in later.


Restart the postfix service. Then from another computer,

orangebox:~$ telnet 25  
Connected to  
Escape character is '^]'.  
250-SIZE 10240000  
250 DSN  
250 2.1.0 Ok  
250 2.1.5 Ok  
354 End data with <CR><LF>.<CR><LF>  
Subject: Hello!  
How have you been?  
250 2.0.0 Ok: queued as 7602C72C2  

This should:

But this is done insecurely. Let’s fix that.

Doing things securely

Generate a self-signed certificate6

openssl req -new -x509 \  
            -newkey rsa:4096 \  
            -days 3650 \  
            -nodes \  
            -out /etc/pki/tls/certs/dovecot.crt \  
            -keyout /etc/pki/tls/private/dovecot.key

chmod o= /etc/pki/tls/private/dovecot.pem

Now configure Postfix to use these certificates for TLS

smtp_tls_security_level = may  
smtpd_tls_security_level = may  
smtpd_tls_cert_file = /etc/pki/tls/certs/postfix.crt  
smtpd_tls_key_file = /etc/pki/tls/private/postfix.key  
smtpd_tls_auth_only = yes  
smtpd_tls_loglevel = 3  
smtpd_tls_received_header = yes  
smtpd_tls_session_cache_timeout = 3600s  
tls_random_source = dev:/dev/urandom

Restart Postfix. As always, see /var/log/maillog for any errors.

Now test.

Important Stuff

Some restrictions

Stepping throught the telnet output in the previous section, start
adding some restrictions to the client connection7:

smtpd_client_restrictions = reject_unknown_client_hostname, permit

Then the HELO command

smtpd_helo_required = yes  
smtpd_helo_restrictions = reject_unknown_helo_hostname, reject_non_fqdn_helo_hostname, reject_invalid_helo_hostname, permit


smtpd_sender_login_maps = pcre:/etc/postfix/login_maps.pcre  
smtpd_sender_restrictions = reject_non_fqdn_sender, reject_sender_login_mismatch, reject_unknown_sender_domain, permit

And finally, RCPT TO
This will allow you to relay messages (i.e. send email to other
domains) if you’re SASL-authenticated.

smtpd_recipient_restrictions = permit_sasl_authenticated, reject_unauth_destination, permit

Add this to /etc/postfix/login_maps.pcre8.

/^(.*)$/   ${1}

Test away! You should see good errors like:

450 4.1.8 <>: Sender address rejected: Domain not found  
450 4.7.1 <blarghhh>: Helo command rejected: Host not found  
553 5.7.1 <>: Sender address rejected: not logged in

Now add a way in which you an log in to the server remotely to send
messages through it.

SASL Authentication

Will use Cyrus.
Postfix uses it by default.
You can see what other libraries Postfix was compiled with support for as well:

[root@example ~]# postconf -a

Install Cyrus

yum install cyrus-sasl

You can then see what authentication methods Cyrus supports:

[root@example !]# saslauthd -v
saslauthd 2.1.23  
authentication mechanisms: getpwent kerberos5 pam rimap shadow ldap

Install the appropriate package. Since I’m using plain auth,

yum install cyrus-sasl-plain

Since we’re dealing with local accounts, let’s tell Cyrus to use
/etc/shadow. Open /etc/sysconfig/saslauthd:


Start the service

service saslauthd start

Make sure it starts when you reboot your server

chkconfig saslauthd on


[root@example !]# testsaslauthd -u testuser -p secretpassword
0: OK "Success."

Now tell Postfix to use Cyrus in /etc/postfix/

smtpd_sasl_auth_enable = yes  
smtpd_sasl_type = cyrus

Set some security

smtpd_sasl_security_options = noanonymous

Restart the Postfix service. Test:

[root@toolkit ~]# openssl s_client -starttls smtp -CAfile /path/to/postfix.crt -connect  
(Certificate, connection info)  
250 DSN  
auth plain An8o0tjsHojfDausWtzblk4bnZA
235 2.7.0 Authentication successful

Generate the funky MD5 output with your username and password:

echo -ne '\000user\000password' | base64

Preventing Spam, Bad Email, and DOS Attacks

Using Blocklists

Change smtpd_recipient_restrictions to add some blocklists9 and
other stringent policies:

smtpd_recipient_restrictions =   

Most residential IPs are banned by blocklists, so keep that in mind when
testing your setup:

554 5.7.1 Service unavailable; Client host [] blocked using;  

Using SPF

Sender Policy Framework
prevents fake sender addresses from your domain. It’s a great idea and
is something everyone should do10.

To empower Postfix with SPF, first install some required packages from

yum install perl-core perl-Mail-SPF --enablerepo=epel

I’m going to try the Perl implementation of
. 11 Download,
extract, move to a good place:

tar -xvzf postfix-policyd-spf-perl-2.010.tar.gz  
cd postfix-policyd-spf-perl-2.010  
mv postfix-policyd-spf-perl /usr/local/bin/

Now set up /etc/postfix/ Add to smtpd_recipient_restrictions

# Other options not shown for brevity  
smtpd_recipient_restrictions =   
  check_policy_service unix:private/policy-spf,  
  policy_time_limit = 3600 # Default is 1000; too short[^12]

Then add to /etc/postfix/

policy-spf unix  -       n       n       -       0       spawn  
    user=nobody  argv=/usr/bin/perl /usr/local/bin/postfix-policyd-spf-perl

Restart the service. Send yourself an email from another service Gmail,
and look for SPF output in /var/log/maillog

Some Limits on Interaction

The first setting is the denominator for the “limit"s

# Connection limits  
anvil_rate_time_unit = 120s  
smtpd_client_connection_rate_limit = 2400  
smtpd_client_message_rate_limit = 12000  
smtpd_error_sleep_time = 60

Prevent Abuse

Greylisting is a great
approach to fighting spam. The idea is that spammy mail servers do not
respect the RFC spec that, if an email couldn’t be delivered initially,
they are to re-attempt delivery later.

Postgrey works well for this. By
default, it asks MTAs to attempt redelivery in 5 minutes.

yum install postgrey  
service postgrey start  
chkconfig postgrey on

This will run on a Unix socket. The next step is to get Postfix to use
it. Edit /etc/postfix/

# Other options not shown for brevity  
smtpd_recipient_restrictions =  
    check_policy_service unix:postgrey/socket

I lowered the default wait time to a minute by creating
/etc/sysconfig/postgrey and adding this:

OPTIONS=$OPTIONS" --delay=60"

Restart Postfix and Postgrey. You’ll see something like this in
maillog to make sure it’s working:

postgrey[12582]: action=pass, reason=client whitelist,,   


“warning: dict_nis_init:”

Disable NIS lookups

alias_maps = hash:/etc/aliases

“Relay Access Denied”

Usually something
quite simple.



  1. The Linode page on mail
    servers is also a great overview ↩︎

  2. Can specify IP address also:

  3. Default is IPv4 ↩︎

  4. Can trust network classes or subnets and specific IP addresses ↩︎

  5. I was a little confused about this but think I understand. Port 25
    is the standard SMTP port that used for MTA-to-MTA communication. So
    if you have a user who is behind an ISP connection that blocks port
    25 (for spam or other reasons like bad proxying), they can still
    send/submit mail to your server, even if it’s not the final
    destination on the message envelope, on port 587. ↩︎

  6. Can also use StartSSL or CACert↩︎

  7. ↩︎

  8. From ServerFault. Postfix can
    use a lot more formats for controlled envelopes. See the output of
    postconf -m. For instance, I initally used this file (Specified
    with hash:/path/to/file):

    # Envelope sender Owner    me
  9. Of which there are a lot

  10. To get started, read about the syntax
    or use a wizard, then the validation tool↩︎

  11. Tried to make the Python version work but ran into issues with
    Python3 and the ipaddr module. ↩︎