Gentoo Wiki


In my adventures as a novice server administrator, I have had the worst trouble trying to setup a simple mail service where I work. So after reading through countless "HOWTOs" I was finally able to create a simple, working configuration. I decided to write this HOWTO for personal reference and public benefit.


A quick overview

Mail is not a simple protocol, there are thousands of rules and regulations to follow when defining your mail server's configuration. If a person deviates from the defined standards of SMTP then your likely to get your mail server blacklisted or exploited. That said, let me explain what SMTP is in detail. SMTP is a protocol, a protocol in which SMTP daemons listen and transmit over. To make it simple, the SMTP daemon sends and receives mail, delivering mail to a remote destination or to a local user. Postfix is an SMTP daemon and nothing more. POP3 and IMAP4 are the result of extra services which retrieve mail for users from wherever postfix drops it. It's important to realize this when configuring your mail server or there is a good chance you could go clinically insane.


While I like to keep things simple, there are a few conventions used here in this guide.

When copying/pasting text files, remember to replace sqldb, sqluser, sqlpass with their appropriate values (mysql authentication).

Also replace all instances of host, domain.tld, and host.domain.tld with their appropriate DNS values. host.domain.tld should always be the fqdn of your system and the composite of the before mentioned host and domain.tld.

Finally, the postfix UID/GID are used many times throughout this HOWTO. While these values are usually always the same (207:207), it is a good idea to make sure:

grep "postfix" /etc/passwd

Should output something like this:

postfix:x:207:207:added by portage for postfix:/var/spool/postfix:/sbin/nologin

Where 207:207 is the UID and GID of Postfix.

Setting up the environment

Before we install everything we have to configure our use flags to ensure support for all the features we will be using today.


# mail setup
mail-mta/postfix                mysql sasl vda
dev-libs/cyrus-sasl             authdaemond -berkdb
www-apps/postfixadmin           mysql

# www setup
dev-lang/php apache2 mysql mysqli pcre xml zlib gd sockets imap spell
dev-libs/apr urandom

Cyrus-SASL controls whether or not mail clients can authenticate, so its pretty important. PostfixAdmin is my manager of choice because of simplicity, if you want to use something else you will have to modify the following Postfix/Courier-Authlib configurations to use whatever database format you had in mind. PHP has a few extra use flags included but only because they are basic functional use-flags. The APR flags are modified due to a bug in Apache2 which causes slowdowns when it runs out of random numbers.

Make sure your FQDN and hostname are set correctly or strange things may happen: FQDN

Installing packages

When emerging applications I like to make the dependencies install my desired applications. Its a bit strange at first but after awhile you get used to the idea of a clean world file and orderly package installs. Note: The ebuild at this time of writing could not fetch the postfixadmin archive, so I had to fetch it myself from:

emerge postfix php postfixadmin amavisd-new clamav spamassassin dcc

This should draw in everything we need to setup a working mail server, including POP3 and IMAP. After the merge completes setup mysql and install the postfixadmin database. Due to improper conformity to standards we must first modify the SQL dump before using it.

Comment out the entire top portion of /usr/share/webapps/postfixadmin/2.1.0/sqlscripts/mysql/2.1.0_create.sql starting from 'USE mysql;' and ending with 'USE postfix;'.

#USE mysql;
# ...
#USE postfix;
mysql -u root -p < /usr/share/webapps/postfixadmin/2.1.0/sqlscripts/mysql/2.1.0_create.sql

Configuring the mail server

Great! We've made it this far... now comes the hard part. We need to configure postfix to use PostfixAdmin's mapping tables and authenticate with it's crypt'd tables using courier-authlib (drawn in from the postfix ebuild). The first step is configuring Postfix's file (the heart of the Postfix configuration).


myhostname = host
mydomain = domain.tld
inet_interfaces = all
mydestination = $myhostname.$mydomain
mynetworks =

smtpd_sasl_auth_enable = yes
smtpd_sasl2_auth_enable = yes
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
smtpd_sasl_local_domain =

smtpd_recipient_restrictions =

virtual_mailbox_base = /var/vmail/
virtual_uid_maps = static:207
virtual_minimum_uid = 207
virtual_gid_maps = static:207
virtual_transport = virtual
virtual_alias_maps = proxy:mysql:/etc/postfix/mysql/
virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql/
virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql/
virtual_mailbox_limit_maps = mysql:/etc/postfix/mysql/
virtual_create_maildirsize = yes
virtual_mailbox_extended = yes
virtual_mailbox_limit_override = yes
virtual_maildir_limit_message = Sorry, the user's maildir has overdrawn his diskspace quota, please try again later.
virtual_overquota_bounce = yes

Now create the four mysql mapping files under /etc/postfix/mysql.


mkdir /etc/postfix/mysql


hosts = localhost
dbname = sqldb
user = sqluser
password = sqlpass
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'


hosts = localhost
dbname = sqldb
user = sqluser
password = sqlpass
query = SELECT description FROM domain WHERE domain='%s' AND active = '1'


hosts = localhost
dbname = sqldb
user = sqluser
password = sqlpass
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'


hosts = localhost
dbname = sqldb
user = sqluser
password = sqlpass
query = SELECT quota FROM mailbox WHERE username='%s' AND active = '1'


pwcheck_method: authdaemond
log_level: 3
mech_list: PLAIN LOGIN

Update 'authdaemonrc' so that the 'authmodulelist' line looks as shown:




MYSQL_SERVER        localhost
MYSQL_USERNAME      sqluser
MYSQL_PASSWORD      sqlpass


MYSQL_HOME_FIELD    '/var/vmail'
MYSQL_OPT           0


use strict;

$MYHOME = '/var/amavis';   # (default is '/var/amavis'), -H
$mydomain = 'domain.tld';      # (no useful default)
$myhostname = 'host.domain.tld';  # fqdn of this host, default by uname(3)

$daemon_user  = 'amavis';   # (no default;  customary: vscan or amavis), -u
$daemon_group = 'amavis';   # (no default;  customary: vscan or amavis), -g
$TEMPBASE = "$MYHOME/tmp";     # prefer to keep home dir /var/amavis clean?
$ENV{TMPDIR} = $TEMPBASE; # used for SA temporary files, by some decoders, etc.

$enable_db = 1;              # enable use of BerkeleyDB/libdb (SNMP and nanny)
$enable_global_cache = 1;    # enable use of libdb-based cache if $enable_db=1

$max_servers  =  8;   # number of pre-forked children          (default 2), -m
$max_requests = 20;   # retire a child after that many accepts (default 20)
$child_timeout=5*60;  # abort child if it does not complete its processing in
$smtpd_timeout = 120; # disconnect session if client is idle for too long

$unix_socketname = "$MYHOME/amavisd.sock"; # amavis helper protocol socket
$inet_socket_port = 10024;        # accept SMTP on this local TCP port
@inet_acl = qw( [::1]);  # allow SMTP access only from localhost IP
@local_domains_maps = ( [".$mydomain"] );  # $mydomain and its subdomains

$DO_SYSLOG = 1;                   # (defaults to 0)
$syslog_ident = 'amavis';     # Syslog ident string (defaults to 'amavis')
$syslog_facility = 'mail';    # Syslog facility as a string
$syslog_priority = 'debug';   # Syslog base (minimal) priority as a string,
$LOGFILE = "/var/log/amavis.log";  # (defaults to empty, no log)
$log_level = 2;            # (defaults to 0), -d
$log_recip_templ = undef;  # undef disables by-recipient level-0 log entries

%final_destiny_by_ccat = (
  CC_BADH,       D_PASS,
  CC_CLEAN,      D_PASS,

$remove_existing_x_scanned_headers = 1; # leave existing X-Virus-Scanned alone
$remove_existing_spam_headers  = 1;     # remove existing spam headers if

$MAXLEVELS = 14;                # (default is undef, no limit)
$MAXFILES = 1500;               # (default is undef, no limit)
$MIN_EXPANSION_QUOTA =      100*1024;  # bytes  (default undef, not enforced)
$MAX_EXPANSION_QUOTA = 300*1024*1024;  # bytes  (default undef, not enforced)
$MIN_EXPANSION_FACTOR =   5;  # times original mail size  (default is 5)
$MAX_EXPANSION_FACTOR = 500;  # times original mail size  (default is 500)

$virus_check_negative_ttl=  3*60; # time to remember that mail was not infected
$virus_check_positive_ttl= 30*60; # time to remember that mail was infected
$spam_check_negative_ttl = 10*60; # time to remember that mail was not spam
$spam_check_positive_ttl = 30*60; # time to remember that mail was spam

$sa_local_tests_only = 0;   # only tests which do not require internet access?
$sa_mail_body_size_limit = 400*1024; # don't waste time on SA if mail is larger
$sa_tag_level_deflt  = 2.0; # add spam info headers if at, or above that level;
$sa_tag2_level_deflt = 6.31;# add 'spam detected' headers at that level to
$sa_kill_level_deflt = $sa_tag2_level_deflt; # triggers spam evasive actions

$QUARANTINEDIR = "$MYHOME/quarantine";
$path = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin:/usr/bin:/bin:/opt/bin';
$file   = 'file';   # file(1) utility; use 3.41 or later to avoid vulnerability

@decoders = (
  ['mail', \&do_mime_decode],
  ['asc',  \&do_ascii],
  ['uue',  \&do_ascii],
  ['hqx',  \&do_ascii],
  ['ync',  \&do_ascii],
  ['F',    \&do_uncompress, ['unfreeze','freeze -d','melt','fcat'] ],
  ['Z',    \&do_uncompress, ['uncompress','gzip -d','zcat'] ],
  ['gz',   \&do_uncompress,  'gzip -d'],
  ['gz',   \&do_gunzip],
  ['bz2',  \&do_uncompress,  'bzip2 -d'],
  ['lzo',  \&do_uncompress,  'lzop -d'],
  ['rpm',  \&do_uncompress, ['','rpm2cpio'] ],
  ['cpio', \&do_pax_cpio,   ['pax','gcpio','cpio'] ],
  ['tar',  \&do_pax_cpio,   ['pax','gcpio','cpio'] ],
  ['deb',  \&do_ar,          'ar'],
  ['zip',  \&do_unzip],
  ['7z',   \&do_7zip,       ['7zr','7za','7z'] ],
  ['rar',  \&do_unrar,      ['rar','unrar'] ],
  ['arj',  \&do_unarj,      ['arj','unarj'] ],
  ['arc',  \&do_arc,        ['nomarch','arc'] ],
  ['zoo',  \&do_zoo,        ['zoo','unzoo'] ],
  ['lha',  \&do_lha,         'lha'],
  ['cab',  \&do_cabextract,  'cabextract'],
  ['tnef', \&do_tnef_ext,    'tnef'],
  ['tnef', \&do_tnef],
  ['exe',  \&do_executable, ['rar','unrar'], 'lha', ['arj','unarj'] ],

@av_scanners = (
  \&ask_daemon, ["CONTSCAN {}\n", "/var/run/clamav/clamd.sock"],
  qr/\bOK$/, qr/\bFOUND$/,
  qr/^.*?: (?!Infected Archive)(.*) FOUND$/ ],

@av_scanners_backup = (
  ['ClamAV-clamscan', 'clamscan',
    "--stdout --no-summary -r --tempdir=$TEMPBASE {}",
    [0], qr/:.*\sFOUND$/, qr/^.*?: (?!Infected Archive)(.*) FOUND$/ ],

1;  # insure a defined return
Retrieved from ""

Last modified: Thu, 02 Oct 2008 22:09:00 +0000 Hits: 2,406