Red Hat / CentOS: Chroot Apache 2 Web Server

last updated in Categories Apache, Linux distribution, package management, RedHat/Fedora Linux, Security

A chroot on Red Hat / CentOS / Fedora Linux operating changes the apparent disk root directory for the Apache process and its children. Once this is done attacker or other php / perl / python scripts cannot access or name files outside that directory. This is called a “chroot jail” for Apache. You should never ever run a web server without jail. There should be privilege separation between web server and rest of the system.


In this exclusive series, you will learn more about:

  • Securing an Apache 2 web server under Red Hat Enterprise Linux / CentOS Linux using mod_chroot
  • Virtual hosting configuration under chrooted jail.
  • Troubleshooting Chrooted Apache jail problem.


  1. Server: Apache 2 Web server.
  2. Jail directory: /httpdjail.
  3. User / Group: apache / apache (never ever run chroot using root user).
  4. Virtual domain directory for all domain inside jail: /home/httpd.
  5. PHP is configured via default mod_php.
  6. Instructions are tested under CentOS / RHEL 5.x.

More about Jail directory: /httpdjail

Create a jail directory as follows:
# J=/httpdjail
# mkdir $J

  1. Do not create /dev directory inside your jail.
  2. Do not create special device files inside jail.
  3. Do not copy shell or any other single executable files inside your jail.
  4. Do not run httpd or php / perl / python as root user.
  5. If possible mount $J using a separate partition with nosuid, nodev and noexec options. This will improve security as user will not able to run suid enabled programs and device files inside a jail.

Install Apache, PHP and MySQL

Install required packages using yum command, enter:
# yum install mysql mysql-server httpd php-mysql php-pear php-xml php-mysql php-cli php-imap php-gd php-pdo php-devel php-mbstring php-common php-ldap php httpd-devel
Now, create required directories inside your jail:
# mkdir -p $J/var/run
# chown -R root.root $J/var/run
# mkdir -p $J/home/httpd
# mkdir -p $J/var/www/html
# mkdir -p $J/tmp
# chmod 1777 $J/tmp
# mkdir -p $J/var/lib/php/session
# chown root.apache $J/var/lib/php/session

  1. $J/var/run will store PID and other files.
  2. $J/var/lib/php/session PHP session file path (configured in php.ini).
  3. $J/tmp – Used by many scripts and cms software to upload files.

Install mod_chroot

mod_chroot makes running Apache in a secure chroot environment easy. You don’t need to create a special directory hierarchy containing /dev, /lib, /etc. mod_chroot allows you to run Apache in a chroot jail with no additional files. The chroot() system call is performed at the end of startup procedure – when all libraries are loaded and log files open. Download mod_chroot using wget command:
# cd /opt/
# wget

Untar it:
# tar -zxvf mod_chroot-0.5.tar.gz
Compile and install mod_chroot for using apxs, enter:
# cd mod_chroot-0.5
# apxs -cia mod_chroot.c

Configure Apache mod_chroot

Open /etc/httpd/conf/httpd.conf file, type:
# C=/etc/httpd/conf/httpd.conf
# vi $C

Set PidFile path in which the server should record its process identification number when it starts. Find line that reads as follows:

PidFile run/

Replace with:

PidFile /var/run/

Next add ChrootDir directive, enter:

ChrootDir /httpdjail

Find line that read as follows:

ServerRoot "/etc/httpd"

Append following lines:

LockFile /var/run/httpd.lock
CoreDumpDirectory /var/run
ScoreBoardFile /var/run/httpd.scoreboard

Make sure line exists. For example, 64 bit Linux should have line as follows:

LoadModule chroot_module      /usr/lib64/httpd/modules/

32 bit Linux config line:

LoadModule chroot_module      /usr/lib/httpd/modules/

Save and close the file.

Disable SELinux for Apache

You need to disable SELinux for apache, enter:
# setsebool httpd_disable_trans 1
See article “disabling SELinux for only Apache / httpd in Linux” for further details.

Patch up /etc/init.d/httpd

Open /etc/init.d/httpd file, enter:
# vi /etc/init.d/httpd
Find out line that read as follows:

# Start httpd in the C locale by default.

Add following line (set ROOT to $J):


Find stop() that read as follows:

stop() {
        echo -n $"Stopping $prog: "
        killproc -d 10 $httpd
        [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}

Replace it as follows (you need to link /var/run/ to $J/var/run/; so that stop operation works):

stop() {
        /bin/ln -s $ROOT/var/run/ /var/run/
        echo -n $"Stopping $prog: "
        killproc -d 10 $httpd
        [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}

Save and close the file. Set immutable permission on /etc/init.d/httpd so that file cannot be modified, updated by yum, deleted or renamed, no link can be created to this file and no data can be written to the file. Only the superuser or a process possessing the CAP_LINUX_IMMUTABLE capability can set or clear this attribute:
# chattr +i /etc/init.d/httpd

How do I start chrooted httpd?

Type the following command:
# /etc/init.d/httpd start
You should not see any error in /var/log/httpd/error_log file:

[Sun Dec 21 18:43:09 2008] [notice] core dump file size limit raised to 18446744073709551615 bytes
[Sun Dec 21 18:43:09 2008] [notice] SELinux policy enabled; httpd running as context root:system_r:initrc_t
[Sun Dec 21 18:43:09 2008] [notice] suEXEC mechanism enabled (wrapper: /usr/sbin/suexec)
[Sun Dec 21 18:43:09 2008] [notice] Digest: generating secret for digest authentication ...
[Sun Dec 21 18:43:09 2008] [notice] Digest: done
[Sun Dec 21 18:43:10 2008] [notice] mod_chroot: changed root to /httpdjail.
[Sun Dec 21 18:43:10 2008] [notice] Apache/2.2.3 (CentOS) configured -- resuming normal operations

How do I stop chrooted httpd?

# /etc/init.d/httpd stop

How do I restart chrooted httpd?

# /etc/init.d/httpd restart

Posted by: Vivek Gite

The author is the creator of nixCraft and a seasoned sysadmin, DevOps engineer, and a trainer for the Linux operating system/Unix shell scripting. Get the latest tutorials on SysAdmin, Linux/Unix and open source topics via RSS/XML feed or weekly email newsletter.


46 comment

  1. My Fedora 10 mirrors do not include php-par or php-dv:

    ~ root $ yum install php-par php-dv
    Loaded plugins: dellsysidplugin, refresh-packagekit
    Setting up Install Process
    Parsing package install arguments
    No package php-par available.
    No package php-dv available.
    Nothing to do

    My mirrors are:

  2. I got trouble when I typed
    apxs -cia mod_chroot.c
    notification appeared :
    bash: apxs: command not found
    I have installed httpd-devel in my system

    I’m using fedora 9

    please help me

  3. @adhoy,

    You are using old apache version 1.3. or older. This is not supported and not tested by me. All information on this page is tested with APACHE 2 and latest Red Hat Linux version 5.

  4. I use new version of apache server.
    there is :

    [root@aksanfamily algebra25]# yum -y install httpd
    Loaded plugins: fastestmirror, refresh-packagekit
    Loading mirror speeds from cached hostfile
    * livna:
    * rpmfusion-free-updates:
    * rpmfusion-nonfree-updates:
    * rpmfusion-free:
    * updates-newkey:
    * fusion:
    * updates:
    * rpmfusion-nonfree:
    Setting up Install Process
    Parsing package install arguments
    Package httpd-2.2.9-1.fc9.i386 already installed and latest version
    Nothing to do

    what wrong?

  5. @adhoy

    look for apxs on your system, I used /usr/sbin/apxs on a CentOS5 install

    @everyone, some typos to deal with:

    vi /etc/init.d/httpd.conf will not work, the correct command is

    # vi /etc/init.d/httpd

    chatter +i /etc/init.d/httpd.conf is incorrect, the program is chattr. the file is httpd (as above) so the correct command is

    # chattr +i /etc/init.d/httpd

    otherwise, good informative article which works.

  6. No problem, good to spread the word and get more people taking charge of their systems.

    You still need to change this:
    # chatter +i /etc/init.d/httpd
    its chattr, no e

    and also this:

    PidFile /var/run/httpd.pidM

    no M

  7. Installing Apache in a chroot jail does not make Apache itself any more secure. Rather, it serves to restrict the access of Apache and its child processes to a small subset of the filesystem. The advantage in chrooting a process is not in preventing a breakin, but rather in containing a potential threat.

  8. Your instructions were extremely easy to follow–thank you. Unfortunately, I am having a problem starting Apache after running down the list. I’ve created the httpdjail directory exactly as described, and edited the referenced files exactly as described, but I here’s what /var/log/httpd/error_log shows whenever I try to start the service:

    [Thu Jan 01 16:21:00 2009] [notice] core dump file size limit raised to 4294967295 bytes
    [Thu Jan 01 16:21:00 2009] [notice] suEXEC mechanism enabled (wrapper: /usr/sbin/suexec)
    [Thu Jan 01 16:21:01 2009] [notice] Digest: generating secret for digest authentication …
    [Thu Jan 01 16:21:01 2009] [notice] Digest: done
    [Thu Jan 01 16:21:01 2009] [notice] mod_chroot: changed root to /httpdjail.
    [Thu Jan 01 16:21:01 2009] [alert] (2)No such file or directory: Can’t chdir to /httpdjail

    (this alert repeats seven times)

    [Thu Jan 01 16:21:01 2009] [notice] Apache/2.2.10 (Unix) DAV/2 mod_chroot/0.5 configured — resuming normal operations
    [Thu Jan 01 16:21:01 2009] [alert] Child 4620 returned a Fatal error… Apache is exiting!

    And that’s it. I’ve verified that the /httpdjail directory does indeed exist, and it’s permission block is “drwxr-xr-x”, which I believe is correct. I’m running Fedora 10. Any suggestions?

  9. This is well known issue with selinux, try disabling SELinux for httpd:

    setsebool httpd_disable_trans 1

    If this failed disable SELinux totally.

  10. Thanks for the suggestion. I already have SELinux disabled (by changing “SELINUX=enforcing” to “SELINUX=disabled” in /etc/selinux/config), so the setsebool command yields a notification that SELinux is disabled. I re-enabled it and rebooted, then tried the setsebool command, which now says “Could not change active booleans: Invalid boolean”.

    The only difference running with SELinux enabled vs. disabled is that with it enabled, the following additional line appears in /var/log/httpd/error_log:

    [notice] SELinux policy enabled; httpd running as context unconfined_u:unconfined_r:unconfined_t:s0

    Every other line is the same as reported earlier. With SELinux disabled, that line isn’t there.

    I do appreciate the help. Any other suggestions?

  11. I think is more secure stay apache with selinux enabled than do yours instructions and run apache in chroot. SELinux is a very important software to make the system more secure. I recommend do not do this, and use apache+selinux.

  12. Hello

    I’m running into the exact problem as LeeH. I have the message repeated 5 times. I’m using apache-2.2.10 w/mod_chroot-0.5 on a Gentoo System. My set up is a little different, and after upgrading from apache-2.2.9-r1, I noticed this problem.

    [Sat Jan 10 09:37:30 2009] [notice] mod_chroot: changed root to /var/chroot/apache.
    [Sat Jan 10 09:37:30 2009] [alert] (2)No such file or directory: Can’t chdir to /var/chroot/apache
    [Sat Jan 10 09:37:30 2009] [alert] (2)No such file or directory: Can’t chdir to /var/chroot/apache
    [Sat Jan 10 09:37:30 2009] [alert] (2)No such file or directory: Can’t chdir to /var/chroot/apache
    [Sat Jan 10 09:37:30 2009] [alert] (2)No such file or directory: Can’t chdir to /var/chroot/apache
    [Sat Jan 10 09:37:30 2009] [alert] (2)No such file or directory: Can’t chdir to /var/chroot/apache
    [Sat Jan 10 09:37:30 2009] [notice] Apache/2.2.10 (Unix) mod_ssl/2.2.10 OpenSSL/0.9.8j mod_chroot/0.5 Apache configured — resuming normal operations
    [Sat Jan 10 09:37:30 2009] [alert] Child 24458 returned a Fatal error… Apache is exiting!

    It’s almost appears that apache goes into chroot, but is referencing /var/chroot/apache after it’s been jailed, so it can’t reach these. Just like LeeH, I have those folders in place with proper permissions. Rolling back to apache-2.2.9-r1 and the problem goes away. Definitely appears to be related to apache-2.2.10.

    Any ideas?? I am not using SELinux


  13. Tom

    Not sure if I’m following. Like I said, this was working for me in 2.2.9-r1 (with SSL ability). I do have /etc/ssl/certs in the jail as well as /dev/urandom. What else would I need in the jail? Going through my strace, I didn’t see any complaints about SSL, but then again, I didn’t see it really complaining about anything except being unable to chdir.


  14. I’ve tested this with Apache/2.2.3 and have no problem. Look like this is related to Apache version. Do you have SELinux endabled?

  15. Hello Vivek

    I do not have SELInux enabled. I agree, it seems like there is a problem with apache-2.2.10 and above with mod_chroot.


  16. Hello

    I have it figured out. You cannot use mod_chroot with apache-2.2.10. Apache-2.2.10 has built in chroot handling now. All you need to do is provide a ChrootDir variable in httpd.conf. The problem is that mod_chroot sets that value as well, I think it was trying to do a “double” chroot. Removing the -D chroot flag from apache startup options and configuring the variable got the server to start, and I verified that the chroot jail was working.


  17. Hi there, i’ve testet this with CentOS 5, looks like it works but….

    in my vhost.conf:

    DocumentRoot /home/httpd/.sklep/http points into the httpdjailroot/home….
    ErrorLog point into the main / not into the httpjail ?????

  18. hello sir actually i want to create a directory in linux by using command line i created a directory by using mkdir but i am unable to create a directory in the name red Hat if any one knows plz help me waiting for your answers plz kindly reply me

  19. Hello guys,

    I also received the following errors in the apache error logs and it happened two times causing apache to fail

    [Mon Apr 20 00:20:02 2009] [emerg] (43)Identifier removed: couldn’t grab the accept mutex
    [Mon Apr 20 00:20:02 2009] [emerg] (43)Identifier removed: couldn’t grab the accept mutex
    [Mon Apr 20 00:20:02 2009] [emerg] (43)Identifier removed: couldn’t grab the accept mutex
    [Mon Apr 20 00:20:02 2009] [emerg] (43)Identifier removed: couldn’t grab the accept mutex
    [Mon Apr 20 00:20:02 2009] [emerg] (43)Identifier removed: couldn’t grab the accept mutex
    [Mon Apr 20 00:20:02 2009] [emerg] (43)Identifier removed: couldn’t grab the accept mutex
    [Mon Apr 20 00:20:02 2009] [emerg] (43)Identifier removed: couldn’t grab the accept mutex
    [Mon Apr 20 00:20:02 2009] [emerg] (43)Identifier removed: couldn’t grab the accept mutex
    [Mon Apr 20 00:20:02 2009] [alert] Child 13096 returned a Fatal error…nApache is exiting!
    exclog: signal received 15

    While digging through some forums i noticed the cause can probably due to extra swap alloted. Can someone throw some light on it?

  20. doing so http, mysql, php work fine but, perl script will not run.
    log file show
    (2)No such file or directory: exec of ‘/var/www/cgi-bin/test.cgi’ failed
    Premature end of script headers: test.cgi
    I guess httpdjail needs /usr/lib/perl5 into it
    Please advice.

  21. I anticipated wanting to jailroot my apache server but would like to contain it all in one area of an LVM volume, the moint point being:


    I have created:


    But wanted to put other servers folders in their like the area for ftp access (not the actual server just to make it as secure as humanly possible), is this the best way?

    Just wanted to have a sort of directory that had:


    Is this a wise approach or would I have to go for:

    /ftpjail etc?

    Just seems logical to me to have all jailed services in one mount point.

    Any advice given would be brilliant thanks.


  22. there is some issue, and it killing me…

    on chrooted fedora 13, using php’s 5.3.3 function date, getting the following error:

    Warning: date(): It is not safe to rely on the system’s timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected ‘UTC’ for ‘GMT/0.0/no DST’ instead in /var/www/html/index.php on line 6 Fatal error: date(): Timezone database is corrupt – this should *never* happen! in /var/www/html/index.php on line 6

    if i have (or not) defined date.timezone, it ALWAYS returns same error.

    if apache is not chrooted, it works fine.

    please, please, please, please help……….

  23. I do not understand the purpose of this tutorial. What is the security benefit to disable SELinux that runs Apache in isolated sandbox to create a weak [Linux chroot is not as strong as FreeBSD one; there are several techniques known to break out of it] chrooted environment?

  24. make sure that php have enough access to read


    I created hardlinks from the above to /etc/localtime and /usr/share/zoneinfo – worked like a charm

  25. Hi there, i followed your tutorial, but the apache won’t start up. error log says:

    [notice] mod_chroot: changed root to /httpjail.
    [emerg] (2) No such file or directory: mod_fcgid: Can’t create share memory for size %zu byte

    Any ideas how to fix this?

  26. Thanks Vivek,

    Everything seems to work fine, am using CentOS 5.5 Finnal with Webmin, as am newbie with chrooting apache here but i have 1 simple question, how could we now add websites under webmin after this chroot apache completely works, and does this thread works fine with mySQL databases after chrooting ?

    Thanks a bunch!

  27. Thanks Vivek,

    Everything seems to work fine, am using CentOS 5.5 Finnal with Webmin, as am newbie with chrooting apache here but i have 1 simple question, how could we now add websites under webmin after this chroot apache completely works, and does this thread works fine with mySQL databases after chrooting ?

    Thanks a bunch!

    no it dos not i just have this problem now. and it kinda sucks.

  28. there is one mistake in the post {at least it was a mistake w.r.t. my implementation}
    in the section above where we are supposed to edit the service script file for httpd/apache and add the line
    {{ /bin/ln -s $ROOT/var/run/ /var/run/ }} to {{stop() method}}
    to the service script so that the PID (which gets created at $Jail/var/run) gets sym-linked at ‘/var/run’ and can be actually accessed by system service… say, so that ‘service httpd status’ don’t give ‘httpd not running, but subsys locked’ as message

    but sym-linking of PID when service gets stopped doesn’t make sense and neither does work…
    it should be added to
    {{ start() service }}
    so that when the HTTP Server gets started the PID gets sym-linked to its default system-location and remains accessible by system services

    {{at least this was a problematic case in my implementation and the way I solved it}}

  29. Good howto

    Just an update that you may want to mention at the start of this post. As of apache 2.2.10 chroot is now included in the Apache MPM Common Directives –

    Meaning it does not have to be compliled and installed. You just have to declare the ChrootDir directive in the httpd.conf

    ChrootDir /path_to_chroot_jail

    Still comes with all the normal chroot pains though.

  30. Gary,

    Thanks in advance, but is there any links/tips how to install it as you said, other than Vivek thread?

  31. Hi,

    I have using slackware linux 13.1 64bit, I have already installed the apache below path( /usr/loca/apache2). Now I have install the mod_xslt2 module in apahce. while install the
    moudle I am getting below error. Please help me how to fix this issue.

    ../configure –with-sapi=apache2
    configure: * Trying with: ‘apache2’
    checking for apxs2… no
    checking for /usr/sbin/apxs2… no
    checking for /usr/bin/apxs2… no
    checking for /usr/local/bin/apxs2… no
    checking for /usr/local/apache/bin/apxs2… no
    checking for apxs… /usr/sbin/apxs
    configure: WARNING:
    * Apache has been compiled with a different compiler than the specified (or detected) one!
    * I will use the same compiler as used by apache: ‘x86_64-slackware-linux-gcc’
    * I’m also assuming ‘x86_64-slackware-linux-gcc’ is ISO C and POSIX compliant.
    checking for apr-config… no
    configure: error: apache2: couldn’t find a valid ‘apr-config’. Please use –with-apr-config.


  32. Hi Vivek,

    Just want to ask, in the chroot directories why the httpd.conf file is not included i.e. the “/etc/httpd” directory, as that is also one of the important configuration directory which can be hacked or modified. Whether that is required ? What is the reason of not including that ?


  33. So after doing this in CentOS I get this:

    [error] NSS_Initialize failed. Certificate database: /etc/httpd/alias.
    [error] SSL Library Error: -8038 SEC_ERROR_NOT_INITIALIZED


  34. dear, after doing these steps now i can’t open my site, i have done these settings in my localhost :((

    any idea??

    thanks in advance

  35. Hello
    i got some errors in log file .

    [Sun Aug 05 17:52:33 2012] [notice] mod_chroot: changed root to /httpdjail.
    [Sun Aug 05 17:52:33 2012] [error] (13)Permission denied: could not create /var/run/
    [Sun Aug 05 17:52:33 2012] [error] httpd: could not log pid to file /var/run/

    how can i fix that ?

    1. Generally when you see ‘permission denied’ followed by ‘could not create’ your process lacks the ability to write to that directory.

      Also when you see ‘could not log pid fiel to…’ either the directories do not exist or your process cannot write to them.

  36. Hi.
    apxs -cia mod_chroot.c

    /usr/lib64/apr-1/build/libtool –silent –mode=compile gcc -prefer-pic -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector –param=ssp-buffer-size=4 -m64 -mtune=generic -Wformat-security -fno-strict-aliasing -DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -pthread -I/usr/include/httpd -I/usr/include/apr-1 -I/usr/include/apr-1 -c -o mod_chroot.lo mod_chroot.c && touch mod_chroot.slo
    /usr/lib64/apr-1/build/libtool: line 970: gcc: command not found
    apxs:Error: Command failed with rc=65536

    Any ideea?

Leave a Comment