Running a CentOS server with Lighttpd, PHP, MySQL, Exim with virtual users, Dovecot, and Squirrelmail

This is a tutorial how to setup a server for mail and web.

It is based on the following components:
OS: Linux CentOS 5.2
HTTP Server: Lighttpd 1.4.19, PHP 5.1.6
Database: MySQL 5.045
Mailserver: Exim 4.63 with Vexim 2.2.1, Spamassassin 3.2.4, Clamav 0.94
IMAP/Pop3 Server: 1.07

This tutorial has been written during setup of a new virtual server. In the beginning some basic tasks and some security stuff will be configured before installing and configuring the main applications on the server.

After initial setup of a minimal server login as root with the provided password and change it immediately:


Next step is to check the current date and timezone of the server:

date --utc

If your timezone and/or date is not what it should be you need to change it:

rm /etc/localtime
ln -s /usr/share/zoneinfo/Europe/Berlin /etc/localtime
date -s 101421422008 (sets time+date to 21:42:00 Oct 14 2008)

As fsck during reboot always comes inconvenient I normally disable it and force it manually from time to time:

tune2fs -i 0 -c 0 /dev/hda1

Let's start now to install the latest fixes:

yum update

If your favourite editor is missing you need to install it:

yum install vim-minimal

For security reason we don't want to work as root directly but create a normal user who is using sudo for running system commands with root privileges.

yum install sudo
adduser -m dracula
passwd dracula

Now check login with user dracula. In case everything is working as expected we need to enable user dracula to run sudo. visduo is the only command that should be used to edit /etc/sudoers:


This will open /etc/sudoers in safe mode which checks for correct syntax when leaving the editor.
Add this statement at the end of the file:

dracula ALL=(ALL) ALL

If you want to increase the timeout for retyping the passwords when using sudo command you need to add this statement to the Default section:

Defaults timestamp_timeout = 15 (the default is 5 minutes, this will increase the timeout to 15 minutes)

Try to run a sudo command as user dracula:

sudo date

When prompted for a password you need to enter draculas password.

Now let's continue to harden ssh against unwelcome guests.

cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
vi /etc/ssh/sshd_config
PermitRootLogin no
service sshd restart

(before restarting sshd make sure you have an open session or you are still able to open a console terminal)
If you now try to connect as root via SSH your login will be refused. To make login more secure login with authorized key is recommended. Login as user dracula and create the directory .ssh and the file .ssh/authorized_keys:

mkdir .ssh
vi .ssh/authorized_keys

The private and public key can be generated with PuTTYgen if you are running a windows PC. Copy the public key in the .ssh/authorized_keys file.
Access to the file and the directory needs to be restricted to the owning user.

chmod 600 .ssh/authorized_keys
chmod 700 .ssh

Check to login with the private key to the server as user dracula. Do not continue with changing /etc/ssh/sshd_config as this will disable SSH for you. The following lines need to be changed/uncommented:

PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
UsePAM no

The following statement needs to be added to /etc/ssh/sshd_config:

AllowUsers dracula

After you have made these changes just restart sshd again

service sshd restart

Keep you current session open and try to login again as dracula using the private key - unless you lose the private key nobody else can login via ssh to the server.

Next step is to setup priorities for yum repoistories and an additional repository from rpmforge.

yum install yum-priorities

In the file /etc/yum.repos.d/CentOS-Base.repo the following needs to added to the sections [base], [addons], [updates], [extras]:


Now the rpmforge repository will be added to the system and the GPG keys will be imported:

rpm --import
rpm -K rpmforge-release-0.3.6-1.el5.rf.*.rpm
rpm -i rpmforge-release-0.3.6-1.el5.rf.x86_64.rpm

In the file /etc/yum.repos.d/rpmforge.repo the following statement needs to be added to the [rpmforge] section:


This will make sure that no rpm from rpmforge will override any of the CentOS rpm.

Now we can install some additional tools that are not available in the official CentOS repository.

As there are still many script kiddies and bots out there trying to login via ssh you will still see many login tries in /var/log/secure. To avoid this there fail2ban needs to be installed and configured:

yum install fail2ban
vi /etc/fail2ban/jail.conf
enabled = true
logpath = /var/log/secure
maxretry = 1
service fail2ban start
chkconfig --level 2345 fail2ban on

Now every IP from where an invalid login is tried will be banned for 10 minutes. In most cases this avoids furthers tries later on.
If you are the only user and no password login is allowed this shouldn't do any harm. In case you still login with using password, you should increase maxretry.

After focussing on security and some basic stuff let's now continue with installing and setup of lighttpd with fastcgi and php.

yum install php php-mbstring php-mcrypt php-pear-DB lighttpd lighttpd-fastcgi

Check vi /etc/php.d/lighttpd.ini for the following statement and change/add it in case it is missing/unset:

cgi.fix_pathinfo = 1

Otherwise fastcgi will not work.
In the file /etc/php.ini check if expose_php is disabled to prevent that PHP will provide to much information to the ouside world:

expose_php = Off

Some basic stuff needs to be changed in /etc/lighttpd/lighttpd.conf. Uncomment the modules that you like to have enabled:

server.modules = (

If you prefer that lighttpd doesn't report its version number the server.tag should be set to just lighttpd:

server.tag = "lighttpd"

To enable fastcgi remove the comment # from these lines:

fastcgi.server = ( ".php" =>
    ( "localhost" =>
    "socket" => "/var/run/lighttpd/php-fastcgi.socket",
    "bin-path" => "/usr/bin/php-cgi"

The next statement will return a 403 to every request that does not match any of the hosted domains domain1 and domain2 (instead of 404):

$HTTP["host"] !~ "(^|\.)domain1$|(^|\.)domain2$" {
    url.access-deny = ( "" )

After making the configuration changes the directory for the fastcgi socket needs to be created

mkdir /var/run/lighttpd
chown lighttpd:lighttpd /var/run/lighttpd

and permission for some directories need to be corrected as these belong to apache by default. These files can be found with the find command:

find / -user apache
find / -group apache

Change owner and/or user apache to lighttpd:

chown root:lighttpd /var/lib/php/session

Now you can test if the webserver is already working and responding. Create a file in /srv/www/lighttpd/ (which is the default document root directory unless you have changed this) named index.php with the following content

<?php phpinfo(); ?>

and start lighttpd daemon:

service lighttpd start

Now point your browser to the IP of the server:


This will show you the lighttpd page with configured modeuls. Just remove the file afterwards as nodody from outside needs to how the webserver is configured:

rm /srv/www/lighttpd/index.php

As the web server is now properly configured to provide content let's go on with configuring self signed SSL certificates to use https (recommended for phpmyadmin and webmail later on). The certificates will be stored in /etc/lighttpd/ssl/ in a subdirectory with the serverip or domain name (depending on what the certificate will be used for)

mkdir /etc/lighttpd/ssl/serverip -p
cd /etc/lighttpd/ssl/serverip
openssl req -new -x509 -keyout server.pem -out server.pem -days 3650 -nodes
chown lighttpd:lighttpd /etc/lighttpd/ssl -R
chmod 0600 /etc/lighttpd/ssl/serverip

This certificate will be valid 3650 days (10 years) so you do not need to renew it regularly. We need configure SSL url now in /etc/lighttpd/lighttpd.conf. Add the following lines at the end of the file:

$SERVER["socket"] == "serverip:443" {
    server.document-root = "/srv/www/serverip"
    ssl.engine = "enable"
    ssl.pemfile = "/etc/lighttpd/ssl/serverip/server.pem"

Make sure /srv/www/serverip exists

mkdir /srv/www/serverip

Run a syntax check on the lighttpd configuration file to check everything is ok

lighttpd -t -f /etc/lighttpd/lighttpd.conf

and restart lighttpd if everything is ok

service lighttpd restart

You can check with netstat if lighttpd is bound to SSL port:

netstat -tulpn | grep :443

With this running fine next step is to install the MySQL database and phpmyadmin for easier database administration:

yum install mysql mysql-server phpmyadmin

We start MySQL right away

service mysqld start

and enter a password for the MySQL root user (do not mix it with the system root!)

mysqladmin -u root password password

Now create link for phpmyadmin in the SSL root directory

ln -s /usr/share/phpmyadmin /srv/www/serverip/phpmyadmin

Check the ownership of /usr/share/phpmyadmin/ and correct it if group is set to apache

ls -l /usr/share/phpmyadmin/
chown root:lighttpd /usr/share/phpmyadmin/

and edit the file. You need to add a passphrase in the following statement:

$cfg['blowfish_secret']= 'secret'

Now it should be possible to access the database with a webbrowser through phpmyadmin:


The next step after setup of MySQL is the mail server including antivirus and antispam applications:

yum install exim mailman clamd clamav spamassassin php-pear

We won't use the standard exim configuration but vexim which provides virtual users stored in a MySQL database and also a easy to use web interface for configuring domains and users.
First we create the vexim user:

useradd vexim -u 90 -d /usr/local/mail -s /sbin/nologin -m

Download and extract vexim package:

cd /usr/share
tar zxvf vexim2.2.1.tar.gz
chown -R root:root vexim2/

Now the standard exim.conf file will be replaced by the one provided with vexim. As some configuration from standard exim.conf will be used later it make sure to have a copy of it.

mv /etc/exim/exim.conf /etc/exim/exim.conf.bak
cp /usr/share/vexim2/docs/configure /etc/exim/exim.conf
cp /usr/share/vexim2/docs/vexim-* /etc/exim/

/etc/exim/exim.conf needs some customization for our needs.
Users and groups must be set to exim:

exim_user = exim
exim_group = exim

Same user needs to be used in the system_alias section

user = exim
group = exim

IP used for the external interface:

MY_IP = serverip

Furtheron check for the following statements and adjust them (the password is the one used for MySQL database by vexim user):

domainlist local_domains = @ : ${lookup mysql{VIRTUAL_DOMAINS}} : ${lookup mysql{ALIAS_DOMAINS}}
primary_hostname = mail.domain.tld
hostlist relay_from_hosts = localhost : MY_IP
trusted_users = vexim:lighttpd
hide mysql_servers = localhost::(/var/lib/mysql/mysql.sock)/vexim/vexim/password
log_selector = +subject +tls_cipher +tls_peerdn
av_scanner = clamd:/var/run/clamav/clamd.sock
.include /etc/exim/vexim-acl-check-spf.conf
.include /etc/exim/vexim-acl-check-helo.conf
.include /etc/exim/vexim-acl-check-rcpt.conf
.include /etc/exim/vexim-acl-check-content.conf
.include /etc/exim/vexim-group-router.conf

This block is taken from the original exim.conf. Just copy it after the spamd stanza.

# If Exim is compiled with support for TLS, you may want to enable the
# following options so that Exim allows clients to make encrypted
# connections. In the authenticators section below, there are template
# configurations for plaintext username/password authentication. This kind
# of authentication is only safe when used within a TLS connection, so the
# authenticators will only work if the following TLS settings are turned on
# as well.
# Allow any client to use TLS.
tls_advertise_hosts = *
# Specify the location of the Exim server's TLS certificate and private key.
# The private key must not be encrypted (password protected). You can put
# the certificate and private key in the same file, in which case you only
# need the first setting, or in separate files, in which case you need both
# options.
tls_certificate = /etc/pki/tls/certs/exim.pem
tls_privatekey = /etc/pki/tls/private/exim.pem
# In order to support roaming users who wish to send email from anywhere,
# you may want to make Exim listen on other ports as well as port 25, in
# case these users need to send email from a network that blocks port 25.
# The standard port for this purpose is port 587, the "message submission"
# port. See RFC 4409 for details. Microsoft MUAs cannot be configured to
# talk the message submission protocol correctly, so if you need to support
# them you should also allow TLS-on-connect on the traditional but
# non-standard port 465.
daemon_smtp_ports = 25 : 465 : 587
tls_on_connect_ports = 465

For basic setup this should be ok.
Following statement should be added to /etc/exim/vexim-acl-check-rcpt.conf right after the comments to avoid spam checking for authenticated login:

accept authenticated = *

This one needs to be changed in the same file as has been disabled:

dnslists =

Go on with changing /etc/exim/vexim-acl-check-content.conf near the end:

spam = vexim:true
spam = vexim:true

The next step is editing /usr/share/vexim2/setup/mysql.sql with the uid and gid of the vexim system user and the MySQL password for vexim database user:

uid smallint(5) unsigned NOT NULL default '90',
gid smallint(5) unsigned NOT NULL default '90',
GRANT SELECT,INSERT,DELETE,UPDATE ON `vexim`.* to "vexim"@"localhost" IDENTIFIED BY 'password';

This file is the database description for vexim and needs to be added to MySQL:

mysql -u root -p < /usr/share/vexim2/setup/mysql.sql

You need to supply the vexim database password in /usr/share/vexim2/vexim/config/variables.php

$sqlpass = "password";

For mailman you also need to change this stanza:

$mailmanroot = "http://www.domain.tld/mailman";

For creating domains and managing mail users with vexim web interface we create a link for the SSL web site:

ln -s /usr/share/vexim2/vexim/ /srv/www/serverip/vexim

As we want to have incoming mails checked for virus and malware ClamAV needs to have read access to incoming mails. These will be put in /var/spool/exim/scan for checking. This directory is only accessible by user and group exim:

# ls -ld /var/spool/exim/scan
drwxr-x--- 2 exim exim 4096 Mar 5 18:54 /var/spool/exim/scan

Therefore we need to add the user clamav to the group exim:

usermod -aG exim clamav

In /etc/clamd.conf we need to check for the following stanza:

# Initialize supplementary group access (clamd must be started by root).
# Default: no
AllowSupplementaryGroups yes

The exim setup is finished - just check the config

exim -bV

If config check reports ok, enable all the necessary daemons for running proper mail services

chkconfig --level 2345 spamassassin on
chkconfig --level 2345 clamd on
chkconfig --level 2345 mysqld on
chkconfig --level 2345 lighttpd on
chkconfig --level 2345 exim on

and start them (lighttpd should still be running):

service spamassassin start
service clamd start
service exim start

Now vexim web interface should be ready - let's try (user/password is siteadmin/CHANGE - please change password immediately):


After configuring a domain and a user, system is ready to receive mail from outside (assuming that your domain MX has been properly configured to use the server als mail server). To receive mail with a email client on any workstation a pop3 and/or imap server needs to be running. Dovecot is fulfilling this function:

yum install dovecot

To setup dovecot to work with the current setup /etc/dovecot.conf needs to be edited. Define the protocols that will be used (prefenrence is SSL secured, but if you like also plain login add imap +pop3):

protocols = imaps pop3s

Any IP4 interface should be used to listen on:

listen = *

The path for SSL certificates and mail directory need to be checked and maybe changed:

ssl_cert_file = /etc/pki/dovecot/certs/dovecot.pem
ssl_key_file = /etc/pki/dovecot/private/dovecot.pem
mail_location = /usr/local/mail/%d/%u

The following settings must fit the ones we have used for the vexim system user:

mail_privileged_group = vexim
first_valid_uid = 90
last_valid_uid = 90

The "passdb pam" authentification block must be disabled

#passdb pam {
    # ...

and the "passdb sql" section must be enabled including the args with the sql config file.

# SQL database
passdb sql {
    # Path for SQL configuration file, see doc/dovecot-sql-example.conf
    args = /etc/dovecot-sql.conf

The same is necessary for the userdb section. "userdb sql" must be enabled

# SQL database
userdb sql {
    # Path for SQL configuration file, see doc/dovecot-sql-example.conf
    args = /etc/dovecot-sql.conf

instead of "userdb passwd"

#userdb passwd {
    # ...

Dovecor already provides an example for the dovecot-sql.conf which must be copied to /etc

cp /usr/share/doc/dovecot-1.0.7/examples/dovecot-sql-example.conf /etc/dovecot-sql.conf

Following changes need to be made in this file (password is the database password for vexim database user):

driver = mysql
connect = host=localhost dbname=vexim user=vexim password=password
default_pass_scheme = CRYPT

The password and user query are a little bit tricky:

password_query = SELECT username AS user, crypt AS password FROM users WHERE username = '%u'
user_query = SELECT smtp AS mail, uid, gid FROM users WHERE username = '%u'

Now the setup is nearly done. The only steps left are creating the SSL certificates used by dovecot and for authenticated smtp. Dovecot delivers already a script and a config file for generating the keys


First edit /etc/pki/dovecot/dovecot-openssl.cnf with the necessary information. Afterwards rename/remove the existing ones that have been installed with dovecot before running the script:

mv /etc/pki/dovecot/certs/dovecot.pem /etc/pki/dovecot/certs/dovecot.pem.bak
mv /etc/pki/dovecot/private/dovecot.pem /etc/pki/dovecot/private/dovecot.pem.bak

Now dovecot service can be enabled and started:

chkconfig --level 2345 dovecot on
service dovecot start

The exim certificates can be created the same way and the same script can be used with some modifications:

cp /usr/share/doc/dovecot-1.0.7/examples/ /usr/share/doc/exim-4.63/doc/
cp /etc/pki/dovecot/dovecot-openssl.cnf /etc/pki/tls/exim-openssl.cnf

/etc/pki/tls/exim-openssl.cnf can be used without any changes but the paths in /usr/share/doc/exim-4.63/doc/ needs to be adjusted

chown exim:exim $CERTFILE $KEYFILE

The next step is now to create the certificate:


The finale step: installation of squirrelmail for webmail access

yum install squirrelmail

Unfortunately some file will have owner or group set to apache so this needs to be changed to apache. Search for these files with

find / -user apache
find / -group apache

and change them

chown root:lighttpd /etc/squirrelmail/*
chown lighttpd:lighttpd /var/lib/squirrelmail/prefs
chown lighttpd:lighttpd /var/cache/mod_proxy /var/lib/dav /var/spool/squirrelmail/attach

For changing squirrelmail setup a perl script is delivered with the package:


As imaps is configured in exim.conf this needs to be changed for suirrelmail. Start the script and change

2. Server Settings
    A. Update IMAP Settings : localhost:993 (uw)
       5. IMAP Port : 993
       7. Secure IMAP (TLS) : true

Now create the link to the SSL web site:

ln -s /usr/share/squirrelmail /srv/www/serverip/squirrelmail


The server is ready to mail services with virtual users for several domains that can be defined via vexim. In case the server should also provide web services beneath administrating MySQL, vexim and squirrelmail, virtual hosts must be added to /etc/lighttpd/lighttpd.conf.

Feel free to use the given setup. If you run into trouble during configuration, if you have any hints how to improve the setup or if you find any errors please let me know.
Have fun!

unknown ACL


i'm following your guides and got this error when trying to check exim config

[root@localhost share]# exim -bV
Exim version 4.63 #1 built 13-Jul-2010 08:20:55
Copyright (c) University of Cambridge 2006
Berkeley DB: Sleepycat Software: Berkeley DB 4.3.29: (July 12, 2010)
Support for: crypteq iconv() IPv6 PAM Perl TCPwrappers OpenSSL Content_Scanning Old_Demime
Lookups: lsearch wildlsearch nwildlsearch iplsearch cdb dbm dbmnz dnsdb dsearch ldap ldapdn ldapm mysql nis nis0 nisplus passwd pgsql sqlite
Authenticators: cram_md5 cyrus_sasl plaintext spa
Routers: accept dnslookup ipliteral manualroute queryprogram redirect
Transports: appendfile/maildir/mailstore/mbx autoreply lmtp pipe smtp
Fixed never_users: 0
Size of off_t: 4
2010-08-02 19:08:53 Exim configuration error in line 9 of /etc/exim/vexim-group-router.conf:
error in ACL: unknown ACL verb "driver" in "driver = redirect"

i tried to google but still got no hint .. got any hint ? Thanks.


Just wanted to thank you for this great tutorial, I have used it for two servers and have had no problems at all.

Problem with Dovecot

First of all your manual is ok and is very usefull, thanks, but i get this error by dovecot

+OK Dovecot ready.
user user-existent-in-db-exim
pass 123
-ERR Authentication failed.
+OK Logging out
Connection closed by foreign host.

And the dovecot's log shows

pop3-login: Aborted login: user=, method=PLAIN, rip=IP-remote, lip=IP-local, secured

the users exist in the DB exim and has mail in his userbox but i don't know whats the problem could you help me, thanks for your time.

It seems that you don't pass

It seems that you don't pass any user account with your pop3: "user="
Normally you should see the account for which you are fetching mail.

Thanks problem fix

Thanks the problem was a query reference in the db of exim, when I edited the /etc/dovecot.sql.conf.

Thanks for everything

howto works fine... but


the howto works just fine, the only thing i miss is the possibility to do server-side-filtering

I might do so when I have

I might do so when I have more time. If you have any suggestions please let me know.