Lighttpd FasCGI PHP, MySQL chroot jail installation under Debian Linux

The instruction mentioned below only applies to Debian and Ubuntu Linux. I am going to document following things:

=> Install lighttpd
=> Prepare the file system for the jail
=> Run FastCGI PHP and MySQL from the jail
=> Add Perl support to the jail
=> Take care of sendmail
=> Run multiple domains (virtual hosting) from chrooted jail etc

Please note that information outlined below is for advanced UNIX users or admins only ;).

Note: If you are using Ubuntu Linux read this howto.

Step # 1: Install lighttpd, php4-cgi and mysql server

Use apt-get command to install packages:# apt-get install lighttpd php4-cgi php4-cli php4-mysql mysql-server Note: If you need other php modules just install them using apt-get command.

Step # 2: Prepare the file system

Create a directory called /webroot:# mkdir /webroot

Create temporary /webroot/tmp directory:# mkdir /webroot/tmp/
# chmod 1777 /webroot/tmp/

Create /etc directory to store php.ini file:# mkdir /webroot/etc

Create a log directory for lighttpd web server:# mkdir -p /webroot/var/log/lighttpd
# chown www-data:www-data /webroot/var/log/lighttpd

Create a cache directory:# mkdir -p /webroot/var/tmp/lighttpd/cache/compress/
# chown www-data:www-data /webroot/var/tmp/lighttpd/cache/compress/

Create a lighttpd home directory for virtual hosting
# mkdir -p /webroot/home/lighttpd
# chown www-data:www-data /webroot/home/lighttpd
# chmod 0700 /webroot/home/lighttpd
# ls -dl /webroot/home/lighttpd

drwx------  2 www-data www-data 4096 Oct  5 23:15 /webroot/home/lighttpd

A handy shell script (l2chroot [download] ) to copy necessary shared system libraries:


if [ $# -eq 0 ]; then
  echo "Syntax : $0 /path/to/executable"
  echo "Example: $0 /usr/bin/php5-cgi"
  exit 1

[ ! $BASE ] && mkdir -p $BASE || :

# iggy ld-linux* file as it is not shared one
FILES="$(ldd $1 | awk '{ print $3 }' |egrep -v ^'\(')"

echo "Copying shared files/libs to $BASE..."
for i in $FILES
  d="$(dirname $i)"
  [ ! -d $BASE$d ] && mkdir -p $BASE$d || :
  /bin/cp $i $BASE$d

# copy /lib/ld-linux* or /lib64/ld-linux* to $BASE/$sldlsubdir
# get ld-linux full file location
sldl="$(ldd $1 | grep 'ld-linux' | awk '{ print $1}')"
# now get sub-dir
sldlsubdir="$(dirname $sldl)"

if [ ! -f $BASE$sldl ];
  echo "Copying $sldl $BASE$sldlsubdir..."
  /bin/cp $sldl $BASE$sldlsubdir

Put l2chroot in /bin directory and set executable permission:# wget
# mv l2chroot.txt l2chroot
# cp l2chroot /bin
# chmod +x /bin/l2chroot

Step 3: Put PHP in the jail

Now you need to copy PHP executable files and necessary extensions (php-mysql) to /webroot directory.
# mkdir -p /webroot/usr/bin
# cp /usr/bin/php4-cgi /webroot/usr/bin/
# cp /usr/bin/php4 /webroot/usr/bin/

Copy /etc/php4/cgi/php.ini file to /webroot/etc/ directory.
# cd /webroot/etc/
# cp -avr /etc/php4 .

Now copy other config files in jail:
# cp /etc/hosts /webroot/etc/
# cp /etc/nsswitch.conf /webroot/etc/
# cp /etc/resolv.conf /webroot/etc/
# cp /etc/services /webroot/etc/
# cp /etc/localtime /webroot/etc/

Copy all php shared libraries used by /usr/bin/php4 and /usr/bin/php4-cgi using your l2chroot script:
# /bin/l2chroot /usr/bin/php4
# /bin/l2chroot /usr/bin/php4-cgi

Now you have all shared libraries in /webroot directory. You can verify this with ls command. There is one more file, which you need to copy manually – /lib/
# cp /lib/ /webroot/lib

Step 4: Put php MySQL extension in the jail

To access MySQL database server you need to use php4-mysql extension.
Copy php mysql extension from /usr/lib/php4/20050606 directory, use following command to determine exact location of file:
# dpkg -L php4-mysqlOutput:


Copy /usr/lib/php4/20050606/ file to /webroot/usr/lib/php4/20050606/ and related shared libs using /bin/l2chroot script:
# mkdir -p /webroot/usr/lib/php4/20050606
# cp /usr/lib/php4/20050606/ /webroot/usr/lib/php4/20050606/
# /bin/l2chroot /usr/lib/php4/20050606/

Repeat above procedure to copy all your php shared modules such as php-imap (required for webmail), php-gd (GD module for php4 used by wordpress and other softwares), php-memcache etc.

Step # 5: Configure lighttpd to run from chrooted jail

Make sure fastcgi module is enabled:
# lighty-enable-mod fastcgiOutput:

Available modules: auth cgi cml fastcgi proxy simple-vhost ssi ssl trigger-b4-dl userdir
Already enabled modules:
Enabling fastcgi: ok
Run /etc/init.d/lighttpd force-reload to enable changes

Configure lighttpd by editing /etc/lighttpd/lighttpd.conf file:
# vi /etc/lighttpd/lighttpd.conf

The most importat part is server.chroot directive. Open config file:
# vi /etc/lighttpd/lighttpd.conf
Set server.chroot to /webroot:
server.chroot = "/webroot"

Above directive applies chroot() call to directory called /webroot. Once applied no one (except root user) can access file system outside /webroot directory.

Rest of the configuration directives is documented very well in file itself. Start your lighttpd:
# /etc/init.d/lighttpd start

Test jail setup

Create two test php files in /webroot/home/lighttpd

  • db.php : Test MySQL database connectivity, make sure you modify this file for correct MySQL server hostname, username and password.
  • test.php : Test php via phpinfo()

Open a web browser and type url and

Congratulations, if you are able to run both db.php and test.php w/o problem. Always refer to /var/log/message (outside /webroot directory) for troubleshooting purpose. If you see error message that read as follows (tail -f /var/log/message) :

php5-cgi[7325]: segfault at 0000000000001e98 rip 00002ad2cf6bd101 rsp 00007fffdb3f1ed0 error 4

To fix this problem, copy all shared libs from /lib and /usr/lib to /chroot (or /lib64 & /usr/lib if you are using 64 bit Linux) directory. But please do NOT copy any executable files from /bin/ /usr/bin or /usr/sbin directory.
# cp -avr /lib/* /webroot/lib/
# cp -avr /usr/lib/* /webroot/usr/lib/

Follow these instructions for more information.

Size of the /webroot jail

Here is size of webroot jail:
# du -chOutput:

28K     ./var/www
104K    ./var/log/lighttpd
108K    ./var/log
4.0K    ./var/run
4.0K    ./var/tmp/lighttpd/cache/compress
8.0K    ./var/tmp/lighttpd/cache
12K     ./var/tmp/lighttpd
16K     ./var/tmp
160K    ./var
4.0K    ./tmp
5.9M    ./usr/bin
2.7M    ./usr/lib/i686/cmov
2.7M    ./usr/lib/i686
48K     ./usr/lib/php4/20050606
52K     ./usr/lib/php4
7.5M    ./usr/lib
14M     ./usr
1.7M    ./lib/tls
2.0M    ./lib
44K     ./etc/php4/cgi
48K     ./etc/php4
56K     ./etc
16K     ./home/lighttpd
20K     ./home
16M     .
16M     total

As you see our jail only took 16MB disk space. I will address rest of the issues such as perl support and sendmail problem tomorrow 🙂

Continue reading the rest of Lighttpd series articles.

Updated for accuracy.

🐧 If you liked this page, please support my work on Patreon or with a donation.
🐧 Get the latest tutorials on SysAdmin, Linux/Unix, Open Source & DevOps topics via:
CategoryList of Unix and Linux commands
File Managementcat
FirewallAlpine Awall CentOS 8 OpenSUSE RHEL 8 Ubuntu 16.04 Ubuntu 18.04 Ubuntu 20.04
Network Utilitiesdig host ip nmap
OpenVPNCentOS 7 CentOS 8 Debian 10 Debian 8/9 Ubuntu 18.04 Ubuntu 20.04
Package Managerapk apt
Processes Managementbg chroot cron disown fg jobs killall kill pidof pstree pwdx time
Searchinggrep whereis which
User Informationgroups id lastcomm last lid/libuser-lid logname members users whoami who w
WireGuard VPNAlpine CentOS 8 Debian 10 Firewall Ubuntu 20.04
80 comments… add one
  • tuxd3v Feb 2, 2017 @ 14:26

    Only to share a remake of your script:

    # Use this script to copy shared (libs) files to SSH/SFTP chrooted jail Server
    # ----------------------------------------------------------------------------
    # Written by nixCraft
    # (c) 2006 nixCraft under GNU GPL v2.0+
    # + Added ld-linux support
    # + Added error checking support
    # 2016/11/11
    # Changed by Carlos Domingues
    # (c) 2016 Carlos Domingues under  GNU GPL v2.0+
    #  + Reporting actions is now better.
    #  + All Operations are now done, in a simple 'for cycle'...
    # ------------------------------------------------------------------------------
    # See url for usage:
    # -------------------------------------------------------------------------------
    # Set CHROOT directory name
    # -------------------------------------------------------------------------------
    if [ $# -eq 0 ]; then
      echo "Syntax : $0 /path/to/executable"
      echo "Example: $0 /usr/bin/php5-cgi"
      exit 1
    [ ! -d $BASE ] && mkdir -p $BASE || :
    FILES="$(ldd $1 | awk '{if($3 ~/^// && $2 ~/^=>/ ){print $3;}else{ if($1 ~/^// && $2 ~/^(/ )print $1;}}')"
    #echo "Copying shared files/libs to $BASE..."
    for i in $FILES;do
            echo "Copying $i shared files/libs to $BASE..."
            d="$(dirname $i)"
            [ ! -d $BASE$d ] && mkdir -p $BASE$d || :
            /bin/cp -pv $i $BASE$d
  • Nguyen Thanh Binh Jun 5, 2013 @ 9:32

    Thanks for your the article… It’s very great.

  • none-101 Oct 17, 2011 @ 15:01

    Where do you put the static web files that one wishes to serve?
    I have these on /www/ Is it possible, using your set-up above to still server from /www. These are on different partitions.


  • maximebuy Apr 13, 2011 @ 22:48

    I followed the instruction and got 400 error also. Then I made the following changes:
    1. change the base dir in /etc/lighttpd/lighttpd.conf from /var/www to /home/lighttpd
    2. change the cache dir from /webroot/var/tmp/lighttpd/cache/compress/ to /webroot/var/cache/lighttpd/compress/
    3. change the /etc/lightpd/conf-enablede/10-fastcgi.conf, where /usr/bin/php-cgi to /usr/bin/php5-cgi

    Then I can open the db.php and test.php, my system is “PHP Version 5.2.6-1+lenny10”.

    After these three steps, there are still some error in output of db.php, just see the log file in /webroot/var/log/lighttpd/error.log and check the errors.

  • jh Dec 16, 2010 @ 2:49

    Followed the guide to put my lighttpd server into jail. All working fine except DNS resolution not working in chroot jail. Turn out I have to copy both and from /lib to /webroot/lib. Great guide.

  • Christoph Dec 6, 2010 @ 17:14

    nice tutorial

    The default path of the compress cache directory in Debian is now “/var/cache/lighttpd/compress/”.

    So the diretory “/webroot/var/cache/lighttpd/compress/” should exists to work (instead of the directory in the tutorial). 😉

  • Julien Aug 29, 2010 @ 19:58

    Thanks for the howto, I used it as a based to my own script that chroot nginx and php on Debian. I believe it might be of interest to some of the readers here, so here is the link.

  • lfitz Jun 16, 2010 @ 1:31

    hi, i get 403 – forbidden errors for db.php and test.php, how can i fix this?

  • james Apr 12, 2010 @ 9:52

    Hi. I have followed carefully each steps and I received this error when starting lighttpd:

    (mod_fastcgi.c.1042) the fastcgi-backend /usr/bin/php-cgi failed to start:
    (mod_fastcgi.c.1046) child exited with status 9 /usr/bin/php-cgi
    (mod_fastcgi.c.1049) if you try do run PHP as FastCGI backend make sure you use the FastCGI enabled version.
    You can find out if it is the right one by executing ‘php -v’ and it should display ‘(cgi-fcgi)’ in the output, NOT (cgi) NOR (cli)

    lighttpd has been working fine before I wanted to put it in jail. whenever I comment out the server.chroot line. everything works ok.

    i have set correctly /usr/bin/php-cgi correctly to /usr/bin/php5-cgi but still the same error.

  • SilentHunter Mar 12, 2010 @ 16:51

    Sorry but again to this problem:

    I used this tutorial with Debian Etch rc3 and PHP5. It works fine until I want to use php fastcgi. After enable FASTCGI with “lighty-enable-mod fastcgi”, I changed /etc/lighttpd/conf-enabled/10-fastcgi.conf:

    “bin-path” => “/usr/bin/php4-cgi”,
    “socket” => “/tmp/php.socket”,

    “bin-path” => “/webroot/usr/bin/php5-cgi”,
    “socket” => “/webroot/tmp/php.socket”,

    After restart lighttpd I get an error message:
    “unix: /webroot/tmp/php.socket could not be found or is not writeable”

    Is there a better solution than cutting the /webroot? I get the same error with the socket but without the /webroot it wouldn’t work anyway….

  • grand wazoo Jan 29, 2010 @ 16:39

    If you’re a “noobie who need[s] help,” then you shouldn’t be preparing an externally facing chroot’ed security-critical linux web server installation. Pay someone to do this for you. Security is really, really, really hard. Buck up.

  • mandible Jan 5, 2010 @ 0:45

    this website sucks, typical linux users who dont give a shit about noobies who need help, this tutorial doesnt work. install latest debian, and use php5 instead of 4, and follow this tutorial, it doesnt work.

  • maxBirdy Nov 9, 2009 @ 1:35

    i run through this whole tutorial

    i make sure that /home/lighttpd/ is the root dir

    and that /webroot is the chroot jail

    then i restart lighty

    all is good, and it restarts without any errors

    then i type in,, or, firefox goes “Firefox can’t establish a connection to the server at x.x.x.x” my port 80 is forwarded, but when i go to, it says port 80 is not in use.

    i run ps aux in bash shell, i see nothing that says “lighttpd”

    im using php5, mysql 5.1, and lighttpd 1.4.23

    wtf is wrong with my setup, just because my software is newer shouldnt change anything right?

    the.conf files for lighty on this site, and mine, are identical, in configuration, but mine has a few other settings that are commented out so i just leave them be.

    i have even checked my error logs, not a damn thing is in them

    and ive checked my network configuration, all is well there too.

    plllleeeeeeeaaaaaaaasssssseeeeeeeeeee help, im really lost and frustrated.

  • L1ttl3J1m Apr 30, 2009 @ 9:53

    For $DEITY’s sake, don’t approve that last one 🙂 Try this one instead.

    I had some trouble getting it to work, but here’s some stuff I worked out.

    For missing libraries ( you can do this;

    ldd ./ – Which you can find by looking in the PHP error log. I found it especially good for this one;

    PHP Startup: Unable to load dynamic library ‘’ – File not found in Unknown on line 0

    So do an LDD on that file (ldd ./ (be in the directory where the file is, of course)) and it outputs the libraries that that extension needs to run – scatter those around in the jail versions of /lib, /opt/lib, usr/lib, /usr/local/lib, and those errors stop happening.

    For the ones that it didn’t make go away (I’m looking at you, MySQL!), doing;

    ldconfig -p | grep (name of broken process**)

    will give you a few more to copy across.

    The thing that I was wondering about though, is how to go about securing that tmp folder. I don’t like the idea of having a world-executable folder on my server, no matter how jailed it is.

    Any ideas?

    *Make sure you don’t forget to set that one to a path that’s inside the jail, too, otherwise PH can’t write to it and you’ll never see these handy-dandy helpful messages. ***

    ** MySQL, grep bz for bzip & bzip2, for example – one for each extension that’s in your PHP.ini file, basically.

    *** That’s sarcasm, by the way. File not found in Unknown at line 0, yeah that’s helpful!

  • Janez Dolinar Apr 17, 2009 @ 10:13

    (i know this is an old reply but I had the same problem)

    @magnus: make sure, you copied files correctly. In my case I had /webroot/lib64 as a file!

  • niyo Mar 18, 2009 @ 2:35


    Can this chrooted setup be managed by cpanel or ispconfig?


  • mdev Jul 20, 2008 @ 11:59

    Thanks although some things do not work when using your tutorial and need changes I got it running and learned alot on trial and error.

  • Mike Apr 30, 2008 @ 16:14

    Question: What about the ‘*.pid’ files? Can they stay where they are or do I have to copy them into the jail too?

  • Mike Apr 30, 2008 @ 14:51

    Hmmm…I guess I got it. I had to copy the ‘’ file to, which was not showing with ‘dpkg -L php5-mysql’ 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

Use HTML <pre>...</pre>, <code>...</code> and <kbd>...</kbd> for code samples.