Cheap VPS & Xen Server

Residential Proxy Network - Hourly & Monthly Packages

Postfix Spam Filter using Ubuntu Dapper, MailScanner, SpamAssassin, Razor, Pyzor, DCC and ClamAV


This is a similar version of the HOWTO that can be found here: If you’re looking at this howto it is assumed that you have some level of experience with email servers, DNS, TCP/IP, Firewalls and Linux in general.

1 Setting up Ubuntu Server

Please see the documentation already online for details on setting up an Ubuntu server. kreationnext has a couple of HowTo’s that are very detailed. However there are a couple of installation configurations outlined here that should be taken into account when setting up the server that might differ from other howto’s.

NOTE: Ubuntu is installed as a setuid system by default. What that means is that the root account is disabled and you have to run everything with a ‘sudo’ in front of it to run it as root. The way around this is to run ‘sudo su -‘ and you will no longer have to type ‘sudo’ in front of the command because you are in a bash session as root. For the rest of this document it is assumed that you have done this and are running a bash session as root so no sudo commands will be given. Moral of the story is, when you log into Ubuntu via SSH or console, remember to run ‘sudo su -‘ before you start editing system files, moving/making directories or installing/uninstalling software and services.

1.1 Partitions

Partitioning the drive this way is not a requirement but it will keep your server from completely running out of space if something goes wrong in the /var directory. Also, partitioning the drive up this way will allow you to create some extra graphs with MailScannerMRTG that can look at /var, /var/log and /var/spool separately. MailScannerMRTG will not check drive space on directories, it can only calculate size by partitions.

Example of my partition table:
/boot		50MB		Boot Partition		EXT3 (PRIMARY)
Swap		2GB		Swap Partition (Size depends on your Memory, double your memory should be fine) (PRIMARY)
/		2GB		Root Partition		EXT3 (LOGICAL)
/var		1GB		Variable Data partition	EXT3 (LOGICAL)
/var/log	3GB		Variable Data partition	EXT3 (LOGICAL)
/var/spool	2GB		Variable Data partition Spool	EXT3 (LOGICAL)
/usr		4GB		User installed programs		EXT3 (LOGICAL)
/usr/local	2GB		User installed programs		EXT3 (LOGICAL)
/home		ANY		Home Directories (Good place to put any extra space, you can repartition if you run out of space and use this.)	EXT3 (LOGICAL)


1.2 Verify Network Settings

We need to make sure that the system is setup with a valid static IP, the correct DNS servers are in /etc/resolv.conf and your server is identified in the /etc/hosts file.

vi /etc/network/interfaces


The Network Interfaces file should look something like this:

auto lo eth0
iface lo inet loopback
# The primary network interface
iface eth0 inet static


Restart the network service to make these changes take effect:

/etc/init.d/networking restart


Edit the resolv.conf file to add DNS servers:

vi /etc/resolv.conf


Make sure your domain name is at the top of the resolv.conf, it should look look something like this:



vi /etc/hosts


The top of file should look something like this: localhost.localdomain localhost sfp


Since we are here, you might as well add any other hosts you would like our spamfilter to know about. Add any internal mail server(s) here. Simply append any other entries to the bottom of the list.

1.3 APT and Other Tweaks

Apt needs to be setup to search in the universe and multiverse repositories. Backup your current /etc/apt/sources.list:

cp /etc/apt/sources.list /etc/apt/sources.list.default


Replace it with the following sources.list file:

vi /etc/apt/sources.list


deb dapper main restricted
deb-src dapper main restricted
deb dapper-updates main restricted
deb-src dapper-updates main restricted
deb dapper universe multiverse
deb-src dapper universe multiverse
deb dapper-security main restricted
deb-src dapper-security main restricted
deb dapper-security universe multiverse
deb-src dapper-security universe multiverse


We need to update/refresh the apt cache and install some software. To update the cache run:

apt-get update


Note: This is a good time to change your kernel image to the correct one, most likely you will need the ‘linux-image-i686’. If you don’t know what that means, look it up. Run an apt-get upgrade and take a moment to troubleshoot any errors and or problems that you might be having. We want to eliminate anything that could cause problems in the future. Remember to reboot once everything is ready and run a quick dmesg | less, it can go a long way.

apt-get install ssh


Once ssh is installed you should connect to the server via ssh using PuTTY from your linux or windows desktop. This would make it easier to get the rest of this howto done because you will be able to copy/paste onto the terminal from the desktop. So go ahead, ssh this puppy.

I usually just set the BIOS clock to local time then run the following to sync the clock.

apt-get install ntpdate

hwclock –systohc


Now we install most of the stuff we’ll need. I split the list into 4 APT install runs. 5-15 installs at one time seems prudent, up to you:

apt-get install libc6-dev dpkg-dev db4.3-util libdb4.3-dev vim lynx bzip2 unzip perl-doc libwww-perl ntp-simple

apt-get install zlib1g-dev zip libdbi-perl libconvert-binhex-perl gcc make autoconf automake libtool libmail-spf-query-perl rblcheck libnet-ident-perl

apt-get install flex bison libcompress-zlib-perl pax libberkeleydb-perl ncftp unzoo arj lzop nomarch arc zoo

apt-get install postfix postfix-pcre postfix-mysql postfix-ldap cabextract lha unrar razor pyzor spamassassin


Select NO CONFIGURATION when Debconf for Postfix comes up.

Install unarj:


dpkg -i unarj_3.10.21-2_all.deb


Now we also need to remove some programs, hopefully you don’t need PCMCIA or printer support. This server will not need dial-up support either. You will not necessarily have all of these programs installed.

Uninstall the following software (All one line):

apt-get remove ipchains lpr nfs-common portmap pidentd pcmcia-cs pcmciautils pppoe pppoeconf ppp pppconfig uw-imapd qpopper mailagent


1.6 Cleaning up services

Some services might still linger even after uninstalling the daemons. First we need to backup inet.d:

cp -R /etc/init.d /etc/init.d.backup


Now we can stop all of the services that might be running which we don’t need:

/etc/init.d/lpd stop
update-rc.d -f lpd remove

/etc/init.d/nfs-common stop
update-rc.d -f nfs-common remove

/etc/init.d/portmap stop
update-rc.d -f portmap remove

/etc/init.d/pcmcia stop
update-rc.d -f pcmcia remove

/etc/init.d/pcmciautils stop
update-rc.d -f pcmciautils remove

/etc/init.d/ppp stop
update-rc.d -f ppp remove

/etc/init.d/exim4 stop
update-rc.d -f exim4 remove

update-rc.d -f ntpdate remove


Disable all of the services we stopped:

update-inetd –disable time

update-inetd –disable daytime

update-inetd –disable echo

update-inetd –disable chargen

update-inetd –disable ident

update-inetd –disable discard


The last one may ask you a question regarding “multiple entries”, answer yes (y).

Check that we got everything:

lsof -i | grep LISTEN


The only daemon you should see at this point is *:ssh. You may have to run this again:

update-inetd –disable discard


If there are other programs shown, try rebooting and test again.

2 Setting up Postfix

We need some sample files from the Postfix source code, note that the source code we grab here is the original source code, before it is modified by the Ubuntu/Debian package maintainers:

cd /usr/local/src


tar xzvf postfix_2.3.3.orig.tar.gz


Stop Postfix:

postfix stop


MAKE SURE you answer “n” to “overwrite?” Do each section separately:

cp -i /usr/share/postfix/ /etc/postfix/

cp -i /usr/local/src/postfix-2.3.3/conf/* /etc/postfix

cp -i /etc/postfix/header_checks /etc/postfix/body_checks

cp -i /etc/postfix/access /etc/postfix/sender_access


2.1 Edit

BTW watch for the two Postfix configuration files, both located in the /etc/postfix folder. More than one admin has gotten confused between and!

First backup the current

cp /etc/postfix/ /etc/postfix/



vi /etc/postfix/


We need to add two items below the pickup service type. The pickup service “picks up” local mail (local meaning “on this machine”) and delivers it. Later we will create a daily/weekly report that this box will mail to us and because the report will contain contents that will classify the report itself as spam, this is a way to bypass content filtering for mail generated by this machine.

Add this just below the ‘pickup’ service type:

         -o content_filter=
         -o receive_override_options=no_header_body_checks


It should look like this when you are done:

pickup    fifo  n       -       -       60      1       pickup
         -o content_filter=
         -o receive_override_options=no_header_body_checks


2.2 Edit

This is the main configuration file for Postfix. For more information please read the comments in the file, the documentation in /usr/local/src/postfix-2.3.3/README_FILES, or the Postfix website

First we need to backup the file.

cp /etc/postfix/ /etc/postfix/


Since we are setting up our spamfilter to relay all of its mail to another server, we will be using what Postfix considers a “relay domain address class” which essentially means that we will use, out of the 300+ configurable parameters in Postfix, a small group of parameters that serves our purpose best. This address class is described here: We are also acting as a primary MX for another server so please read this appropriate section:

It is common in Postfix to store items in lookup tables. We are going to use several hash tables to store data that Postfix will use. Once we have plain text data in these tables, we use the postmap command to create binary files (Berkeley DB format) that Postfix will ultimately use to retrieve the data. For example, if you have a file called “sampletext” and you postmap sampletext, a new file is created “sampletext.db”. Postfix will retrieve data from “sampletext.db”, not “sampletext”. There are more than a dozen other types of data files that Postfix can use to store data. Hash tables are an appropriate choice for several tables we will use, and pcre (Perl Compatible Regular Expressions) is appropriate for a couple tables we will use to hold content filtering data. In its simplest form a hash table is comprised of 2 pieces of data, a key and a value; typically referred to as the key/value pair. The key and the value are separated with whitespace (typically a space or tab). The data in a typical table that we use in Postfix would look something like this: OK OK OK


Suggested reading:

We will be using postconf to edit instead of typing in the perameters into the file with vi. The “” marks are required as it appears in the command boxes.

2.2.1 alias_maps

We simply need to make a correction to the default setting here:

postconf -e “alias_maps = hash:/etc/aliases”


Create the aliases file:



You will see there is now an aliases.db file in /etc/ directory. That is what Postfix usually reads. Now that you have a proper aliases file and because we are going to configure our system to relay all mail (no mail will be locally delivered), the aliases file will be ignored by Postfix. Instead we will set up virtual_alias_maps for address redirecting. Other programs may not work properly if the /etc/aliases file is missing so do not remove it.

2.2.2 myorigin

The domain name that mail created on this machine appears to come from. For example, if cron sends mail to “” it will appear to come from “”.

postconf -e “myorigin =”


Obviously, in the above, and all the following commands, replace my example parameters, like “”, with your own specific values.

2.2.3 myhostname

The fully-qualified domain name (FQDN) of the machine running the Postfix system.

postconf -e “myhostname =”


2.2.4 mynetworks

These are the machines I trust, and will relay mail for, to any destination. Generally, this is set to my LAN, or just one, or a few trusted internal mail servers. Along with relay_domains, this is an important one to get right lest you run the possibility of becoming an “open relay”. In other words, your box could accept and forward mail to domains for which it has no business doing so. Being an open relay is a serious issue, and can cause you to get blacklisted by various Internet anti-spam lists, among other problems. You can specify a single computer, multiple individual computers, or any computer on a specified network. You can also exclude certain hosts in your network by preceding the IP address with an exclamation mark. If you will be dealing with multiple internal mail servers, and/or want to allow several machines and/or subnets to relay through this server (careful!), just add them to this parameter in CIDR format and seperate the networks like this:

postconf -e “mynetworks =,,”


The above will allow the machines on the networks, and to relay smtp mail through this box. The is there to allow the local server to send, you need to at least put this one in. You could also specify a single computer’s IP address. If you only know your dotted decimal netmask (i.e. and need to convert it to CIDR format, try the (Input an IP address on your network, select the subnet info tab, select your subnet mask, your network is Subnet ID/Mask Bits.) Or simply take a look at

If you do not have any networks or computers that will relay through this server, run this command only with the address.

2.2.5 message_size_limit

Maximum size email that Postfix will let in the “front door”.

postconf -e “message_size_limit = 10485760”


The above allows email up to 10MB, the value is in bytes (10*1024*1024). Mail larger than this may possibly get bypassed by the anti-virus scanner (ClamAV). You could increase this if you also configure ClamAV to scan files larger than 10MB. If you allow messages larger than 10MB, keep an eye on RAM.

2.2.6 local_transport

Return an error message for local delivery attempts.

postconf -e “local_transport = error:No local mail delivery”


2.2.7 mydestination

An empty mydestination tells Postfix this machine is not the final destination.

postconf -e “mydestination = “


2.2.8 local_recipient_maps

An empty local_recipient_maps tells Postfix there are no local mailboxes.

postconf -e “local_recipient_maps = “


2.2.9 virtual_alias_maps

Our spamfilter must be able to receive mail for postmaster@yourIP. Reportedly, some things actually expect this ability to exist. We will also allow mail to abuse@yourIP. Since we do not allow local mail delivery, mail addressed to our spamfilter’s IP address will get rejected with an error message. Setting up virtual_alias_maps allows email to these two accounts to be forwarded to an inside address. Make sure your Exchange server is set up to receive messages addressed to “root”, “postmaster” and “abuse”.

Set up a reference to the virtual file:

postconf -e “virtual_alias_maps = hash:/etc/postfix/virtual”


Then edit the virtual file:

vi /etc/postfix/virtual


Add these two lines to the top of the virtual file:



Save and exit the file, then create the binary file that Postfix will use:

postmap /etc/postfix/virtual


2.2.10 relayhost

/etc/postfix/relayhost tells or spamfilter how outgoing email should be sent. relayhost will contain the IP or FQDN of the email server you wish to use to send outbound email. In other words, non-local mail, mail bound for domains other than ours. Not to be confused with the /etc/postfix/transport file, transport is used to route incoming mail bound for one of our local domains. We will get to the transport in a moment.

If relayhost is blank, or not configured, then our spamfilter will be used for that purpose.

Note: The brackets [] are required in this command.


postconf -e “relayhost = []”


Optionally you can setup your spamfilter as your outgoing SMTP server but you should first have your reverse DNS record in place and of course your “A” record and “MX” record so other servers on the Internet will accept your mail. When you reconfigure your clients and other SMTP servers to use our spamfilter for their outgoing mail that mail will go through the same scanning process as your inbound mail (unless you find some fancy tweaks to prevent it). If these are not yet in place, it is very useful to temporarily configure relayhost with your current, working, outgoing mail server.

Also, make sure that the relayhost you designate is configured to accept email from this machine and THAT machine is not using THIS machine for ITS outgoing mail (can you say “loop”?).

2.2.11 relay_recipient_maps

We are going to build a table of every single user in every single domain that we accept mail for. This table will be used to reject mail that is addressed to nonexistent users in our domain(s). DON’T FREAK OUT JUST YET… At this time we are only going to set up the structure of the table. If you are using Exchange, there are HOWTOs available that describe automating the process of building the relay_recipients table. It has been very common of late for spammers to launch dictionary attacks; sending thousands of messages to a domain using fabricated user names. Our spamfilter would have to process each and every one of these unless you put valid users in the relay_recipients table. Don’t underestimate the importance of this and make sure you are not the only one in your organization who knows how to make changes to this file. We’ll come back to automating this after your spamfilter is functional. If you have a manageable number of users, manually enter them.

Set up a reference to a file we will create to store the data:

postconf -e “relay_recipient_maps = hash:/etc/postfix/relay_recipients”

Then edit relay_recipients:

vi /etc/postfix/relay_recipients

For the moment, we are going to accept mail for all users in our domain(s) so enter each domain you accept mail for in the following format: OK OK OK

Then create the binary file that Postfix will use:

postmap /etc/postfix/relay_recipients

The entries above are temporary. They are wildcards that allow mail to your domains. You MUST remove the entries above at some point in the near future and replace them with every single one of your valid recipients’ email addresses. When you are ready to enter each user individually in the relay_recipients file, you would first remove (or comment out) the data above that allows mail to all users in the domain, and then list each user individually in the form: OK OK OK

Actually, in this particular file the value “OK” listed after each user is not used for anything, but something must be there because a hash table requires a value after the key. Note that by eliminating from this file, you can prevent users from the Internet from sending mail to, but don’t do this if some of your own servers need to use this machine to send mail to root. If you use Exchange, here are the HOWTOs. Even if you don’t use Exchange, I found the information regarding file transfers useful. – – –

2.2.12 transport_maps

Tells Postfix where to look for a transport file. We use the transport file to tell Postfix where to forward valid mail for our domain(s). Setting up transport is similar to setting up relay_recipients.

Create a reference to it in

postconf -e “transport_maps = hash:/etc/postfix/transport”

Then edit transport:

vi /etc/postfix/transport

Add 1 new line for each domain for which you will be handling mail, similar to the example below. The IP address is that of whatever server is the final destination of messages addressed to our domain(s) (our Exchange server). It does not matter where you place these items in the file, but I like to put them at the top. smtp:[] smtp:[] smtp:[]

DO include the brackets on these lines!). You can also use FQDN hostname instead of an IP address (i.e. smtp:[]).

Now to create the binary file Postfix will use:

postmap /etc/postfix/transport

2.2.13 relay_domains

What destination domains (and subdomains thereof) this system will relay mail for. You want to list here ONLY domains for which you are responsible for accepting mail. In addition to allowing mail to be relayed to these domains, this setting also infers that we do not relay mail to domains not listed here and therefore this is a critical component in preventing the spamfilter from becoming an open relay.

postconf -e “relay_domains = hash:/etc/postfix/relay_domains”

Edit relay_domains:

vi /etc/postfix/relay_domains

Add 1 new line for each domain for which you will be handling mail, similar to the example below: OK OK OK

This file currently has a very similar format to relay_recipients do not mistake the two. This file cannot have ‘@’ in front of the domain name. Just thought I’d mention it, some very smart people have been known to have done this…

Then create the binary file Postfix will use:

postmap /etc/postfix/relay_domains

NOTE: relay_recipients, relay_domains, and transport are files you will become very intimate with. Every time you need to add users to a domain, add a domain or remove a domain from your list, you need to go to all three of these files and edit them. After the edit, run a postmap on each one and restart Postfix with ‘postfix reload’. In the last page there is a ‘Maintenance’ part which contains a README that can be copied to your spamfilter as a quick reference for some common tasks.

2.2.14 recipient_delimiter

If your current SMTP/POP3/IMAP server is configured to use address extensions (for example then recipient_delimiter should be set to match the delimiter you are currently using to separate the user name from the address extension. This has nothing to do with the comma you are using to separate multiple people you send email to (,, using your email client (MUA). You might be familiar with this if you use GMail. If you have a GMail account you can send email to it in this format, for example and set a Filter up in GMail to tag everything with the ‘testtag’ in the to address and skip the inbox. Recently a common use for this feature is to use these tags when you subscribe to any web service and then filter according to the tag. Anyway, if you have it you’ll know you have it if you have no idea what I’m talking about follow the “I Don’t use recipient delimiters” instructions.

3 Typical settings – Choose one.

1. I Don’t use recipient delimiters:

postconf -e “recipient_delimiter = “

2. I currently use the plus sign:

postconf -e “recipient_delimiter = +”

2. I currently use the minus sign:

postconf -e “recipient_delimiter = -“

2.2.15 NAT/Proxy Settings (Optional)

If (and only if) the IP address you present to the world is not the IP address of your spamfilter (you are configured to run behind a NAT firewall or a proxy server) go ahead and add the following two lines to, then uncomment and configure proxy_interfaces ( represents the public IP address):

# Specify your NAT/proxy EXTERNAL address here.
#proxy_interfaces =

2.3 Postfix Anti-Spam Settings

Preliminary Notes:

When a client (a computer trying to send us mail) connects to Postfix and begins a communication session, Postfix records information about that session. Prior to the point where Postfix accepts mail from that session for delivery, we have the option of evaluating the session and rejecting the mail by setting some restrictions in This link illustrates what happens during a typical SMTP session: The restrictions below help ward off some spam and prevent our system from becoming an open relay. These restrictions cause some mail to be rejected by Postfix right at the “front door”. This will save system resources because the mail will not enter our system and therefore will not be scanned by MailScanner (and subsequently by SpamAssassin). This is good and bad. It saves system resources, but it also doesn’t let you see the rejected mail. All you will see is a log entry or two from Postfix saying essentially “Hey, a mail server named “xxxxxxxx” at IP address X.X.X.X tried to send some mail in, but it broke rule XYZ, so I rejected it”. Now this could have been spam (spammers often intentionally don’t follow RFCs in order to accomplish their goals) but if it was a “legitimate” mail, say from a customer whose IT Department has simply misconfigured their mail server (it happens), you could have some ruffled feathers to deal with.

If you want to allow ALL mail addressed to us to come in the front door, and therefore allow MailScanner/SpamAssassin to handle all spam control within the system, you would need to modify a couple of the settings below. I personally find a combination of Postfix and SpamAassassin anti-spam control to be best. At the very least you need to insure you do not disable the built in default anti-relay control in Postfix (smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination).

The configuration below is actually very conservative, allowing most email to come in the front door so MailScanner and SpamAssassin have their shot at it. For me it (safely) rejects about 35% of the email bound for my users, so I think these settings are quite valuable. I recommend this approach to start. Adding additional restrictions will increase the likelihood of rejecting valid email from improperly configured computers. If you decide to add/remove permissions/restrictions in the future, do so one at a time and give yourself ample time to evaluate the effect of the change. I strongly suggest you actually have a good understanding of how these restrictions work before you make changes to the entries below. Among other things, getting this stuff wrong could reject legitimate mail and/or cause us to become an open relay. Note that restrictions don’t always restrict, some also permit.

If you want to gain a better understanding of these settings, good resources are,,, the somewhat dated (2001), and this excellent book. You will note I only use a couple of the same settings as these, so this configuration is no where near as restrictive. Keep in mind that SpamAssassin and MailScanner will come into the picture in a bit, and will provide us with much more flexible and configurable options to recognize and manipulate spam.

2.3.1 smtpd_helo_required

Make any connecting mail server do a proper smtp “handshake” and announce its name. Internet RFCs require this, so we do too.

postconf -e “smtpd_helo_required = yes”

Preface: Postfix’ restriction stages are as follows, and are processed in the following order:



We are only going to place entries in the last three restriction stages. Restriction stages are processed in this order regardless of the order listed in

2.3.2 smtpd_sender_restrictions

This restriction stage restricts what sender addresses this system accepts in MAIL FROM: commands (the envelope sender). We will place three tests (restrictions) in this restriction stage.

A restriction stage holds a list of restrictions (tests). Typically, tests evaluate to either DUNNO, REJECT, or OK. DUNNO means “I don’t know what to do, let the next test decide”. REJECT simply rejects the mail. OK means no more tests are performed in this restriction stage, tests continue with the next stage (if any). Reject_* type tests typically evaluate to REJECT or DUNNO. Permit_* type tests typically evaluate to OK or DUNNO, and check_*_access type tests can perform a variety of actions. The illustration shows the basic logic.

         SMTP session
restriction stage-------------
  test ---------------REJECT->
  |   \
  |    DUNNO
  |     \
  |      V
  |   next test------REJECT->
  |      |   \
  OK     OK   DUNNO
  |      |     \
  |      |      V
  V      V
  next restriction stage-------> check_sender_access

See Here we ask Postfix to compare the envelope sender to entries in an /etc/postfix/sender_access database and act upon those entries if a match is found. We also define what action is taken there (OK, DUNNO, REJECT etc.) on a sender by sender basis. If the sender is not listed in the file, the test evaluates to DUNNO, and the next test is performed. I will provide examples a little later. One use of this file is to place a list of senders (email addresses, domains, network addresses etc.) from which we wish to not receive mail (blacklist them). We will also have the ability to blacklist senders in MailScanner and SpamAssassin but it will save resources if we blacklist them here. We will also use this file to allow specific senders to bypass the next two tests in this restriction stage. If we give them an OK here, no more tests are performed in this restriction stage, the tests continue in the next restriction stage (smtpd_recipient_restrictions). Note that if we were to place this setting in the smtpd_recipient_restrictions restriction stage before the reject_unauth_destination test, and we were to give someone the OK there, the reject_unauth_destination test located there would be bypassed. This would be bad because anyone we gave the OK to would then be able to use our server as an open relay. Access restrictions are evaluated in the same order we list them, and if a match is found it will influence how (whether) further restrictions are evaluated. reject_non_fqdn_sender

Reject when the envelope sender mail address is not in the proper format. Remember, the “envelope sender” is what the sending mail server gives in the “MAIL FROM:” line during the SMTP session, not the header “From:” line. “Joe” is not allowed to send us mail (because we can’t reply to “Joe”) but “” is at the very least an email address. If the sender does not get rejected at this point, this test evaluates to “DUNNO”. reject_unknown_sender_domain

Reject when the envelope sender’s domain part of the mail address has no DNS “A” or “MX” record at all. This setting kicks about 35% of the mail coming in my mail server. It is common for spammers to use a bogus domain name so they don’t have to deal with the backlash of rejected mail. It is also important for us not to fill up our queue with Bounce notices that can never be delivered due to the fact that the sender’s domain does not even exist. If the sender’s domain has an “A” or “MX” record, this test will also evaluate to “DUNNO”. On occasion, you will see in a report that someone you wish to receive mail from has been rejected by this setting. One possible cause of this is when legitimate senders deliberately use bogus domain names so you will not reply to them. This is where the sender access list comes in handy. You can give them an OK there, and this test will be bypassed.

Now to implement these three restrictions:

postconf -e “smtpd_sender_restrictions = check_sender_access hash:/etc/postfix/sender_access, reject_non_fqdn_sender, reject_unknown_sender_domain”


2.3.3 smtpd_recipient_restrictions

The access restrictions that the Postfix SMTP server applies in the context of the RCPT TO: command. This refers to the “envelope recipient” which is what the client gave in the “RCPT TO:” line during the SMTP session, not the header “To:” line. smtpd_recipient_restrictions is another restriction stage that holds a list of specific restrictions. Other restriction stages that are evaluated prior to smtpd_recipient_restrictions are smtpd_client_restrictions, smtpd_helo_restrictions and smtpd_sender_restrictions (in that order). Restrictions that would normally go in these prior restriction stages can alternately be placed in smtpd_recipient_restrictions. Therefore, some people prefer to place all the smtpd_*_restrictions that would normally go in prior restriction stages into smtpd_recipient_restrictions (in the proper order) and leave the prior stages unconfigured (empty). In our case it is safer to use smtpd_sender_restrictions and smtpd_recipient_restrictions. Let’s look at those specific restrictions (tests) we place in smtpd_recipient_restrictions: permit_mynetworks

Allows machines listed in “mynetworks” to skip the rest of the tests in this restriction stage (permit = OK). In other words, it exits this stage and is tested in the next stage (smtpd_data_restrictions). Because permit_mynetworks is placed in front of reject_unauth_destination, this means machines in $mynetworks are allowed to relay mail to any domain. Without this, we would only be able to send mail to our own domain(s). If the IP address of the sender is not listed in $mynetworks, the test evaluates to “DUNNO” and continues on to the next test (reject_unauth_destination). reject_unauth_destination

This, along with permit_mynetworks is used for relay control. This setting, in essence, means that mail bound for any domain that we have not configured our machine to accept mail for will be rejected. In our case Postfix will use the relay_domains setting (or table) that we configured earlier to determine what domains those are. See for additional details. If the domain is listed in relay_domains, this test evaluates to “DUNNO” and the session is allowed to go on to the next test (if any). Just like “mynetworks”, this setting is extremely critical. By placing permit_mynetworks directly ahead of reject_unauth_destination, we are assured that we can send mail to domains other than ours, but we will only accept mail addressed to us from computers outside our network, thus permit_mynetworks and reject_unauth_destination work as a team. reject_unauth_pipelining

Rejects bulk mailers that attempt to use pipelining to speed delivery, without checking if it is supported first (non-RFC, common among spammers).

Now to implement these three restrictions:

postconf -e “smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination, reject_unauth_pipelining”


2.3.4 smtpd_data_restrictions

Optional access restrictions that the Postfix SMTP server applies in the context of the SMTP DATA: command. Like smtpd_recipient_restrictions, this is a restriction stage. reject_unauth_pipelining

I repeat this setting in smtpd_data_restrictions as it is not always effective when placed in smtpd_recipient_restrictions. I include it in smtpd_recipient_restrictions as I like to place it prior to any policy servers. Note that there are only a couple of restrictions that make good use of smtpd_data_restrictions.

postconf -e “smtpd_data_restrictions = reject_unauth_pipelining”


2.3.5 Postfix content filtering control files: – /etc/postfix/header_checks and /etc/postfix/body_checks

These files will list certain “strings” of text, and tell Postfix what to do with mail if it encounters these strings in email headers or the body of the message. Sample files are already created for us, with comments explaining what to put in them. You can edit them at your leisure. Note that these files require the use of “regular expression format”. “regexp” is something you’ll want to learn about in order to live in the *nix world. Get a book or research it on the Net sometime. For an example of an elaborate header_checks file from someone with an attitude, take a look at But don’t blindly follow this, it’s only an example! Here is an example of a body_checks file: It would be worth your while to check out when you have a leisure moment. Specifically and I recommend against making a large number of changes to any part of Postfix without giving yourself plenty of time to evaluate the effects. Like a turtle, go slow, live long. header_checks & body_checks

Let’s go ahead and put these two in header_checks is required because it allows us to hold all incoming email in order for MailScanner to do it’s thing:

postconf -e “header_checks = pcre:/etc/postfix/header_checks”

postconf -e “body_checks = pcre:/etc/postfix/body_checks”


Edit header_checks:

vi /etc/postfix/header_checks


Add this line to the header_checks file, without it MailScanner will not work:

/^Received:/ HOLD


Note that we do not have to postmap pcre type tables. They remain plain text and Postfix uses them “as is”.

You could also configure mime_header_checks and nested_header_checks along with header_checks and body_checks. If you get too creative with content filtering in Postfix (using header_checks and body_checks), it can have a significant impact on performance. /etc/postfix/sender_access

We referenced this file in smtpd_sender_restrictions. We use this file to check the sender right at the front door. In this file, we’ll list certain senders/domains/IPaddress ranges for special handling. Below are bogus examples, create your own as you see fit. Please read /etc/postfix/sender_access for more information. Although you could use this file for various purposes, considering the way we have set this up in smtpd_sender_restrictions, I suggest using it to either blacklist senders, or allow certain senders to bypass the remaining tests in smtpd_sender_restrictions.

vi /etc/postfix/sender_access

#Example sender access map file
makeabuck@mlm.tld 550 No MLM thanks
allspam.tld 550 Spam is not accepted here REJECT REJECT OK OK

Since this is a hash table, you need to postmap it as usual:

postmap /etc/postfix/sender_access

2.3.6 Final Look at the Postfix Install

Riview changes:

less /etc/postfix/

Check the contents of the file for errors and repair if needed. Fire up Postfix:

postfix start

Check that Postfix responds:

telnet 25

You should see:

220 [yourFQDNhere] ESMTP Postfix (Ubuntu)

hit [enter] a few times; then type quit to exit.

If it does not reply in this manner, open another terminal window and stop Postfix postfix stop. Make sure you ran newaliases and all the postmap commands above. Check all the settings in and There is a nice paper on troubleshooting Postfix at but keep in mind our system is not ready to relay mail at this point (it will end up in the queue).

Any time you make changes to or or to data tables, most (not all) of the time, it is required that you to reload Postfix with:

postfix reload

Now that we have a basic Postfix configuration, and are good places to gain a better understanding of some of the settings we used and at this point these READMEs will make more sense.

By default, our Postfix runs chrooted. If you don’t know what that means, it will not matter much at this point. I leave it to you to research what chroot means at some later time. Some of the system files that Postfix needs to run properly were copied to Postfix’s chroot jail (/var/spool/postfix) during installation. On occasion, the original files will get modified, and Postfix will complain that the copy it has is not the same as the original. When this happens, you can manually copy the file(s) postfix has complained about to the chroot jail, or we can simply run a script that is supplied with the Postfix source code (called LINUX2) that will once again copy all the files that Postfix needs to where it needs them.

cd /usr/local/src/postfix-2.3.3/examples/chroot-setup
postfix start
chmod +x LINUX2
cp LINUX2 /usr/bin
postfix check

This makes the LINUX2 script executable, copies it to a directory in our path, then executes it. LINUX2 will usually resolve any issues or problems with Postfix if you see any errors in the logs with Postfix accessing configuration files or directories.

3 Pyzor, Razor, DCC, SpamAssassin and MailScanner Configuration

3.1 Pyzor Configuration

We need to change some permissions on pyzor first:

chmod -R a+rX /usr/share/doc/pyzor /usr/bin/pyzor /usr/bin/pyzord


This next command will have to be modified if you have a different version of python. Try locate pyzor.

chmod -R a+rX /usr/lib/site-python/pyzor


Here we supply the IP address of the Pyzor server to Pyzor. This will create a .pyzor directory in both user’s home directories, and place the server’s IP address in a servers file therein. Then it will test the connection. If you are behind a firewall, open port 24441/udp in and out to your server. While you’re at it also open up 6277/udp for DCC, 2703/tcp for Razor and 783/tcp for SpamAssassin:

pyzor ping

Usually you’ll get a timeout from the pyzor ping so don’t worry about it. We’ll test again later.

If in the future the IP address of the server changes, you will need to run through this section again. You can find the address of the current Pyzor server here

If Pyzor is working, you will see “Pyzor: got response:” Pyzor queries a Pyzor server in much the same way your computer queries a DNS server. The only practical difference is the port number that is used. If Pyzor is not working, you might need to open up the port on your firewall or the Pyzor server is busy.

3.2 Razor Configuration

Create a default .razor configuration under root home directory:

rm /etc/razor/razor-agent.conf
razor-admin -create
razor-admin -create

Razor v2 requires reporters to be registered so their reputations can be computed over time and they can participate in the revocation mechanism. Registration is done with razor-admin -register. When razor-admin -register is invoked as root, it negotiates a registration with the Nomination Server and writes the identity information in /root/.razor/identity-username. Manually invoked it in one of the following ways:

1. To register user:foo and password:s1kret (foo and s1kret are examples) :

razor-admin -register -user=foo -pass=s1kr3t

2. To register with an email address and have the password assigned:

razor-admin -register

3. To have both (random) username and password assigned:

razor-admin -register

I usually just do number 3. Make the following changes to /root/.razor/razor-agent.conf:

vi /root/.razor/razor-agent.conf

Change debuglevel = 3 to debuglevel = 0 (yes zero not “o”). This will prevent Razor from filling up your drive with debug information. Also we will move these configs someplace that the Postfix user can read them so add the razorhome line to the end of the file. Those two lines should look like this when done:

debuglevel             = 0
razorhome             = /var/lib/MailScanner/.razor/

We will test Razor later. man razor-agent.conf or go to for more information on Razor.

3.3 DCC Setup and Configuration

Install DCC:

apt-get install dcc-client

We are not running a DCC server, so we don’t need to waste time checking ourselves: If you are a large organization (100,000 messages per day), you should investigate running your own server.

Once the installation is done run:

cdcc “delete”
cdcc “delete Greylist”

Test our installation with:

cdcc info

You should get ‘requests ok’ from the servers.

4 MailScanner, ClamAV and SpamAssassin Configuration

4.1 MailScanner and ClamAV

Stop Postfix:

postfix stop

Install the packages:

apt-get install mailscanner clamav

Update ClamAV virus defenitions:


Let’s start with MailScanner. The MailScanner that was just installed from the repositories is a very old version so we will now remove it and install the MailScanner package from source

Download the tarball from … At the time of this writing it is at version 4.56.8-1 and the tarball link is Then install MailScanner using the script.

apt-get remove mailscanner
tar zxvf MailScanner-install-4.56.8-1.tar.gz
cd MailScanner-install-4.56.8

Ignore the message about the cron lines that we need to add to cron for now.

Once that is done, we need to make a directory for SpamAssassin in the spool and give postfix permissions to it, if you run sa-learn –force as root, bayes databese that is stored in these directories will change to root:root and spamassassin will error looking at the db. Just keep an eye on the mail.log and you’ll remember to change the permissions back. Also disable the MailScanner default configs:

mkdir /var/spool/MailScanner/spamassassin
mv /etc/MailScanner /etc/MailScanner.dist

Backup your MailScanner.conf file:

cp /opt/MailScanner/etc/MailScanner.conf /opt/MailScanner/etc/MailScanner.conf.back

Edit MailScanner.conf:

vi /opt/MailScanner/etc/MailScanner.conf

Change the following parameters in MailScanner.conf:

%org-name% = YOURDOMAIN-COM
%org-long-name% = Your Company Long Name INC
%web-site% =
Run As User = postfix
Run As Group = postfix
Incoming Queue Dir = /var/spool/postfix/hold
Outgoing Queue Dir = /var/spool/postfix/incoming
MTA = postfix
Virus Scanners = clamav
Spam Subject Text = [SPAM]
Send Notices = no
Required SpamAssassin Score = 6
High SpamAssassin Score = 10
Spam Actions = deliver striphtml
High Scoring Spam Actions = delete
Rebuild Bayes Every = 86400
Wait During Bayes Rebuild = yes
SpamAssassin User State Dir = /var/spool/MailScanner/spamassassin

The first 9 lines are basically required in order for everything to work, the rest are recommended. The MailScanner.conf is well documented so please read the notes there if you have any questions about the rest of the options we changed. Poke around this file from top to bottom.

Also take a look at the section “Removing/Logging dangerous or potentially offensive content” in the MailScanner.conf file. I had to disable most of these because clients were complaining about ‘{Disarmed}’ messages.

4.2 SpamAssassin

First we need to disable the default SpamAssassin configuration file:

mv /etc/spamassassin/ /etc/spamassassin/

Now lets backup the SpamAssassin configuration file in MailScanner then edit:

cp /opt/MailScanner/etc/spam.assassin.prefs.conf /opt/MailScanner/etc/spam.assassin.prefs.conf.back

vi /opt/MailScanner/etc/spam.assassin.prefs.conf

Add these two lines to the top of spam.assassin.prefs.conf:

pyzor_options --homedir /var/lib/MailScanner/
razor_config /var/lib/MailScanner/.razor/razor-agent.conf

Change where SpamAssassing looks for the Bayes database, comment out the default bayes_path or change it accordingly:

#bayes_path /var/lib/MailScanner/bayes
bayes_path /var/spool/MailScanner/spamassassin/bayes

Look for these lines and change them accordingly:

bayes_ignore_header X-YOURDOMAIN-COM-MailScanner
bayes_ignore_header X-YOURDOMAIN-COM-MailScanner-SpamCheck
bayes_ignore_header X-YOURDOMAIN-COM-MailScanner-SpamScore
bayes_ignore_header X-YOURDOMAIN-COM-MailScanner-Information

“YOURDOMAIN-COM” should be replaced with whatever you used for “%org-name%” in the MailScanner.conf file. Leave the “X-” in place.

Make sure that “bayes_auto_expire 0” is not commented out:

bayes_auto_expire 0

Mailscanner-MRTG Installation (Optional)

apt-get install mrtg apache2 snmp snmpd libsnmp4.2

Answer “Yes” to debconf MRTG screen.

rm /etc/cron.d/mrtg

We won’t need that one

First let’s backup our default SNMP config. Then create a new one with the correct settings.

mv /etc/snmp/snmpd.conf /etc/snmp/snmpd.conf.dist vi /etc/snmp/snmpd.conf


Paste this text into the new snmpd.conf file.

# This line makes the snmp daemon listen only on the loopback interface
# If you want to run on an alternative port change the 161 part
# (Don't forget to update the mailscanner-mrtg.conf file with the new port
agentaddress localhost:161
# Use this version instead for ucd-snmp
#agentaddress 161@localhost

# This line sets up a single community string (with read-only access)
# With access only permitted from localhost
# If you have users logging into your machine and want to make sure they
# can't easily get info from snmp change 'public' to something else
# (Don't forget to update the community string in mailscanner-mrtg.conf)
rocommunity  public localhost

# System Information, change this if you want
syslocation YourSysLocation
syscontact Support <>


/etc/init.d/snmpd restart


To install MailScanner-MRTG, we need to download then install it.

Go to and download the latest tarball. At the time of this writing it was 0.10.0

tar xzvf mailscanner-mrtg-0.10.00.tar.gz
cd mailscanner-mrtg-0.10.00


Edit the



Change a couple of options in this script. The following parameters should look like this:

"mrtgcfg" => "/etc/",
"www" => "/var/www/",
"wwwconf" => "/etc/apache2",


Run the



We have checked the configuration if so go ahead and install… When your editor with the mailscanner-mrtg config opens up in your editor, close it, we will edit it later:

Edit the /etc/MailScanner/mailscanner-mrtg.conf, change the following lines:

WWW Root = /var/www/
MTA = postfix
MTA Incoming Log = /var/log/mail.log
MTA Outgoing Log = /var/log/mail.log
MTA Reject Log = /var/log/mail.log
Incoming Queue Dir = /var/spool/postfix/hold/
Restart MailScanner = /usr/bin/check_mailscanner
MailScanner Log = /var/log/mail.log
Outgoing Queue Dir = /var/spool/postfix/deferred/:/var/spool/postfix/defer/
#MailScanner Work Directory = /var/spool/MailScanner/incoming #COMMENT THIS OUT UNLESS IT'S A MOUNTPOINT
Spool Directory = /var/spool #COMMENT THIS OUT UNLES IT'S A MOUNTPOINT
Use SNMP = yes

Lets try MailScanner-MRTG, run this two or three times. The first couple of times you should see some erros. If it keeps showing errors there’s something wrong:

env LANG=C /usr/bin/mrtg /etc/mailscanner-mrtg.cfg

Add this line into cron to run mrtg every 5 minutes:

crontab -e


0-59/5 * * * * env LANG=C /usr/bin/mrtg /etc/mailscanner-mrtg.cfg 1> /dev/null 2> /dev/null


/etc/init.d/cron restart


Ok go to and let’s see if it comes up and the graphs are updating. You should edit out any graphs that don’t work by editing the /var/www/mailscanner-mrtg/index.htm file and commenting those graphs out from /etc/mailscanner-mrtg.cfg.


Edit the SpamAssassin v310.pre to enable Razor and DCC

vi /etc/spamassassin/v310.pre


Uncomment the following lines:

loadplugin Mail::SpamAssassin::Plugin::DCC
loadplugin Mail::SpamAssassin::Plugin::Razor2


5 Bring it all Together

Copy over the Pyzor and Razor configs to someplace that the Postfix user will be able to read them:

cp -R /root/.pyzor /var/lib/MailScanner
cp -R /root/.razor /var/lib/MailScanner


Now that we have everything in there, set the correct permissions:

chown -R postfix.postfix /var/spool/MailScanner/
chown -R postfix.postfix /var/lib/MailScanner/


Let’s see if SpamAssassin is happy:

su postfix -p -c ‘spamassassin -x -D -C /opt/MailScanner/etc/spam.assassin.prefs.conf –lint’


You should see lines come up with DCC, Pyzor and Razor that say loading plugin and hopefully no errors.

NOTE: If your ever run the sa-learn, remember to run it like this, su postfix -p -c ‘sa-learn –sync –force-expire -C /opt/MailScanner/etc/spam.assassin.prefs.conf’ otherwise when SpamAssassin rebuilds the bayes databese it will not be able to read it.

If everything is looking dandy, continue, if not, troubleshoot and then continue.

Finishing up this part we need to add cron jobs that will clean/update/run Mailscanner, you probably saw the message about this after the MailScanner install script finished. The reason we do it now is because we don’t want MailScanner starting while we finish the SpamAssassin configuration.

crontab -e


Add these lines:

37      5 * * * /opt/MailScanner/bin/update_phishing_sites
58     23 * * * /opt/MailScanner/bin/clean.quarantine
42      * * * * /opt/MailScanner/bin/update_virus_scanners
3,23,43 * * * * /opt/MailScanner/bin/check_mailscanner


We need to add a line to rc.local so that MailScanner starts on a reboot:

vi /etc/rc.local


Before the “Exit 0” line add:



Might as well link the “check_mailscanner” file into the bin directory. This way you can run it whenever you need to restart MailScanner:

cd /usr/bin
ln -s /opt/MailScanner/bin/check_mailscanner check_mailscanner


Just to give you a clean log to look at and reboot:

rm /var/log/mail.log


If you see some errors on reboot when starting MailScanner about the Perl Sys/Hostname/ you need to install it like so:

perl -MCPAN -e shell


If it asks you to configure it now say no, unless you know what you’re doing. Now run the following to install the module:

install Sys::Hostname::Long

When that’s done type “quit” to go back to the console. Reboot or start MailScanner with the “check_mailscanner” script.

At this point you should have a fully functional spamfilter. Take a look at tail -f /var/log/mail.log, it should be pretty much clear of errors.

This README should come in handy for future use. Copy it to your spamfilter for a quick reference.

- Edit '/etc/postfix/relay_recipients', 'relay_domains' and 'transport'.
- Run 'postmap /etc/postfix/relay_recipients'. Same for 'relay_domains' and 'transport' after edit to add domains into db file.
- 'postfix reload' for postfix to read new db files.
- To add users to domains edit the 'relay_recipients' and 'postmap' it.
- Edit /etc/postfix/sender_access
- Run 'postmap /etc/postfix/sender_access'
- Run 'postfix reload'
- To edit MailScanner Settings "/opt/MailScanner/etc/MailScanner.conf"
- To edit spamassassin Settings "/opt/MailScanner/etc/spam.assassin.prefs.conf"
- To edit clamav Settings "/etc/clamav/clamd.conf"
- Run 'LINUX2' if postfix errors appear about files not matching in the jailroot.
- Run 'postfix check' to see if postfix is synched with jailroot.
- Run newaliases to refresh the /etc/postfix/aliases database if any changes are made on that file..
- 'mailq' and 'qshape' to check queue.
- 'check_mailscanner' to restart MailScanner.