I was trying to run npm
from a PHP web page, but it would never run. I always got an exit code of 127 and no output. After doing some testing I narrowed down the problem to the shebang in npm
which looks like this:
#!/usr/bin/env node
Pretty standard, but I did up some test code:
<?php
$result = exec("/usr/bin/env node --version", $output, $exit);
var_dump($result);
var_dump($exit);
$result = exec("node --version", $output, $exit);
var_dump($result);
var_dump($exit);
$result = exec("/usr/bin/env gzip --version", $output, $exit);
var_dump($result);
var_dump($exit);
And got this output in my browser:
string(0) ""
int(127)
string(6) "v8.4.0"
int(0)
string(28) "Written by Jean-loup Gailly."
int(0)
I enabled catch_workers_output
in the PHP-FPM config and saw this in the PHP log:
[18-Aug-2017 15:15:35] WARNING: [pool web] child 27872 said into stderr: "/usr/bin/env: "
[18-Aug-2017 15:15:35] WARNING: [pool web] child 27872 said into stderr: "node"
[18-Aug-2017 15:15:35] WARNING: [pool web] child 27872 said into stderr: ": No such file or directory"
[18-Aug-2017 15:15:35] WARNING: [pool web] child 27872 said into stderr: ""
I also tried running exec("which node")
from the web server and saw this in the PHP log:
[18-Aug-2017 15:31:12] WARNING: [pool web] child 27873 said into stderr: "which: no node in ((null))"
I tried running var_dump(exec('echo $PATH'))
and got this output:
string(28) "/usr/local/bin:/bin:/usr/bin"
This seems related to how the env
command processes the path. I tried setting it manually using a fastcgi_param PATH
directive in nginx.conf
but it made no change to the above output.
Running env
alone and printing the output looks like this, with no PATH entry:
Array
(
[0] => USER=nginx
[1] => PWD=/var/www/html
[2] => SHLVL=1
[3] => HOME=/var/cache/nginx
[4] => _=/bin/env
)
I'm on a RHEL-based distro, with SELinux disabled, running PHP-FPM 5.6.31 via UNIX socket from Nginx 1.12.1. /usr/local/bin/node
is a symbolic link to /usr/local/nodejs/bin/node
which has 775 permissions. The nginx user owns the /usr/local/nodejs
directory and all its descendants, for testing purposes. Any suggestions?
Note that if I run the PHP code from CLI (as the nginx user) it works as expected, so this is definitely related to the CGI/FPM environment.
$ su -s "/bin/sh" -c "/var/www/html/test.php" nginx
string(6) "v8.4.0"
int(0)
string(6) "v8.4.0"
int(0)
string(28) "Written by Jean-loup Gailly."
int(0)
You need to set PATH
environment in /etc/php5/fpm/pool.d/www.conf
to include /usr/local/bin/node
.
In ubuntu distro it is commented out:
;env[PATH] = /usr/local/bin:/usr/bin:/bin
so
env[PATH] = /usr/local/bin/node
should do the job. RHEL shouldn't be very different, but even if the line is not there, you can always add it.
Don't forget to restart php-fpm.
If nothing works, you can always set the path explicitly when you call npm
:
exec('PATH=$PATH:/usr/local/bin/node npm');
but it is the last resort and I wouldn't recommend it.
One thing that makes me curious... Why running script as ngnix user is working at all?
For real, ngnix is asking php-fpm for process, and it is the PHP-FPM that is executing your script. So, probably PHP-FPM service is one, that is missing permissions to your /usr/bin/env node
.
Keeping things easy, ngnix needs permissions to put its virtual hands on PHP-FPM. But it is the PHP-FPM that needs permissions to put hands on files.
To keep solution easy, it will probably be fixed by adding PHP-FPM user (www-data?) to a group, that node binary is in and ngnix user is already in.
Or better follow these suggestions: https://serverfault.com/a/52703
Edit #1:
I would still look after privelages.
/usr/local/bin/node is a symbolic link to /usr/local/nodejs/bin/node which has 775 permissions. The nginx user owns the /usr/local/nodejs
Setting up a particular user on /usr/local
tree withoux -x
is kind of not standard. By the time /usr/local
was ment to be repository for any user.
Please run this script under different circumstances (from CLI, FPM, as ngnix, etx) and inspect your privelages:
<?php
$myuid = getmyuid();
$mygid = getmygid();
// you should change to check /usr/local/nodejs/bin/node
$path = '/usr/local/n/versions/node/7.2.0/bin/node';
$patharray = array_filter(explode('/', $path));
$dump = "%s \t %s \t %s%s%s \t %s" . PHP_EOL;
$fileOrFolder = __FILE__;
printf("uid \t gid \t rwx \t file" . PHP_EOL);
printf($dump, $myuid, $mygid, (is_readable($fileOrFolder) ? 'r' : '-'), (is_writable($fileOrFolder) ? 'w' : '-'), (is_executable($fileOrFolder) ? 'x' : '-'), $fileOrFolder);
$fileOrFolder = '';
while(count($patharray) > 0) {
$fileOrFolder .= ('/' . array_shift($patharray));
$myuid = posix_getpwuid(fileowner($fileOrFolder));
$mygid = posix_getgrgid(filegroup($fileOrFolder));
printf($dump, $myuid['name'], $mygid['name'], (is_readable($fileOrFolder) ? 'r' : '-'), (is_writable($fileOrFolder) ? 'w' : '-'), (is_executable($fileOrFolder) ? 'x' : '-'), $fileOrFolder);
if (is_dir($fileOrFolder) && !is_executable($fileOrFolder)) {
echo PHP_EOL . "Ouh, cant go further cause of privelages" . PHP_EOL;
break;
}
}
Mine output proofs, that despite being a different user, PHP can look all the way down to target file:
@riddick:~/temp$ php phpowner.php
uid gid rwx file
1000 1000 rw- /home/yergo/temp/phpowner.php
root root r-x /usr
root root r-x /usr/local
root root r-x /usr/local/n
root root r-x /usr/local/n/versions
root root r-x /usr/local/n/versions/node
root root r-x /usr/local/n/versions/node/7.2.0
r-x /usr/local/n/versions/node/7.2.0/bin
r-x /usr/local/n/versions/node/7.2.0/bin/node