Written for CentOS 6.4, Postfix 2.6.6.


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

Mail Transfer - Basic Idea

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

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 topologies 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 /etc/postfix/

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 IPv63)

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 options

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 EPEL:

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

I’m going to try the Perl implementation of SPF. 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 available ↩︎

  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. ↩︎