Nginx – “No input file specified” when running php under fastcgi inside chroot under nginx outside of chroot

chrootfastcginginxPHPUbuntu

I have a scenario with nginx/1.4.6 running on Ubuntu 14.04 but with php/5.2.10 inside a chroot Ubuntu 9.10 (Karmic Koala).

My issue is that all requests to php files result in the dreaded "No input file specified."

I have the site stored inside the chroot, so it is readable both from php inside the chroot jail and from nginx outside of the jail with this setup:

From nginx' point of view:
/var/chroot/karmic/var/www/domains/dummysite/web:
.         www-data:www-data drwxr-xr-x
index.php www-data:www-data -rw-r--r--
test.jpg  www-data:www-data -rw-r--r--

And inside chroot

From php's point of view:
/var/www/domains/dummysite/web:
.         www-data:www-data drwxr-xr-x
index.php www-data:www-data -rw-r--r--
test.jpg  www-data:www-data -rw-r--r--

And index.php is dead simple!

<?php
    echo '<h1>Hello World</h1> Foo bar...';
?>

I have started php-fcgi with spawn-fcgi from lighttpd using this command:

LANG=C chroot /var/chroot/karmic /usr/bin/spawn-fcgi -C 12 -a 127.0.0.1 -p 9000 -u www-data -g www-data -f /usr/bin/php5-cgi -P /var/run/fastcgi-php.pid

Nginx can successfully serve the static test.jpg, but php-fcgi fails to read index.php

# /etc/nginx/site-enabled/dummysite -> /etc/nginx/site-available/dummysite:

server {
    listen 80;
    root /var/chroot/karmic/var/www/domains/dummysite/web;
    server_name dummysite.wtf;

    location / {
        try_files $uri $uri/ /index.php?q=$uri&$args;
        index index.php index.html;
        allow all;
    }

    location ~ ^/index\.php {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_intercept_errors on;
        fastcgi_param SCRIPT_NAME $fastcgi_script_name;
        fastcgi_param SCRIPT_FILENAME /var/www/domains/dummysite/web$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT /var/www/domains/dummysite/web;
        include fastcgi_params;
    }
}

As far as I have understood, this should be the correct syntax. I have also tried with some variations such as $document_root$fastcgi_script_name, without SCRIPT_NAME or DOCUMENT_ROOT set and with SCRIPT_FILENAME relative to DOCUMENT_ROOT or the root inside the server block.

I have no open_basedir restrictions set in php.

Despite having maximum logging enabled in php and nginx, I get no workable information in either php.log, nginx.error.log or dummysite.wtf.error.log.

I have resorted to connecting to php-fcgi directly with the cgi-fcgi utility directly and this is the response I get:

env -i SCRIPT_NAME=index.php DOCUMENT_ROOT=/var/www/domains/dummysite/web SCRIPT_FILENAME=/var/www/domains/dummysite/web/index.php QUERY_STRING= REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000
Status: 404 Not Found
X-Powered-By: PHP/5.2.10-2ubuntu6
Content-type: text/html

No input file specified.

The same result applies to all calls with SCRIPT_FILENAME set to

  • /index.php
  • /web/index.php
  • /dummysite/web/index.php
  • /domains/dummysite/web/index.php
  • /www/domains/dummysite/web/index.php
  • /var/www/domains/dummysite/web/index.php
  • /karmic/var/www/domains/dummysite/web/index.php
  • /chroot/karmic/var/www/domains/dummysite/web/index.php
  • /var/chroot/karmic/var/www/domains/dummysite/web/index.php

And I have tried the same with various DOCUMENT_ROOT.

Best Answer

TL;DR

/etc/nginx/sites-enabled/dummysite -> /etc/nginx/sites-available/dummysite
...
location ~ \.php {
    root /var/www/domains/dummysite/web;
    fastcgi_pass 127.0.0.1:9000
    include fastcgi_params;
}
...

And

/etc/nginx/fastcgi_params
...
fastcgi_param SCRIPT_NAME $document_root$fastcgi_script_name;
...

Long solution

Ok, this is how I solved it (I thought it might come handy to someone). As php-cgi isn't very verbose I resorted to using strace to capture php's file operations to disk.

 sudo strace -p <pid-of-first-php-process> -p <pid-of-2nd-php> ... -p <pid-of-nth-php> -e trace=all -s 4096

Then I called the php-fcgi directly with:

env -i SCRIPT_NAME=index.php DOCUMENT_ROOT=/var/www/domains/dummysite/web SCRIPT_FILENAME=/var/www/domains/dummysite/web/index.php QUERY_STRING= REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000

And the interesting lines in the strace was

[pid 24822] read(3, "\v\tSCRIPT_NAMEindex.php\r\33DOCUMENT_ROOT/var/www/domains/dummysite/web\17%SCRIPT_FILENAME/var/www/domains/dummysite/web/index.php\f\0QUERY_STRING\16\3REQUEST_METHODGET\0", 152) = 152
[pid 24822] lstat("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
[pid 24822] lstat("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
[pid 24822] lstat("/var/www/domains", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
[pid 24822] lstat("/var/www/domains/dummysite", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
[pid 24822] lstat("/var/www/domains/dummysite/web", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
[pid 24822] lstat("/var/www/domains/dummysite/web/index.php", {st_mode=S_IFREG|0644, st_size=31, ...}) = 0
[pid 24822] open("/index.php", O_RDONLY) = -1 ENOENT (No such file or directory)

Ok, so clearly, it successfully tried to lstat [...]/web/index.php (which has the correct 0644 permissions), but then it tried to open /index.php. This led me to experiment with SCRIPT_NAME, and voila!

env -i SCRIPT_NAME=/var/www/domains/dummysite/web/index.php DOCUMENT_ROOT=/var/www/domains/dummysite/web SCRIPT_FILENAME=/var/www/domains/dummysite/web/index.php QUERY_STRING= REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000
X-Powered-By: PHP/5.2.10-2ubuntu6
Content-type: text/html

<h1>Hello World!</h1> Foo bar...

So, my first problem was that my nginx config should read

        fastcgi_param SCRIPT_NAME /var/www/domains/dummysite/web$fastcgi_script_name;
#           see what i did here   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

To be honest; I have no idea why this is. I thought the purpose of SCRIPT_NAME is not to point to the file but merely state the name of the file. But I guess I must have misunderstood its purpose.

But, unfortunately, my issues where not over just yet. For when I tried to curl http://dummysite.wtf/, it still said No input file specified.

So, yet again, strace to the rescue!

[pid 24819] read(3, "\r\33DOCUMENT_ROOT/var/www/domains/dummysite/web\17%SCRIPT_FILENAME/var/www/domains/dummysite/web/index.php\v%SCRIPT_NAME/var/www/domains/dummysite/web/index.php\f\0QUERY_STRING\16\3REQUEST_METHODGET\f\0CONTENT_TYPE\16\0CONTENT_LENGTH\0177SCRIPT_FILENAME/var/chroot/karmic/var/www/domains/dummysite/web/index.php\v\nSCRIPT_NAME/index.php\v\1REQUEST_URI/\f\nDOCUMENT_URI/index.php\r-DOCUMENT_ROOT/var/chroot/karmic/var/www/domains/dummysite/web\17\10SERVER_PROTOCOLHTTP/1.1\21\7GATEWAY_INTERFACECGI/1.1\17\vSERVER_SOFTWAREnginx/1.4.6\v\tREMOTE_ADDR127.0.0.1\v\5REMOTE_PORT46644\v\tSERVER_ADDR127.0.0.1\v\2SERVER_PORT80\v\nSERVER_NAMEdummysite.wtf\17\3REDIRECT_STATUS200\17\vHTTP_USER_AGENTcurl/7.35.0\t\nHTTP_HOSTdummysite.wtf\v\3HTTP_ACCEPT*/*\0\0\0\0\0\0", 672) = 672

And right there is the answer, SCRIPT_NAME, SCRIPT_FILENAME and DOCUMENT_ROOT appears two times, the first time they are correct, the second time with an incorrect value. It turns out the include fastcgi_params directive in the nginx server block will insert these variables itself, and as I put this include statement last in my location block, it was effectively overriding my previous settings.

This is how I fixed this:

/etc/nginx/sites-enabled/dummysite -> /etc/nginx/sites-available/dummysite
...
location ~ \.php {
    root /var/www/domains/dummysite/web;
#   ^ This line will set the $document_root variable used later on
    fastcgi_pass 127.0.0.1:9000
    include fastcgi_params;
}
...

And in the fastcgi_params file that is included i changed one line to this

/etc/nginx/fastcgi_params
...
fastcgi_param   SCRIPT_NAME             $document_root$fastcgi_script_name;
# This is needed for chroot to work -> ^$document_root^ 
...

And finally, glorious success!

$ curl http://dummysite.wtf
<h1>Hello World!</h1> Foo bar...

This is one way to run a fatally old php version inside a chrooted jail on top of modern software such as ubuntu 14.04 LTS and nginx 1.4.x :-)