Nginx + PHP-FPM with chroot

This document covers the process of setting up Nginx and PHP-FPM, with the latter running in chroot mode. It is assumed that the reader is familiar with what chroot is and its limitations.

These instructions were tested on a freshly installed Debian GNU/Linux (Wheezy, 7.7) system and require root privileges.

Introduction

Using chroot is useful where you would like to restrict PHP code from accessing files outside of a specific directory. This is especially useful if you allow other people to upload PHP code to your server (for example by hosting a WordPress instance for your friends or customers and allowing them to edit their custom theme).

The steps below shows how to configure an FPM pool (with chroot enabled) that is shared by two domains for a single imaginary customer "example.com".

Please note that this is not the most secure configuration – in practice it would be better to have a pool per domain, if your server resources allow it.

Install the packages

aptitude -q update
aptitude -q install nginx php5-fpm

Create web data directories

mkdir -p /srv/www/chroot-example.com/host1
mkdir -p /srv/www/chroot-example.com/host2

Add some sample content

cat << 'EOF' > /srv/www/chroot-example.com/host1/index.php
<?php
echo "<p>This is <b>host1.example.com</b></p>\n";
echo '<p>Current working directory is <b>' . getcwd() . "</b></p>\n";
EOF

cat << 'EOF' > /srv/www/chroot-example.com/host2/index.php
<?php
echo "<p>This is <b>host2.example.com</b></p>\n";
echo '<p>Current working directory is <b>' . getcwd() . "</b></p>\n";
EOF

Configure Nginx

cat << 'EOF' > /etc/nginx/sites-enabled/host1.example.com
server {
    listen 80;
    server_name host1.example.com;
    access_log /var/log/nginx/host1.example.com-access.log;
    error_log  /var/log/nginx/host1.example.com-error.log;
    server_tokens off;
    root /srv/www/chroot-example.com/host1;
    index index.php;
    location ~ /\.ht {deny all;}
    location ~ \.php$ {
        include         fastcgi_params;
        fastcgi_index   index.php;
        fastcgi_param   SCRIPT_FILENAME /host1$fastcgi_script_name;
        fastcgi_pass    unix:/var/run/php5-fpm-chroot-example.com.sock;
        try_files       $uri =404;
    }
}
EOF

cat << 'EOF' > /etc/nginx/sites-enabled/host2.example.com
server {
    listen 80;
    server_name host2.example.com;
    access_log /var/log/nginx/host2.example.com-access.log;
    error_log  /var/log/nginx/host2.example.com-error.log;
    server_tokens off;
    root /srv/www/chroot-example.com/host2;
    index index.php;
    location ~ /\.ht {deny all;}
    location ~ \.php$ {
        include         fastcgi_params;
        fastcgi_index   index.php;
        fastcgi_param   SCRIPT_FILENAME /host2$fastcgi_script_name;
        fastcgi_pass    unix:/var/run/php5-fpm-chroot-example.com.sock;
        try_files       $uri =404;
    }
}
EOF

Configure PHP-FPM

cat << 'EOF' > /etc/php5/fpm/pool.d/chroot-example.com.conf
[chroot-example.com]
; OUR CUSTOM SETTINGS
listen = /var/run/php5-fpm-chroot-example.com.sock
prefix = /srv/www/chroot-example.com
chroot = $prefix
; DEFAULT DEBIAN SETTINGS
chdir = /
user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
EOF

Restart services

service php5-fpm restart
nginx -t && service nginx restart

Testing

root@debian-wheezy:~# wget -q -O - http://host1.example.com
<p>This is <b>host1.example.com</b></p>
<p>Current working directory is <b>/host1</b></p>

root@debian-wheezy:~# wget -q -O - http://host2.example.com
<p>This is <b>host2.example.com</b></p>
<p>Current working directory is <b>/host2</b></p>

And we’re done! :)

Disclaimer

The information above is accurate to my knowledge, however I provide no guarantees to this effect and consequently accept no liability whatsoever for any bad things that may happen as a result of the reader using this information in practice. Use at your own risk. Oh, and backup your data while you’re at it.