Postfix (2.9)

The following describes a postfix site installation, using MySQL as a backend and Dovecot as the MDA. You will want to get nearly everything else regarding your mail working first - MySQL, the tables, and any components you may be using (ClamAV, Spamassassin, OpenDKIM), etc.

This installation is somewhat involved - but between it and the Spamassassin configuration given, you will have very little spam to deal with. Barely a piece of spam a day even makes it to spamassassin - and this is with e-mail addresses that have been public for years. About than one in a thousand make it through Spamassassin. My gmail accounts are let more spam through than this.

There are three key rules that drastically cut down on spam:


 * 1) Requiring forward-confirmed reverse DNS (reject_unknown_client_hostname in the following config)
 * 2) Block generic domain names that pass the above test.
 * 3) Block spammers claiming to be your users.

While it does not solve all spam, it makes what is left a great deal more manageable. The resulting successful spam-friendly providers get addressed in one fashion or another.

To start, after postfix is installed, run postalias /etc/aliases

We will still be using these (occasionally).

/etc/postfix/main.cf

 * 1) There's not much left of Debian's default postfix configuration here.

smtpd_banner = $myhostname ESMTP $mail_name biff = no

append_dot_mydomain = no
 * 1) appending .domain is the MUA's job.


 * 1) Uncomment the next line to generate "delayed mail" warnings
 * 2) delay_warning_time = 4h

readme_directory = /usr/share/doc/postfix

smtpd_tls_cert_file=/etc/maincert/example.crt smtpd_tls_key_file=/etc/maincert/example.key smtpd_use_tls=yes smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache smtpd_sasl_security_options = noanonymous smtpd_tls_protocols = TLSv1, SSLv3 smtp_tls_block_early_mail_reply = yes smtp_tls_mandatory_ciphers = high smtp_tls_exclude_ciphers = aNULL, NULL, MD5, ADH
 * 1) TLS parameters
 * 2) This should probably be done master.cf, but that file is ugly enough.
 * 1) smtpd_sasl_auth_enable = yes

smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth
 * 1) Because we will be using dovecot...


 * 1) See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
 * 2) information on enabling SSL in the smtp client.

myhostname = mail.example.com smtp_bind_address = 68.233.227.82 smtp_bind_address6 = 2604:4500:0:7:3::2 alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases myorigin = example.com mydestination = examplename, localhost, , relayhost = mynetworks = 127.0.0.0/8 [::1]/128 198.51.100.187 [2001:db8::4]/128 mailbox_size_limit = 0 recipient_delimiter = _ inet_interfaces = all html_directory = /usr/share/doc/postfix/html
 * 1) Most providers still use IPv4. Mailservers prefer to use IPv6 if they can, Google's among them.
 * 1) If you have multiple servers across the Internet, but want them all to send mail to the same machine,
 * 2) be sure to set myorigin to the one primary domain you want to use.
 * 1) Leave this blank if this is your main mx, otherwise, set it to your primary mailserver where you are directing mail:
 * 2) relayhost = mail.example.com
 * 3) Assuming mail.example.com is your highest-priority mx.
 * 1) In mynetworks, IPv6 addresses need to be in brackets, like so.
 * 1) Make sure the ips of any secondary mxes are listed in mynetworks.
 * 1) Having a nonstandard recipient delimiter is exceedingly handy.
 * 1) You may not want all of them on.

smtpd_hard_error_limit  = 3 smtpd_soft_error_limit  = 1 smtpd_junk_command_limit = 20 smtpd_helo_required     = yes disable_vrfy_command    = no strict_rfc821_envelopes  = no
 * 1) Be strict. Somewhat.

maximal_backoff_time        = 19200s qmgr_message_active_limit   = 65000 qmgr_message_recipient_limit = 65000 bounce_queue_lifetime       = 1w maximal_queue_lifetime      = 1w
 * 1) The next five lines were when I was dealing with Yahoo headaches. The defaults are probably fine.

message_size_limit = 33554432

authorized_submit_users = !banusera, !banusertoo, static:all

milter_default_action = accept milter_protocol = 2 smtpd_milters = local:/var/run/opendkim/opendkim.sock non_smtpd_milters = local:/var/run/opendkim/opendkim.sock
 * 1) Milters

smtpd_client_restrictions = permit_mynetworks, reject_unknown_client_hostname, reject_unauth_pipelining, check_client_access pcre:/etc/postfix/valid-domains, check_client_access pcre:/etc/postfix/reject-domains, permit
 * 1) Blocking non-FCrDNS hostnames stops about 300 pieces of spam per day, generic hostnames about 100. This would be much worse if they weren't blocked.

smtpd_helo_restrictions = permit_mynetworks, check_helo_access pcre:/etc/postfix/reject-nomailfrom, check_helo_access mysql:/etc/postfix/reject-mydomains.cf, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, permit
 * 1) reject-mydomains got hit hundreds of times per day just after turning it on. Have not had a peep lately.

smtpd_sender_restrictions = permit_mynetworks, check_sender_access pcre:/etc/postfix/reject-nomailfrom, check_sender_access mysql:/etc/postfix/reject-mydomains.cf, reject_non_fqdn_sender, reject_unknown_sender_domain, permit

smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination, check_recipient_access pcre:/etc/postfix/reject-users, reject_non_fqdn_recipient, permit


 * 1) Currently such a rare occurance that I don't see the need to discriminate yet. Saved for posterity, though.
 * 2) These block 'new' domains. See spameatingmonkey.net for details.
 * 3)                               reject_rhsbl_client fresh15.spameatingmonkey.net,
 * 4)                               reject_rhsbl_helo fresh15.spameatingmonkey.net,
 * 5)                               reject_rhsbl_sender fresh15.spameatingmonkey.net,

smtpd_data_restrictions = permit_mynetworks, reject_multi_recipient_bounce, permit

virtual_mailbox_base   = /var/vmail/ virtual_mailbox_limit  = 536870912 virtual_minimum_uid    = 999 virtual_uid_maps       = static:999 virtual_gid_maps       = static:999 virtual_mailbox_domains = mysql:/etc/postfix/virtual-domains.cf smtpd_sender_login_maps = mysql:/etc/postfix/virtual-accounts.cf virtual_mailbox_maps   = mysql:/etc/postfix/virtual-users.cf virtual_alias_maps      = mysql:/etc/postfix/virtual-aliases.cf virtual_transport       = dovecot

dovecot_destination_recipient_limit = 1 spamassassin_destination_recipient_limit = 1

sender_dependent_default_transport_maps = pcre:/etc/postfix/sdd_transport_maps

/etc/postfix/master.cf
# # # localhost:smtp                 inet  n       -       n       -       -       smtpd -o smtpd_sasl_auth_enable=yes 198.51.100.187:smtp             inet  n       -       n       -       -       smtpd -o content_filter=spamassassin -o smtpd_milters=local:/var/run/clamav/clamav-milter.ctl [2001:db8::4]:smtp      inet  n       -       n       -       -       smtpd -o content_filter=spamassassin -o smtpd_milters=local:/var/run/clamav/clamav-milter.ctl 198.51.100.187:submission       inet  n       -       n       -       -       smtpd -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_client_recipient_rate_limit=60 -o smtpd_client_message_rate_limit=60 -o smtpd_client_restrictions=permit_sasl_authenticated,reject -o smtpd_helo_restrictions=permit -o smtpd_sender_restrictions=reject_sender_login_mismatch,permit -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject -o smtpd_data_restrictions=permit -o cleanup_service_name=submission_cleanup -o milter_macro_daemon_name=ORIGINATING [2001:db8::4]:submission inet n       -       n       -       -       smtpd -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_client_recipient_rate_limit=60 -o smtpd_client_message_rate_limit=60 -o smtpd_client_restrictions=permit_sasl_authenticated,reject -o smtpd_helo_restrictions=permit -o smtpd_sender_restrictions=reject_sender_login_mismatch,permit -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject -o smtpd_data_restrictions=permit -o cleanup_service_name=submission_cleanup -o milter_macro_daemon_name=ORIGINATING secondmailer           unix  -       -       n       -       -       smtp -o smtp_bind_address=secondmaileripv4address -o smtp_bind_address6=secondmaileripv6address -o myhostname=example.com -o myorigin=example.com -o smtp_helo_name=example.com -o syslog_name=postfix-example secondmaileripv4address:smtp             inet  n       -       n       -       -       smtpd -o content_filter=spamassassin -o myhostname=example.com -o myorigin=example.com -o syslog_name=postfix-example -o smtpd_milters=local:/var/run/clamav/clamav-milter.ctl [secondmaileripv6address]:smtp      inet  n       -       n       -       -       smtpd -o content_filter=spamassassin -o myhostname=example.com -o myorigin=example.com -o syslog_name=postfix-example -o smtpd_milters=local:/var/run/clamav/clamav-milter.ctl pickup   fifo  n       -       -       60      1       pickup cleanup  unix  n       -       n       -       0       cleanup submission_cleanup  unix  n       -       n       -       0       cleanup -o header_checks=pcre:/etc/postfix/header_checks -o syslog_name=postfix/submission/cleanup qmgr     fifo  n       -       n       300     1       qmgr tlsmgr   unix  -       -       -       1000? 1      tlsmgr rewrite  unix  -       -       n       -       -       trivial-rewrite bounce   unix  -       -       -       -       0       bounce defer    unix  -       -       -       -       0       bounce trace    unix  -       -       -       -       0       bounce verify   unix  -       -       -       -       1       verify flush    unix  n       -       -       1000? 0      flush proxymap unix  -       -       n       -       -       proxymap proxywrite unix -      -       n       -       1       proxymap smtp     unix  -       -       -       -       -       smtp relay    unix  -       -       -       -       -       smtp showq    unix  n       -       -       -       -       showq error    unix  -       -       -       -       -       error retry    unix  -       -       -       -       -       error discard  unix  -       -       -       -       -       discard local    unix  -       n       n       -       -       local virtual  unix  -       n       n       -       -       virtual lmtp     unix  -       -       -       -       -       lmtp anvil    unix  -       -       -       -       1       anvil scache   unix  -       -       -       -       1       scache # # # # maildrop unix  -       n       n       -       -       pipe flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient} # # # # # # # # # # # # uucp     unix  -       n       n       -       -       pipe flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient) # # ifmail   unix  -       n       n       -       -       pipe flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient) bsmtp    unix  -       n       n       -       -       pipe flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient scalemail-backend unix -       n       n       -       2       pipe flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension} mailman  unix  -       n       n       -       -       pipe flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py  ${nexthop} ${user} dovecot unix   -       n       n       -       -      pipe flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -m ${extension} spamassassin unix -       n       n       -       -       pipe user=debian-spamd argv=/usr/bin/spamc -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
 * 1) Some of this stuff covers a few odd things you may want to do, I will highlight
 * 2) them in these comments.
 * 1) Postfix master process configuration file.  For details on the format
 * 2) of the file, see the master(5) manual page (command: "man 5 master").
 * 1) Do not forget to execute "postfix reload" after editing this file.
 * 1) service type  private unpriv  chroot  wakeup  maxproc command + args
 * 2)               (yes)   (yes)   (yes)   (never) (100)
 * 1)               (yes)   (yes)   (yes)   (never) (100)
 * 1) Run spamassassin and clamav only on incoming mail.
 * 1) I use the submission port to actually accept mail from users, including my own forums.
 * 2) They need different rules, obviously.
 * 1) Some policies may suggest that you setup a second mailing ip to segregate e.g. marketing mail from your
 * 2) more mission-critical mail. 'secondmailer' here and in sdd_transport_maps shows how to go about this.
 * 1) The second mailer acts as its own mailserver, down to receiving mail.
 * 1) smtp     inet  n       -       -       -       -       smtpd
 * 2) smtp     inet  n       -       -       -       1       postscreen
 * 3) smtpd    pass  -       -       -       -       -       smtpd
 * 4) dnsblog  unix  -       -       -       -       0       dnsblog
 * 5) tlsproxy unix  -       -       -       -       0       tlsproxy
 * 6) submission inet n      -       -       -       -       smtpd
 * 7)  -o syslog_name=postfix/submission
 * 8)  -o smtpd_tls_security_level=encrypt
 * 9)  -o smtpd_sasl_auth_enable=yes
 * 10)  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
 * 11)  -o milter_macro_daemon_name=ORIGINATING
 * 12) smtps    inet  n       -       -       -       -       smtpd
 * 13)  -o syslog_name=postfix/smtps
 * 14)  -o smtpd_tls_wrappermode=yes
 * 15)  -o smtpd_sasl_auth_enable=yes
 * 16)  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
 * 17)  -o milter_macro_daemon_name=ORIGINATING
 * 18) 628      inet  n       -       -       -       -       qmqpd
 * 1) Cleanup for header checks. This prevents user's IP addresses from leaking
 * 2) out to nosy people.
 * 1) qmgr    fifo  n       -       n       300     1       oqmgr
 * 1)       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
 * 1) Interfaces to non-Postfix software. Be sure to examine the manual
 * 2) pages of the non-Postfix software to find out what options it wants.
 * 1) pages of the non-Postfix software to find out what options it wants.
 * 1) Many of the following services use the Postfix pipe(8) delivery
 * 2) agent.  See the pipe(8) man page for information about ${recipient}
 * 3) and other message envelope options.
 * 1) maildrop. See the Postfix MAILDROP_README file for details.
 * 2) Also specify in main.cf: maildrop_destination_recipient_limit=1
 * 1) Recent Cyrus versions can use the existing "lmtp" master.cf entry.
 * 1) Specify in cyrus.conf:
 * 2)   lmtp    cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
 * 1) Specify in main.cf one or more of the following:
 * 2)  mailbox_transport = lmtp:inet:localhost
 * 3)  virtual_transport = lmtp:inet:localhost
 * 1) Cyrus 2.1.5 (Amos Gouaux)
 * 2) Also specify in main.cf: cyrus_destination_recipient_limit=1
 * 1) cyrus    unix  -       n       n       -       -       pipe
 * 2)  user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
 * 1) Old example of delivery via Cyrus.
 * 1) Old example of delivery via Cyrus.
 * 1) old-cyrus unix -       n       n       -       -       pipe
 * 2)  flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
 * 1) See the Postfix UUCP_README file for configuration details.
 * 1) Other external delivery methods.
 * 1) The following are for dovecot and spamassassin, obviously.
 * 2) -m ${extension} lets us sent delimited mail straight to the appropriate folder.

Supporting Files
You might have noticed that the above configuration refers to a lot of supporting map files. Some of these are optional, others are highly recommended.

/etc/postfix/header_checks
/^Received: from/      REPLACE Received: from localhost (::1) (authenticated client) /^X-Originating-IP:/   IGNORE /^User-Agent:/         IGNORE /^X-Mailer:/           IGNORE
 * 1) This gets run through on cleanup, via cleanup_submission. You can see the chain for this in master.cf
 * 2) Here we delete a few common identifying marks, and replace the Received header with something explanatory.

/etc/postfix/valid-domains
/(^|-|\.)mail(\-|\.)/i                                        OK /(^|-|\.)mx(\-|\.)/i                                           OK /(^|-|\.)smtp(\-|\.)/i                                         OK /\.hotmail\.com$/                                              OK
 * 1) Some large mailer domains are starting to use names that are beginning
 * 2) to look a lot like generic names. Here we have a couple of catch-alls,
 * 3) covering Google, Yahoo, AoL, Amazon, and many common providers.
 * 4) Hotmail is off in lalaland, so they need their own entry.
 * 5) Note that the goal here is in general to be forgiving - so long as we
 * 6) know that someone owning an ip and a domain name gave some thought to it.

/etc/postfix/reject-domains
/(^|-|\.)[0-9a-f]{2}(\-+|\.)[0-9a-f]{2}(\-+|\.)[0-9]*[a-z]+/i REJECT 554 Dynamic or Generic Hostname /(^|-|\.)[0-9]+(\-+|\.)[0-9]+(\-+|\.)[0-9]*[a-z]+/i           REJECT 554 Dynamic or Generic Hostname /(^|-|\.)(vps)[0-9]{2,}/i                                     REJECT 554 Dynamic or Generic Hostname /(^|-|\.)[a-z]?[0-9a-f]{4,}(\-+|\.)(dip|dyn|pool)/i           REJECT 554 Dynamic or Generic Hostname
 * 1) The first two represend the overwhelming majority of these blocks.
 * 2) Some legitimate people have not bothered to give themselves a proper domain name,
 * 3) but frankly I'm not opening myself up again just to put up with their ignorance.

/etc/postfix/reject-nomailfrom
This is a completely optional file. If you have a big site that generates a lot of e-mail, you may want to use this on typoed domains, alternate tlds, etc. that you own. My server sends nearly a quarter-million e-mails per month - I almost never see this get hit.

/(^|@)([a-z0-9-]+\.)*example\.org$/                        REJECT 554 Domain does not send mail. /(^|@)([a-z0-9-]+\.)*example\.net$/                        REJECT 554 Domain does not send mail. /(^|@)([a-z0-9-]+\.)*example\.biz$/                        REJECT 554 Domain does not send mail. /(^|@)([a-z0-9-]+\.)*example\.info$/                       REJECT 554 Domain does not send mail.

/etc/postfix/reject-users
/^admin1@(local|example)\./         REJECT 550 User unknown /^admin2@(local|example)\./         REJECT 550 User unknown /^admin4@(local|example)\./         REJECT 550 User unknown
 * 1) I use this to hide my admin user or users - those with su access (whether to root or not).

/etc/postfix/sdd_transport_maps
/@example\.com$/            secondmailerexample:
 * 1) This lets you run what amounts to multiple mailservers off of a single postfix instance,
 * 2) in the event that you want to segregate classes of mail.
 * 1) /@example\.org$/                  exampleorgmailer:

/etc/postfix/reject-mydomains.cf
hosts = unix:/var/run/mysqld/mysqld.sock user = vmreader password = passforvmreader dbname = mail query = SELECT CONCAT('REJECT 554 You are not ',domain_name) FROM mail_domains WHERE active=1 AND domain_name='%s' OR '%s' LIKE CONCAT('%%.',domain_name)
 * 1) Some of the most revolting spam is stuff that gets sent claiming to be you. It confuses users
 * 2) who don't know what's going on and pisses off those who do. Shut that down.

/etc/postfix/virtual-accounts.cf
hosts = unix:/var/run/mysqld/mysqld.sock user = vmreader password = passforvmreader dbname = mail query = (SELECT DISTINCT(CONCAT(u.username,'@',d.domain_name)) AS account FROM mail_users AS u, mail_domains AS d WHERE u.isactive >= 1 AND d.ID_DOMAIN=u.ID_DOMAIN AND d.domain_name='%d' AND u.username=SUBSTRING_INDEX('%u', '_', 1)) UNION (SELECT DISTINCT(CONCAT(u.username,'@',d.domain_name)) AS account FROM mail_aliases AS v, mail_users AS u, mail_domains AS d, mail_domains AS da WHERE v.ID_USER=u.id_USER AND u.ID_DOMAIN=d.ID_DOMAIN AND v.ID_DOMAIN=da.ID_DOMAIN AND da.domain_name='%d' AND v.alias_local='%u')
 * 1) WARNING!
 * 2) The underscore passed to SUBSTRING_INDEX here is because I'm using that as the recipient delimiter. If you use a different one in main.cf, you will want to
 * 3) change it in SUBSTRING_INDEX here. This allows people to send mail as their aliases, delimited addresses included.

/etc/postfix/virtual-aliases.cf
hosts = unix:/var/run/mysqld/mysqld.sock user = vmreader password = passforvmreader dbname = mail query = SELECT DISTINCT(CONCAT(u.username,v.alias_ext,'@',d.domain_name)) FROM mail_aliases AS v, mail_users AS u, mail_domains AS d, mail_domains AS da WHERE v.ID_USER=u.id_USER AND u.ID_DOMAIN=d.ID_DOMAIN AND v.ID_DOMAIN=da.ID_DOMAIN AND da.domain_name='%d' AND v.alias_local='%u'
 * 1) Sortof the inverse of the above - instead of letting us know who can send from a given address, this tells us where mail for a given address goes to.

/etc/postfix/virtual-domains.cf
hosts = unix:/var/run/mysqld/mysqld.sock user = vmreader password = passforvmreader dbname = mail query = SELECT 1 FROM mail_domains WHERE active=1 AND domain_name='%s'
 * 1) By far the simplest of these.

/etc/postfix/virtual-users.cf
hosts = unix:/var/run/mysqld/mysqld.sock user = vmreader password = passforvmreader dbname = mail query = SELECT 1 FROM mail_users AS u, mail_domains AS d WHERE u.isactive >= 1 AND d.ID_DOMAIN=u.ID_DOMAIN AND d.domain_name='%d' AND u.username='%u'
 * 1) Like above - just checks if a user exists.