Debian Virtual Mail Server


This article describes how to set up a mail server. It does not handle spam or virus filtering, as we want to use a proxy mail server, ASSP, to remove some of the load from the mail server. Instructions on how to install the proxy server are in the article Creating a Proxy Server with ASSP

I wanted a virtual mail server that is only that; a mail server. The requirements I had were:

  • easily updatable antispam and anti-virus
  • A web based UI for adding/removing users and domains
  • Ability to assign an "administrator" for each domain
  • Ability for users to modify their own accounts
  • A web based e-mail reader
  • Statistics on the mail server
  • imap and pop3
  • runs on a Xen virtual machine
  • all "important" data stored in /home for ease of backup. /home is a separate partition so it can grow

To do this, I chose postfix, postfixadmin, courier, squirrelmail, pflogsumm, spamassassin,

This "How to" is designed as a recipe howto. When I have followed howto's in the past, I have one irritant in that I feel I am constantly modifying what the user says to do. Thus, I'm trying something new.

Every place where you must insert a value, I mark the value to be replaced with a special string, and put it in italics. Thus, you can do a search/replace in many cases. Also, I have tried to set it up so that you can simply copy/paste code directly from the howto into your shell prompt and execute it.

Placeholders are defined with a leading dollar sign, followed by a string. For those of you familiar with the bash shell, you will see that this is exactly what you need to create environmental variables for your script. Thus, if your postfix install results in a uid of 207, executing the following command would place that value into the environmental variable $postfix_uid

export postfix_uid=207

Then, if you execute the command

chown -fRv $postfix_uid /home/vmail

it will substitute $postfix_uid with the correct value.

At the beginning of each section, I will give these strings for you.

Ok, lets get on with it. This document is sparse on "why" and heavy on "what". For the "why", I used the links at the end of this document. Very well written articles, in most cases, and definitely worth the effort to read.

Create the virtual

This assumes you have xen-tools installed. If you do not, install it as per this article, or simply build a server based on your standards. It also assumes Xen uses LVM to store its images and, I create a separate /home partition since that is the way

Create the virtual image

export mail_hostname=your hostname here
export mail_ip=ip of new machine here
export mail_home_size=initial size (in gigabytes) of home directory
export mail_virtual_root=LVM Volume Group where home should be created from
xen-create-image --hostname $mail_hostname --ip $mail_ip
lvcreate -L $mail_home_size G -n $mail_hostname-home $mail_virtual_root
# WARNING, verify the following command before executing it
mke2fs -jm 0 -L $mail_hostname-home /dev/$mail_virtual_root/$mail_hostname-home
# WARNING, verify the following command before executing it
mount /dev/$mail_virtual_root/$mail_hostname-disk /mnt
# WARNING, after this command, verify /mnt/etc/fstab has the line added, and no line breaks are necessary
cat /dev/sda3     /home ext3     defaults              0     2 >> /mnt/etc/fstab
umount /mnt
# xen tools does not create the user directories when it creates a new virtuals, though it does copy user information 
# I just copy them from /home to the new volume for home
mount /dev/$mail_virtual_root/$mail_hostname-home /mnt
cp -av /home/* /mnt/
umount /mnt

Modify the host file to export the partion we created for /home

joe /etc/xen/$mail_hostname.cfg

Find and modify the line beginning with the word "disk = ". Just before the closing square bracket (]), insert the following. You will need to manually substitute the value for the variables. Include the leading comma.

, 'phy:$mail_virtual_root/$mail_hostname-home,sda3,w'

you might also check the values for memory, name, and IP vif to modify them as you like. Followning are some suggestions:

# SUGGESTION ONLY. This makes xm list and xm top prettier
name = 'some short name'
# SUGGESTION ONLY. This makes ifconfig reports easier, and gives a static mac address. On Mac, just come up with a random group of 4 hex numbers
vif  = ['ip=ip address of machine,vifname=short name from above0,mac=00:16:3e:df:xx:xx']

Start up new image

We are now ready to start the virtual. We will use the -c option so we can watch the console come up.

xm create /etc/xen/$mail_hostname.cfg -c

login as root, no password and immediately put a password on root:


Install basic packages

Now, lets get some very basic packages. As an old wordstar user (yes, I'm ancient), I like joe. We also need ssh, less and sudo as far as I'm concerned. I also include locales-all as something about xen-tools fails to set up the locale sometimes.

apt-get update
apt-get upgrade
apt-get install joe ssh less sudo locales-all lsb bzip2 zip

If you get an error about locales, use the following command to set your default locale.

dpkg-reconfigure locales

Now, secure your ssh server by editing

joe /etc/ssh/sshd_config

Edit the following lines. This assumes certificate based ssh, and that the certificate for your users were copied into their respective home directories.

Port xxxxx    # use something weird. you have from 1024 to 65535. Be inventive
PermitRootLogin no
PasswordAuthentication no
X11Forwarding no
AllowUsers list of users who can log in via ssh # if you want

Restart ssh server

/etc/init.d/ssh reload

While still logged in, open another terminal and attempt a login via ssh. Fix any errors that occur before you proceed.

You can now close out of the console window from your virtual host. To do this, perform a logout, then press ctrl-] (hold the control key, then press the left square bracket). This returns you to the DOM0 screen, and you can safely exit that now. All future work assumes you are logged in via an ssh session.

Begin package installation

Now, lets install some packages. Most of these are taken from the howtoforge article, and you may not need them all. I have set them up so as many packages as possible are installed at one time.

apt-get install libhtml-parser-perl libdb-file-lock-perl libnet-dns-perl libpam-mysql metamail
apt-get install binutils cpp fetchmail flex gcc libarchive-zip-perl libc6-dev libcompress-zlib-perl libdb4.3-dev libpcre3 libpopt-dev linux-kernel-headers lynx m4 make ncftp nmap openssl perl perl-modules unzip zip zlib1g-dev autoconf automake1.9 libtool bison autotools-dev g++ fam libfam0
apt-get install mysql-server mysql-client libmysqlclient15-dev postfix postfix-mysql libsasl2 sasl2-bin libsasl2-modules libdb3-util procmail courier-authdaemon courier-authlib-mysql courier-base courier-imap courier-imap-ssl courier-pop courier-pop-ssl courier-ssl libglib2.0-0
Create directories for web based administration -> no
General Type of Installation -> Internet Site
Mail Name -> name of this server visible to outside e-mail world
apt-get install apache2 apache2-doc apache2-mpm-prefork apache2-utils libexpat1 ssl-cert libapache2-mod-php5 php5 php5-common php5-curl php5-dev php5-gd php5-idn php-pear php5-imagick php5-imap php5-json php5-mcrypt php5-memcache php5-mhash php5-ming php5-mysql php5-ps php5-pspell php5-recode php5-sqlite php5-tidy php5-xmlrpc php5-xsl squirrelmail

Continue without maildir support -> yes

apt-get install squirrelmail-locales squirrelmail-decode libemail-valid-perl libmail-sendmail-perl libdate-calc-perl

We need the Mime::EncWords package

apt-get install libmime-encwords-perl

Configure Packages

There are some housekeeping tasks that must be done. Most of this is taken from the "Perfect Debian Setup", with some additions/modifications by me.

First, get rid of some junk:

 mv /etc/cron.daily/lprng /etc/cron.daily/lprng.disabled #this is part of lsb, but it causes an error message everytime cron.daily is run

MySQL root passwords

  mysqladmin -u root password yourrootsqlpassword
  mysqladmin -h ´hostname´ -u root password yourrootsqlpassword


joe /etc/apache2/mods-available/dir.conf # replace DirectoryIndex line with following
  DirectoryIndex index.html index.htm index.shtml index.cgi index.php index.php3 index.xhtml
joe /etc/apache2/ports.conf # add following if it does not exist
  Listen 443
# Enable some apache modules
a2enmod ssl
a2enmod rewrite
a2enmod suexec
a2enmod include
# restart apache
/etc/init.d/apache2 force-reload

Secure PHP

joe /etc/php5/apache2/php.ini
# Search for register_globals and ensure it is off


# Create an alias for root by editing /etc/aliases, and adding the following line
joe /etc/aliases # add/modify the following line. This should be an e-mail account, or name of local user
  root:  someUserName
newaliases # update alias hash
# Set postfix to use maildir
postconf -e 'home_mailbox = Maildir/'
postconf -e 'mailbox_command ='
postconf -e 'smtpd_sasl_local_domain ='
postconf -e 'smtpd_sasl_auth_enable = yes'
postconf -e 'smtpd_sasl_security_options = noanonymous'
postconf -e 'broken_sasl_auth_clients = yes'
postconf -e 'smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination'
postconf -e 'inet_interfaces = all'
# Create the TLS certificate
mkdir /etc/postfix/ssl
cd /etc/postfix/ssl/
# the following are interactive commands. You will be asked for a password in the first command, then will need to repeat
# that password for all subsequent commands.
openssl genrsa -des3 -rand /etc/hosts -out smtpd.key 1024
chmod 600 smtpd.key
openssl req -new -key smtpd.key -out smtpd.csr
# you will need the password from above to open the file. Fill out all items, but you can leave the challenge password alone
openssl x509 -req -days 3650 -in smtpd.csr -signkey smtpd.key -out smtpd.crt
openssl rsa -in smtpd.key -out smtpd.key.unencrypted
mv -f smtpd.key.unencrypted smtpd.key
# This generates a new certificate, with a new password, so you get to fill out the certificate form again
openssl req -new -x509 -extensions v3_ca -keyout cakey.pem -out cacert.pem -days 3650
# end of interactive commands

# Configure Postfix for TLS
postconf -e 'smtpd_tls_auth_only = no'
postconf -e 'smtp_use_tls = yes'
postconf -e 'smtpd_use_tls = yes'
postconf -e 'smtp_tls_note_starttls_offer = yes'
postconf -e 'smtpd_tls_key_file = /etc/postfix/ssl/smtpd.key'
postconf -e 'smtpd_tls_cert_file = /etc/postfix/ssl/smtpd.crt'
postconf -e 'smtpd_tls_CAfile = /etc/postfix/ssl/cacert.pem'
postconf -e 'smtpd_tls_loglevel = 1'
postconf -e 'smtpd_tls_received_header = yes'
postconf -e 'smtpd_tls_session_cache_timeout = 3600s'
postconf -e 'tls_random_source = dev:/dev/urandom'
postconf -e 'myhostname ='
# Make the directory for the chroot jail
mkdir -p /var/spool/postfix/var/run/saslauthd
joe /etc/default/saslauthd # to activate sasl. Make to following changes
  OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd -r"

Create the file /etc/postfix/sasl/smtpd.comf with the following contents:

log_level: 5
pwcheck_method: saslauthd
mech_list: plain login
allow_plaintext: true
auxprop_plugin: mysql
sql_user: postfix
sql_passwd: your database password
sql_database: postfix
sql_select: select password from mailbox where username = '%u'

Save the file, then restart sasl and postfix:

/etc/init.d/postfix restart
/etc/init.d/saslauthd start

I'm not sure this is necessary:

echo "auth    required user=postfix passwd=JJ53!f1 host= db=postfix table=mailbox usercolumn=username passwdcolumn=password crypt=1" >> /etc/pam.d/smtp
echo "account sufficient user=postfix passwd=JJ53!f1 host= db=postfix table=mailbox usercolumn=username passwdcolumn=password crypt=1" >> /etc/pam.d/smtp

Your postfix now has a preliminary setup. Verify by telneting to the server

telnet localhost 25
   ehlo localhost

If you see the two lines:


Then everything is good. Type


to exit.


ln -s /etc/squirrelmail/apache.conf /etc/apache2/conf.d/squirrelmail.conf
/etc/init.d/apache2 reload
# Create database for addresses and preferences
mysql -p mysql # simply copy/paste the following SQL, but change the passwords
  create database squirrelmail;
  GRANT select,insert,update,delete ON squirrelmail.* TO squirreluser@localhost IDENTIFIED BY 'password';
  connect squirrelmail;
     CREATE TABLE address (
     owner varchar(128) DEFAULT '' NOT NULL,
     nickname varchar(16) DEFAULT '' NOT NULL,
     firstname varchar(128) DEFAULT '' NOT NULL,
     lastname varchar(128) DEFAULT '' NOT NULL,
     email varchar(128) DEFAULT '' NOT NULL,
     label varchar(255),
     PRIMARY KEY (owner,nickname),
     KEY firstname (firstname,lastname)
  CREATE TABLE userprefs (
     user varchar(128) DEFAULT '' NOT NULL,
     prefkey varchar(64) DEFAULT '' NOT NULL,
     prefval BLOB NOT NULL,
     PRIMARY KEY (user,prefkey)
cd /etc/squirrelmail
./ # this is an interactive command

The squirrelmail configurator is text mode menu based. You enter the letter of a menu option, and press enter. If additional data is required, a location to insert that data is opened. In the following, I list the menu options. You simply press the first letter of each option in turn.

  D.  Set pre-defined settings for specific IMAP servers
     Choose courier
  1. Organization Preferences
     1.  Organization Name == put your company name here
     5.  Signout Page == I set this to /index.html
     R   Return to Main Menu
  2  Server Settings
     A.  Update IMAP Settings
     8.  Server software == courier
     R   Return to Main Menu
  4.  General Options
     10. Allow server thread sort
     11. Allow server-side sorting == true
     R   Return to Main Menu
  9.  Database
     1.  DSN for Address Book == mysql://squirreluser:sqpassword@localhost/squirrelmail
     3.  DSN for Preferences == mysql://squirreluser:sqpassword@localhost/squirrelmail
     8.  DSN for Global Address Book == mysql://squirreluser:sqpassword@localhost/squirrelmail
     9.  Table for Global Address Book = address  *** you can not have a user named "global"
     11. Allow listing of Global Address Book
     R   Return to Main Menu
  S   Save data
  Q   Quit

Edit /etc/squirrelmail/apache.conf and comment out the following lines:

joe /etc/squirrelmail/apache.conf # comment out the following block temporarily
   #  <Files configtest.php>
   #    order deny,allow
   #    deny from all
   #    allow from
   #  </Files>
# Restart Apache
/etc/init.d/apache2 reload

Open a web browser and point it to:


And verify the configuration is correct.

joe /etc/squirrelmail/apache.conf # uncomment the block above, then restart apache
   <Files configtest.php>
       order deny,allow
       deny from all
       allow from
# Restart Apache
/etc/init.d/apache2 reload

Postfix Admin

Postifix Admin is a great program that allows you to administer your server with virtual domains. It is not a standard debian package, but it has a debian package that can be downloaded and installed

Download and install postfixadmin

cd /usr/src
# Download from sourceforge. Check for a later version
dpkg -i postfixadmin_2.2.1.1_all.deb # install 

Configure postfixadmin

mysql # we need to create a database for postfixadmin
  create database postfix;
  grant all privileges on postfix.* to postfix@localhost identified by 'password';
joe /etc/postfixadmin/ # make the following changes
     SEARCH AND REPLACE change-this-to-your.domain.tld WITH
     $CONF ['configured'] = true;
     $CONF['postfix_admin_url'] = 'http://mailserver host name/postfixadmin';
     $CONF['database_type'] = 'mysqli'; // note the 'i' at the end
     $CONF['database_password'] = 'your password';
     $CONF['admin_email'] = 'postmaster@domain_name';
     $CONF['domain_path'] = 'YES';
     $CONF['vacation'] = 'YES';
     $CONF['welcome_text'] = <<<EOM
     ''change the text here for a new users welcome message
     Welcome to your new account.''
     $CONF['vacation'] = 'YES';
     $CONF['vacation_domain'] = ''; // domain does not have to be real
     $CONF['vacation_control'] ='YES';
     $CONF['vacation_control_admin'] = 'YES';
  /etc/init.d/apache2 reload # restart apache

Open a web browser and go to

  http://mail server url/postfixadmin/setup.php

If you set everything up correctly, it will create your database, and request and admin e-mail account and password. Remember these! Use an e-mail account for the admin name.

rm /usr/share/postfixadmin/setup.php # this is a security risk if you leave it laying around

Open a web browser and go to

http://mail server url/postfixadmin/

And verify that it works. Do Not make any changes at this time.

Configure postfix for postfixadmin

Now, we need to figure out what the User ID (uid) and Group ID (gid) of postfix are. Issue the following command

grep postfix /etc/passwd
# make note of the first (uid) and second (gid) numbers
# example: postfix:x:104:105::/var/spool/postfix:/bin/false
# the uid is 104, the gid is 105 (they may be the same)
export postfix_uid=uid from above # no spaces except between export and postfix_uid
export postfix_gid=gid from above # no spaces excetp between export and postfix_gid

mkdir /home/vmail
chown -fRv $postfix_uid:$postfix_gid /home/vmail
postconf -e "mydomain = ´hostname -d´"
postconf -e "mydestination = ´hostname -f´, localhost.´hostname -d´, localhost"
postconf -e "mynetworks_style = subnet"
postconf -e "smtpd_sasl_authenticated_header = yes"
postconf -e "virtual_alias_maps = proxy:mysql:/etc/postfix/"
postconf -e "virtual_gid_maps = static:$postfix_gid"
postconf -e "virtual_mailbox_base = /home/vmail/"
postconf -e "virtual_mailbox_domains = proxy:mysql:/etc/postfix/"
postconf -e "virtual_mailbox_limit = 112400000"
postconf -e "virtual_mailbox_maps = proxy:mysql:/etc/postfix/"
postconf -e "virtual_minimum_uid = $postfix_uid"
postconf -e "virtual_transport = virtual"
postconf -e "virtual_uid_maps = static:$postfix_uid"
postconf -e "smtpd_recipient_restrictions = reject_unknown_recipient_domain,permit_mynetworks,permit_sasl_authenticated,reject_unauth_destination,permit"
postconf -e "home_mailbox ="
joe /etc/postfix/ # new file
  user = postfix
  password = mysql password for postfix user
  hosts = localhost
  dbname = postfix
  query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
joe /etc/postfix/ # new file
  user = postfix
  password = mysql password for postfix user
  hosts = localhost
  dbname = postfix
  query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'
joe /etc/postfix/ # new file
  user = postfix
  password = mysql password for postfix user
  hosts = localhost
  dbname = postfix
  query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
joe /etc/postfix/mime_header_checks.regexp # (may need to adjust) (one line)
  /^\s*Content-(Disposition|Type).*name\s*=\s*"?(.+\.(ad[ep]|asd|ba[st]|c[ho]m|cmd|cpl|crt|dbx|dll|exe|hlp|hta|in[fs]|isp|js|jse|lnk|md[etw]|ms[cipt]|nws|ocx|ops|pcd|pi|pif|prf|reg|scf|scr|sct|sh[bms]|uue|vb|vb[esx]|vxd|wab|ws[cfh]))"?\s*$/ REJECT Files attached to emails that contain or end in "$3" are prohibited on this server as they may contain viruses. The file named "$2" was rejected.
/etc/init.d/postfix reload
tail /var/log/mail.log # look for errors

Configure Vacation

# Create a user and group, with no rights, no home dir, and no login. I just manually add them 
echo vacation:*:65501:65501::0:0:Virtual Vacation:/nonexistent:/sbin/nologin >> /etc/passwd
echo  vacation:*:65501: >> /etc/group
# Now, create a directory for the spool files. This will be owned only by the vacation users:
mkdir /var/spool/vacation
# And copy the vacation script to it
cp /usr/share/doc/postfixadmin/VIRTUAL_VACATION/ /var/spool/vacation/
gunzip /var/spool/vacation/
# Change ownerships so only the vacation user can do anything with it.
chown -R vacation:vacation /var/spool/vacation
chmod -R 700 /var/spool/vacation
joe /etc/postfix/ # Add this to the bottom of the file (two lines)
   vacation    unix  -       n       n       -       -       pipe
     flags=Rq user=vacation argv=/var/spool/vacation/ -f ${sender} -- ${recipient}
# update to use this transport
postconf -e 'transport_maps = hash:/etc/postfix/transport'
# Create a new file, /etc/postfix/transport with the following line
echo        vacation: > /etc/postfix/transport 
# Now, add this entry to your /etc/hosts file to ensure no dns issues
echo        autoreply >> /etc/hosts
# Tell Postfix to create the transports hash and to reload its configuration:
postmap /etc/postfix/transport
/etc/init.d/postfix reload
joe  /var/spool/
   Change any variables starting with $db_ or $db_type to mysql
   my $db_type = 'mysql';
   my $db_host = 'localhost';
   my $db_username = 'postfix';
   my $db_password = \'database password here\';
   my $db_name     = 'postfix';
=== IMAP setup ===
joe /etc/courier/authmysqlrc
  MYSQL_USERNAME          postfix
  MYSQL_PASSWORD          database password
  MYSQL_DATABASE          postfix
  MYSQL_USER_TABLE        mailbox
  MYSQL_CRYPT_PWFIELD     password
  MYSQL_UID_FIELD         \'UID of Postfix\'
  MYSQL_GID_FIELD         \'GID of Postfix\'
  MYSQL_LOGIN_FIELD       username
  MYSQL_HOME_FIELD        '/home/vmail'
  MYSQL_MAILDIR_FIELD     maildir  # uncomment
  MYSQL_NAME_FIELD        name
  # You can also turn off IMAP authentication when a user is
  # marked as inactive. Be aware that this will cause webmail auth,
  # smtp relay if slaved off courier-authlib, and POP3/IMAP logins to fail.
  #MYSQL_WHERE_CLAUSE     active='1'
joe /etc/courier/authdaemonrc # Change:
  authmodulelist="authmysql authpam" # be sure authmysql is first

Additional Utilities

Postfix Log Summary

I found this cute little perl script named pflogsumm that does a very nice job of giving postfix log summaries.

cd /usr/src
tar -xzvf pflogsumm-1.1.1.tar.gz
cp /usr/src/pflogsumm-1.1.1/ /usr/local/bin/pflogsumm
chown bin:bin /usr/local/bin/pflogsumm
chmod 755 /usr/local/bin/pflogsumm
cp /usr/src/pflogsumm-1.1.1/pflogsumm.1 /usr/share/man/man1/pflogsumm.1
chown bin:bin /usr/share/man/man1/pflogsumm.1
chmod 644 /usr/share/man/man1/pflogsumm.1

Set up a schedule. Choose this, if you want to run it out of cron.d (giving you fine control on when it runs)

echo '# /etc/cron.d/pfloggsumm' > /etc/cron.d/pflogsumm
echo '10 6 * * * /usr/local/bin/pflogsumm /var/log/mail.log.0 2>&1 |/usr/bin/mailx -s "´uname -n´ daily mail stats" postmaster' >> /etc/cron.d/pflogsumm
chmod 644 /etc/cron.d/pflogsumm

Choose this option to have the job run with the normal daily jobs. Since syslog's job runs after this (they are done in lexical order), this will always show the previous day's work (assuming syslog rotates mail.log daily)

echo '#! /bin/bash' > /etc/cron.daily/pflogsumm
echo '/usr/local/bin/pflogsumm /var/log/mail.log.0 2>&1 |/usr/bin/mailx -s "´uname -n´ daily mail stats" postmaster' >> /etc/cron.daily/pflogsumm
chmod 755 /etc/cron.daily/pflogsumm

Copy e-mail from remote server using imapsync

apt-get install imapsync
imapsync --dry --host1  --user1 example.org_user --password1 'xxxxxxx' \
                --host2 localhost --user2   --password2 'xxxxxxx'


  1. setting up virtual domains under postfix

  1. setting up maildrop for pre-delivery filtering

Last update:
2012-02-19 23:01
Average rating:0 (0 Votes)

You cannot comment on this entry

Chuck Norris has counted to infinity. Twice.